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.
- package/README.md +540 -368
- package/engine-requirements.js +0 -36
- package/lib/Socket/chats.js +1 -0
- package/lib/Socket/index.js +0 -34
- package/lib/Socket/messages-send.js +81 -124
- package/lib/Socket/newsletter.js +37 -3
- package/lib/Socket/socket.js +0 -33
- package/lib/Utils/bot-capabilities.js +84 -0
- package/lib/Utils/channel-policy.js +25 -1
- package/lib/Utils/index.js +1 -0
- package/lib/Utils/messages.js +13 -72
- package/lib/Utils/meta-compositing.js +7 -11
- package/lib/Utils/welcome-flow.js +2 -6
- package/lib/index.d.ts +6 -0
- package/package.json +2 -2
- package/lib/Socket/messages-send.js[latest working] +0 -1759
- package/lib/Utils/integrity.js +0 -296
package/engine-requirements.js
CHANGED
|
@@ -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
|
-
}
|
package/lib/Socket/chats.js
CHANGED
package/lib/Socket/index.js
CHANGED
|
@@ -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
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1443
|
-
|
|
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
|
|
1517
|
-
|
|
1518
|
-
|
|
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
|
|
1526
|
-
|
|
1527
|
-
|
|
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
|
-
|
|
2433
|
-
|
|
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
|
-
|
|
2445
|
-
|
|
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);
|
package/lib/Socket/newsletter.js
CHANGED
|
@@ -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
|
-
...(
|
|
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:
|
|
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
|
|
226
|
+
sock.logger?.error?.({ error }, 'Failed to decode newsletter message');
|
|
193
227
|
}
|
|
194
228
|
}
|
|
195
229
|
return messages;
|
package/lib/Socket/socket.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
];
|
package/lib/Utils/index.js
CHANGED
|
@@ -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';
|