plogme 1.0.0
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/LICENSE +61 -0
- package/README.md +212 -0
- package/WAProto/V +1 -0
- package/WAProto/WAProto.proto +8051 -0
- package/WAProto/index.js +289292 -0
- package/engine-requirements.js +51 -0
- package/lib/Defaults/ban-checker.js +44 -0
- package/lib/Defaults/index.js +158 -0
- package/lib/Signal/Group/ciphertext-message.js +11 -0
- package/lib/Signal/Group/group-session-builder.js +29 -0
- package/lib/Signal/Group/group_cipher.js +81 -0
- package/lib/Signal/Group/index.js +11 -0
- package/lib/Signal/Group/keyhelper.js +17 -0
- package/lib/Signal/Group/sender-chain-key.js +25 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +62 -0
- package/lib/Signal/Group/sender-key-message.js +65 -0
- package/lib/Signal/Group/sender-key-name.js +47 -0
- package/lib/Signal/Group/sender-key-record.js +40 -0
- package/lib/Signal/Group/sender-key-state.js +83 -0
- package/lib/Signal/Group/sender-message-key.js +25 -0
- package/lib/Signal/libsignal.js +428 -0
- package/lib/Signal/lid-mapping.js +280 -0
- package/lib/Socket/Client/index.js +2 -0
- package/lib/Socket/Client/types.js +10 -0
- package/lib/Socket/Client/websocket.js +53 -0
- package/lib/Socket/aigroups.js +315 -0
- package/lib/Socket/ban-checker.js +153 -0
- package/lib/Socket/business.js +407 -0
- package/lib/Socket/chats.js +1831 -0
- package/lib/Socket/chats.js[old] +1227 -0
- package/lib/Socket/communities.js +431 -0
- package/lib/Socket/graphql.js +899 -0
- package/lib/Socket/groups.js +549 -0
- package/lib/Socket/groups.js[old] +347 -0
- package/lib/Socket/index.js +71 -0
- package/lib/Socket/interactive-handler.js +600 -0
- package/lib/Socket/interop.js +563 -0
- package/lib/Socket/managed-account.js +208 -0
- package/lib/Socket/messages-recv.js +2144 -0
- package/lib/Socket/messages-send.js +2650 -0
- package/lib/Socket/messages-send.js[latest working] +1759 -0
- package/lib/Socket/mex.js +41 -0
- package/lib/Socket/newsletter.js +226 -0
- package/lib/Socket/privacy.js +446 -0
- package/lib/Socket/registration.js +428 -0
- package/lib/Socket/socket.js +1050 -0
- package/lib/Socket/username.js +229 -0
- package/lib/Store/index.js +4 -0
- package/lib/Store/make-cache-manager-store.js +89 -0
- package/lib/Store/make-in-memory-store.js +420 -0
- package/lib/Store/make-ordered-dictionary.js +78 -0
- package/lib/Store/object-repository.js +23 -0
- package/lib/Types/Auth.js +1 -0
- package/lib/Types/Bussines.js +1 -0
- package/lib/Types/Call.js +1 -0
- package/lib/Types/Chat.js +7 -0
- package/lib/Types/Contact.js +1 -0
- package/lib/Types/Events.js +1 -0
- package/lib/Types/GroupMetadata.js +1 -0
- package/lib/Types/Label.js +24 -0
- package/lib/Types/LabelAssociation.js +6 -0
- package/lib/Types/Message.js +17 -0
- package/lib/Types/Mex.js +39 -0
- package/lib/Types/Product.js +1 -0
- package/lib/Types/RichType.js +22 -0
- package/lib/Types/Signal.js +1 -0
- package/lib/Types/Socket.js +2 -0
- package/lib/Types/State.js +48 -0
- package/lib/Types/USync.js +1 -0
- package/lib/Types/index.js +25 -0
- package/lib/Utils/auth-utils.js +301 -0
- package/lib/Utils/bot-planning-replay.js +169 -0
- package/lib/Utils/browser-utils.js +28 -0
- package/lib/Utils/business.js +230 -0
- package/lib/Utils/channel-policy.js +1 -0
- package/lib/Utils/chat-utils.js +885 -0
- package/lib/Utils/chat-utils.js[old] +871 -0
- package/lib/Utils/companion-reg-client-utils.js +32 -0
- package/lib/Utils/consumer-application.js +102 -0
- package/lib/Utils/crypto.js +117 -0
- package/lib/Utils/decode-wa-message.js +484 -0
- package/lib/Utils/event-buffer.js +589 -0
- package/lib/Utils/games/slot-machine.js +258 -0
- package/lib/Utils/games/website-preview.js +293 -0
- package/lib/Utils/generics.js +390 -0
- package/lib/Utils/generics.js@old +385 -0
- package/lib/Utils/group-history.js +56 -0
- package/lib/Utils/history.js +130 -0
- package/lib/Utils/identity-change-handler.js +48 -0
- package/lib/Utils/index.js +35 -0
- package/lib/Utils/integrity.js +296 -0
- package/lib/Utils/jid-display-normalization.js +209 -0
- package/lib/Utils/link-preview.js +84 -0
- package/lib/Utils/logger.js +2 -0
- package/lib/Utils/lt-hash.js +7 -0
- package/lib/Utils/make-mutex.js +32 -0
- package/lib/Utils/message-composer.js +467 -0
- package/lib/Utils/message-inspect.js +67 -0
- package/lib/Utils/message-retry-manager.js +241 -0
- package/lib/Utils/messages-media.js +1006 -0
- package/lib/Utils/messages-media.js[old] +916 -0
- package/lib/Utils/messages.js +1982 -0
- package/lib/Utils/messages.js[old] +1902 -0
- package/lib/Utils/meta-ai-msmsg.js +130 -0
- package/lib/Utils/meta-compositing.js +202 -0
- package/lib/Utils/native-flow.js +100 -0
- package/lib/Utils/noise-handler.js +200 -0
- package/lib/Utils/offline-node-processor.js +39 -0
- package/lib/Utils/pre-key-manager.js +105 -0
- package/lib/Utils/process-message.js +582 -0
- package/lib/Utils/reporting-utils.js +257 -0
- package/lib/Utils/rich-message-utils.js +942 -0
- package/lib/Utils/rich-message-utils.js[Old] +387 -0
- package/lib/Utils/signal.js +200 -0
- package/lib/Utils/stanza-ack.js +37 -0
- package/lib/Utils/sync-action-utils.js +47 -0
- package/lib/Utils/tc-token-utils.js +162 -0
- package/lib/Utils/use-multi-file-auth-state.js +120 -0
- package/lib/Utils/use-single-file-auth-state.js +96 -0
- package/lib/Utils/use-sqlite-auth-state.js +108 -0
- package/lib/Utils/validate-connection.js +206 -0
- package/lib/Utils/view-once-utils.js +206 -0
- package/lib/Utils/voip-rekey.js +20 -0
- package/lib/Utils/welcome-flow.js +220 -0
- package/lib/WABinary/constants.js +1463 -0
- package/lib/WABinary/decode.js +261 -0
- package/lib/WABinary/encode.js +219 -0
- package/lib/WABinary/generic-utils.js +227 -0
- package/lib/WABinary/index.js +5 -0
- package/lib/WABinary/jid-utils.js +95 -0
- package/lib/WABinary/types.js +1 -0
- package/lib/WAM/BinaryInfo.js +9 -0
- package/lib/WAM/constants.js +22852 -0
- package/lib/WAM/encode.js +149 -0
- package/lib/WAM/index.js +3 -0
- package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +96 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +56 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +53 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -0
- package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +70 -0
- package/lib/WAUSync/Protocols/USyncPictureProtocol.js +28 -0
- package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +25 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -0
- package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +34 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +24 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -0
- package/lib/WAUSync/Protocols/index.js +12 -0
- package/lib/WAUSync/USyncQuery.js +201 -0
- package/lib/WAUSync/USyncUser.js +54 -0
- package/lib/WAUSync/index.js +3 -0
- package/lib/index.d.ts +406 -0
- package/lib/index.js +11 -0
- package/package.json +125 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package integrity & attribution guard.
|
|
3
|
+
*
|
|
4
|
+
* Verifies at runtime that this copy is the genuine, un-rebranded
|
|
5
|
+
* `@crysnovax/baileys` distribution and that the attribution-required
|
|
6
|
+
* license is intact. If the package identity has been renamed/stripped
|
|
7
|
+
* (the usual result of AI-assisted rebranding), premium features are
|
|
8
|
+
* disabled with a loud warning so the stolen copy visibly degrades
|
|
9
|
+
* instead of quietly becoming a "free rebrand".
|
|
10
|
+
*
|
|
11
|
+
* A remote attestation layer sits on top: on socket connect the client
|
|
12
|
+
* phones home to bailey.crysnovax.link with its package identity and a
|
|
13
|
+
* machine fingerprint. The worker answers "genuine", "revoked", or
|
|
14
|
+
* "rebranded"; "revoked" / "rebranded" disable premium features and can
|
|
15
|
+
* be issued remotely by the operator at any time.
|
|
16
|
+
*
|
|
17
|
+
* DEVELOPER ESCAPE HATCH (so the maintainer never locks themself out):
|
|
18
|
+
* Set CRYSNOVAX_TRUST_MODE=1 in the environment to bypass the local
|
|
19
|
+
* integrity checks entirely. Use this when you are actively editing
|
|
20
|
+
* the package (e.g. with an AI assistant) and a check trips on a
|
|
21
|
+
* legitimate edit. Rebranders can set it too — but at that point they
|
|
22
|
+
* are deliberately tampering, which the license already prohibits.
|
|
23
|
+
*
|
|
24
|
+
* Anti-evasion notes:
|
|
25
|
+
* - The expected identity is stored ENCODED and only decoded at runtime,
|
|
26
|
+
* so naive find-and-replace / "remove every mention of crysnovax"
|
|
27
|
+
* rewrites miss it.
|
|
28
|
+
* - This module is imported from several unrelated entry points
|
|
29
|
+
* (socket factory, base socket, message composer, message sender), so
|
|
30
|
+
* removing one hook leaves the others active.
|
|
31
|
+
* - The checks never throw and never break legitimate installs: a
|
|
32
|
+
* manifest that cannot be read — or a worker that cannot be reached —
|
|
33
|
+
* is treated as genuine rather than risking false-positive degradation.
|
|
34
|
+
*/
|
|
35
|
+
import { readFileSync } from 'fs';
|
|
36
|
+
import { createHash } from 'node:crypto';
|
|
37
|
+
import os from 'node:os';
|
|
38
|
+
import { createRequire } from 'node:module';
|
|
39
|
+
import { DONATE_URL, LIBRARY_NAME } from '../Defaults/index.js';
|
|
40
|
+
|
|
41
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
42
|
+
const decode = b64 => Buffer.from(b64, 'base64').toString('utf8');
|
|
43
|
+
|
|
44
|
+
// ─── Encoded brand identity (decoded below at runtime) ──────────────────────
|
|
45
|
+
const EXPECTED = {
|
|
46
|
+
// @crysnovax/baileys
|
|
47
|
+
name: 'QGNyeXNub3ZheC9iYWlsZXlz',
|
|
48
|
+
// crysnovax
|
|
49
|
+
author: 'Y3J5c25vdmF4',
|
|
50
|
+
// https://web.crysnovax.link
|
|
51
|
+
site: 'aHR0cHM6Ly93ZWIuY3J5c25vdmF4Lmxpbms=',
|
|
52
|
+
// SEE LICENSE IN LICENSE
|
|
53
|
+
licenseId: 'U0VFIExJQ0VOU0UgSU4gTElDRU5TRQ==',
|
|
54
|
+
// crysnovax/bailey
|
|
55
|
+
libraryName: 'Y3J5c25vdmF4L2JhaWxleQ==',
|
|
56
|
+
// Crysnovax Source License v1.0 (canary phrase inside LICENSE)
|
|
57
|
+
licenseCanary: 'Q3J5c25vdmF4IFNvdXJjZSBMaWNlbnNlIHYxLjA=',
|
|
58
|
+
// https://bailey.crysnovax.link/api/v1/verify
|
|
59
|
+
verifyUrl: 'aHR0cHM6Ly9iYWlsZXkuY3J5c25vdmF4LmxpbmsvYXBpL3YxL3ZlcmlmeQ=='
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Public brand string used for messages (decoded once). */
|
|
63
|
+
export const getBrandName = () => decode(EXPECTED.name);
|
|
64
|
+
|
|
65
|
+
/** Developer escape hatch — set to '1' while actively editing the package. */
|
|
66
|
+
export const isTrustMode = () => process.env.CRYSNOVAX_TRUST_MODE === '1';
|
|
67
|
+
|
|
68
|
+
let cachedReport = null;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Returns { genuine, reasons, warnings } for this copy. Memoized per process.
|
|
72
|
+
* - CRYSNOVAX_TRUST_MODE=1 → genuine (developer mode)
|
|
73
|
+
* - manifest readable + all checks pass → genuine: true
|
|
74
|
+
* - manifest readable + identity mismatch → genuine: false (reasons)
|
|
75
|
+
* - cosmetic drift (URL/canary touched) → genuine: true (warnings only)
|
|
76
|
+
* - manifest unreadable (unusual packaging) → genuine: true (no false positives)
|
|
77
|
+
*/
|
|
78
|
+
export const getIntegrityReport = () => {
|
|
79
|
+
if (cachedReport) {
|
|
80
|
+
return cachedReport;
|
|
81
|
+
}
|
|
82
|
+
if (isTrustMode()) {
|
|
83
|
+
cachedReport = {
|
|
84
|
+
genuine: true,
|
|
85
|
+
reasons: ['trust mode'],
|
|
86
|
+
warnings: ['CRYSNOVAX_TRUST_MODE=1 is set — integrity checks bypassed (developer mode)']
|
|
87
|
+
};
|
|
88
|
+
return cachedReport;
|
|
89
|
+
}
|
|
90
|
+
const reasons = [];
|
|
91
|
+
const warnings = [];
|
|
92
|
+
let manifest = null;
|
|
93
|
+
try {
|
|
94
|
+
// lib/Utils/integrity.js → ../../package.json = package root manifest
|
|
95
|
+
manifest = nodeRequire('../../package.json');
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// manifest not resolvable — never degrade on packaging oddities alone
|
|
99
|
+
}
|
|
100
|
+
if (manifest) {
|
|
101
|
+
const author = typeof manifest.author === 'string'
|
|
102
|
+
? manifest.author
|
|
103
|
+
: manifest.author?.name;
|
|
104
|
+
// Hard identity checks — these are what define a rebrand.
|
|
105
|
+
if (manifest.name !== decode(EXPECTED.name)) {
|
|
106
|
+
reasons.push(`package renamed to "${manifest.name}"`);
|
|
107
|
+
}
|
|
108
|
+
if (author !== decode(EXPECTED.author)) {
|
|
109
|
+
reasons.push('author attribution removed');
|
|
110
|
+
}
|
|
111
|
+
if (manifest.license !== decode(EXPECTED.licenseId)) {
|
|
112
|
+
reasons.push(`license swapped to "${manifest.license}"`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// LICENSE file must exist and carry the license identifier canary
|
|
116
|
+
try {
|
|
117
|
+
const licenseText = readFileSync(new URL('../../LICENSE', import.meta.url), 'utf8');
|
|
118
|
+
if (!licenseText.includes(decode(EXPECTED.licenseCanary))) {
|
|
119
|
+
// Missing canary = likely edited LICENSE; still licensable, so warn
|
|
120
|
+
warnings.push('license file was edited (canary phrase missing)');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
reasons.push('license file missing');
|
|
125
|
+
}
|
|
126
|
+
// Brand constants — cosmetic drift warns but never disables features,
|
|
127
|
+
// so maintainers editing defaults with an AI don't lock themselves out.
|
|
128
|
+
if (DONATE_URL !== decode(EXPECTED.site)) {
|
|
129
|
+
warnings.push('attribution URL changed');
|
|
130
|
+
}
|
|
131
|
+
if (LIBRARY_NAME !== decode(EXPECTED.libraryName)) {
|
|
132
|
+
warnings.push('library identity constant changed');
|
|
133
|
+
}
|
|
134
|
+
cachedReport = {
|
|
135
|
+
genuine: reasons.length === 0,
|
|
136
|
+
reasons,
|
|
137
|
+
warnings
|
|
138
|
+
};
|
|
139
|
+
return cachedReport;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
let rebrandWarned = false;
|
|
143
|
+
let warningsShown = false;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Logs a loud, one-time warning when this copy fails the integrity check.
|
|
147
|
+
* Also surfaces cosmetic warnings once (genuine but drifted) so the
|
|
148
|
+
* maintainer knows a check was soft-tripped. Returns the report.
|
|
149
|
+
*/
|
|
150
|
+
export const warnIfRebranded = (logger) => {
|
|
151
|
+
const report = getIntegrityReport();
|
|
152
|
+
if (!report.warnings.length && !rebrandWarned) {
|
|
153
|
+
return report;
|
|
154
|
+
}
|
|
155
|
+
const log = (level, message) => {
|
|
156
|
+
if (logger && typeof logger[level] === 'function') {
|
|
157
|
+
logger[level]({ reasons: report.reasons, warnings: report.warnings }, message);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
console.warn('\n' + message + '\n');
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
if (report.warnings.length && !warningsShown) {
|
|
164
|
+
warningsShown = true;
|
|
165
|
+
log('warn',
|
|
166
|
+
`⚙ ${getBrandName()} integrity: ${report.warnings.join('; ')}. ` +
|
|
167
|
+
`Features stay enabled; if this was an accidental edit, set CRYSNOVAX_TRUST_MODE=1 while developing.`);
|
|
168
|
+
}
|
|
169
|
+
if (!report.genuine && !rebrandWarned) {
|
|
170
|
+
rebrandWarned = true;
|
|
171
|
+
log('warn',
|
|
172
|
+
`✖ UNAUTHORIZED REBRANDED COPY DETECTED — this is not an official ` +
|
|
173
|
+
`${getBrandName()} distribution (${report.reasons.join('; ')}). ` +
|
|
174
|
+
`Rebranding or removing attribution violates the ${decode(EXPECTED.licenseCanary)}. ` +
|
|
175
|
+
`Premium features are disabled on this copy. Install the genuine package: ` +
|
|
176
|
+
`https://www.npmjs.com/package/${getBrandName()}`);
|
|
177
|
+
}
|
|
178
|
+
return report;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// ─── Remote attestation (bailey.crysnovax.link) ─────────────────────────────
|
|
182
|
+
|
|
183
|
+
let fingerprint = null;
|
|
184
|
+
const getFingerprint = () => {
|
|
185
|
+
if (fingerprint) {
|
|
186
|
+
return fingerprint;
|
|
187
|
+
}
|
|
188
|
+
const cpu = os.cpus?.()?.[0]?.model || 'unknown-cpu';
|
|
189
|
+
const raw = [
|
|
190
|
+
os.hostname(),
|
|
191
|
+
os.platform(),
|
|
192
|
+
os.arch(),
|
|
193
|
+
cpu,
|
|
194
|
+
process.version
|
|
195
|
+
].join('|');
|
|
196
|
+
fingerprint = createHash('sha256').update(raw).digest('hex').slice(0, 32);
|
|
197
|
+
return fingerprint;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
let remoteRevoked = false;
|
|
201
|
+
let remoteChecked = false;
|
|
202
|
+
let remoteError = false;
|
|
203
|
+
let remoteWarned = false;
|
|
204
|
+
|
|
205
|
+
/** Current remote state: 'genuine' | 'revoked' | 'unknown'. */
|
|
206
|
+
export const getRemoteStatus = () => {
|
|
207
|
+
if (!remoteChecked || remoteError) {
|
|
208
|
+
return 'unknown';
|
|
209
|
+
}
|
|
210
|
+
return remoteRevoked ? 'revoked' : 'genuine';
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Phones home once per process. NEVER throws and FAILS OPEN: any network
|
|
215
|
+
* error, timeout, or malformed reply leaves premium features enabled —
|
|
216
|
+
* only an explicit revoked/rebranded verdict disables them.
|
|
217
|
+
*/
|
|
218
|
+
export const refreshRemoteStatus = async (logger) => {
|
|
219
|
+
if (remoteChecked) {
|
|
220
|
+
return getRemoteStatus();
|
|
221
|
+
}
|
|
222
|
+
remoteChecked = true;
|
|
223
|
+
try {
|
|
224
|
+
let pkg = null;
|
|
225
|
+
let version = '';
|
|
226
|
+
try {
|
|
227
|
+
pkg = nodeRequire('../../package.json');
|
|
228
|
+
version = pkg?.version || '';
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// manifest unreadable — still send what we know
|
|
232
|
+
}
|
|
233
|
+
const controller = new AbortController();
|
|
234
|
+
const timer = setTimeout(() => controller.abort(), 4000);
|
|
235
|
+
try {
|
|
236
|
+
const res = await fetch(decode(EXPECTED.verifyUrl), {
|
|
237
|
+
method: 'POST',
|
|
238
|
+
headers: { 'Content-Type': 'application/json' },
|
|
239
|
+
body: JSON.stringify({
|
|
240
|
+
package: pkg?.name || '',
|
|
241
|
+
version,
|
|
242
|
+
fingerprint: getFingerprint()
|
|
243
|
+
}),
|
|
244
|
+
signal: controller.signal
|
|
245
|
+
});
|
|
246
|
+
if (!res.ok) {
|
|
247
|
+
remoteError = true;
|
|
248
|
+
return 'unknown';
|
|
249
|
+
}
|
|
250
|
+
const body = await res.json();
|
|
251
|
+
if (body?.status === 'revoked' || body?.status === 'rebranded') {
|
|
252
|
+
remoteRevoked = true;
|
|
253
|
+
if (!remoteWarned) {
|
|
254
|
+
remoteWarned = true;
|
|
255
|
+
const message = `✖ THIS INSTALL HAS BEEN REVOKED (${body.status}) — premium features are disabled.`;
|
|
256
|
+
if (logger && typeof logger.warn === 'function') {
|
|
257
|
+
logger.warn({ status: body.status }, message);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
console.warn('\n' + message + '\n');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
finally {
|
|
266
|
+
clearTimeout(timer);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
// fail open — network error/timeout never bricks a genuine install
|
|
271
|
+
remoteError = true;
|
|
272
|
+
}
|
|
273
|
+
return getRemoteStatus();
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Gate for premium features: returns true only on a genuine copy that has
|
|
278
|
+
* not been remotely revoked. On a rebranded/revoked copy, warns once and
|
|
279
|
+
* returns false so the feature degrades to its non-premium behavior.
|
|
280
|
+
*/
|
|
281
|
+
export const premiumEnabled = (logger) => {
|
|
282
|
+
// Remote revocation wins (its warning was already emitted by refreshRemoteStatus)
|
|
283
|
+
if (remoteRevoked) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
const report = getIntegrityReport();
|
|
287
|
+
if (!report.genuine) {
|
|
288
|
+
warnIfRebranded(logger);
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
// Genuine but drifted — surface the warnings once, keep features on
|
|
292
|
+
if (report.warnings.length) {
|
|
293
|
+
warnIfRebranded(logger);
|
|
294
|
+
}
|
|
295
|
+
return true;
|
|
296
|
+
};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { isHostedLidUser, isHostedPnUser, isLidUser, isPnUser, jidDecode } from '../WABinary/index.js';
|
|
2
|
+
|
|
3
|
+
const createLidPnDebug = (logger) => {
|
|
4
|
+
const enabled = process.env.LID_PN_DEBUG === '1' || process.env.LID_PN_DEBUG === 'true';
|
|
5
|
+
return (phase, payload) => {
|
|
6
|
+
if (!enabled) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
logger?.debug({ ...payload, phase }, 'lid->pn normalization trace');
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const fallbackPnFromLidJid = (jid) => {
|
|
14
|
+
const decoded = jidDecode(jid);
|
|
15
|
+
const rawUser = decoded?.user || (typeof jid === 'string' ? jid.split('@')[0] : '');
|
|
16
|
+
const user = rawUser.split(':')[0];
|
|
17
|
+
if (!user) {
|
|
18
|
+
return jid;
|
|
19
|
+
}
|
|
20
|
+
return `${user}@s.whatsapp.net`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const toDisplayPnJid = (jid) => {
|
|
24
|
+
if (!jid || typeof jid !== 'string') {
|
|
25
|
+
return jid;
|
|
26
|
+
}
|
|
27
|
+
const decoded = jidDecode(jid);
|
|
28
|
+
if (!decoded?.user) {
|
|
29
|
+
return jid;
|
|
30
|
+
}
|
|
31
|
+
if (!isPnUser(jid) && !isHostedPnUser(jid)) {
|
|
32
|
+
return jid;
|
|
33
|
+
}
|
|
34
|
+
const user = decoded.user.split(':')[0];
|
|
35
|
+
if (!user) {
|
|
36
|
+
return jid;
|
|
37
|
+
}
|
|
38
|
+
const server = decoded.server === 'hosted' ? 'hosted' : 's.whatsapp.net';
|
|
39
|
+
return `${user}@${server}`;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const buildLidPnHints = (key) => {
|
|
43
|
+
const hints = new Map();
|
|
44
|
+
if (!key) {
|
|
45
|
+
return hints;
|
|
46
|
+
}
|
|
47
|
+
const addHint = (lid, pn) => {
|
|
48
|
+
if (!isLidUser(lid) && !isHostedLidUser(lid)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!isPnUser(pn) && !isHostedPnUser(pn)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
hints.set(lid, pn);
|
|
55
|
+
const lidDecoded = jidDecode(lid);
|
|
56
|
+
const pnDecoded = jidDecode(pn);
|
|
57
|
+
if (lidDecoded?.user && pnDecoded?.user) {
|
|
58
|
+
hints.set(`${lidDecoded.user}@lid`, `${pnDecoded.user}@s.whatsapp.net`);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
addHint(key.participant, key.participantAlt);
|
|
62
|
+
addHint(key.remoteJid, key.remoteJidAlt);
|
|
63
|
+
addHint(key.participantAlt, key.participant);
|
|
64
|
+
addHint(key.remoteJidAlt, key.remoteJid);
|
|
65
|
+
return hints;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const mergeGroupDataHints = (hints, groupData, debug) => {
|
|
69
|
+
if (!groupData?.participants?.length) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
debug?.('groupData-size', { participants: groupData.participants.length });
|
|
73
|
+
for (const participant of groupData.participants) {
|
|
74
|
+
const lidCandidate = participant?.lid;
|
|
75
|
+
const idCandidate = participant?.id;
|
|
76
|
+
const pnCandidate = participant?.phoneNumber;
|
|
77
|
+
const lid =
|
|
78
|
+
((isLidUser(lidCandidate) || isHostedLidUser(lidCandidate)
|
|
79
|
+
? lidCandidate
|
|
80
|
+
: undefined)) ||
|
|
81
|
+
((isLidUser(idCandidate) || isHostedLidUser(idCandidate) ? idCandidate : undefined));
|
|
82
|
+
const pn =
|
|
83
|
+
((isPnUser(pnCandidate) || isHostedPnUser(pnCandidate)
|
|
84
|
+
? pnCandidate
|
|
85
|
+
: undefined)) ||
|
|
86
|
+
((isPnUser(idCandidate) || isHostedPnUser(idCandidate) ? idCandidate : undefined));
|
|
87
|
+
if (!lid || !pn) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (
|
|
91
|
+
(isLidUser(lid) || isHostedLidUser(lid)) &&
|
|
92
|
+
(isPnUser(pn) || isHostedPnUser(pn))
|
|
93
|
+
) {
|
|
94
|
+
const displayPn = toDisplayPnJid(pn);
|
|
95
|
+
hints.set(lid, displayPn);
|
|
96
|
+
const lidDecoded = jidDecode(lid);
|
|
97
|
+
if (lidDecoded?.user) {
|
|
98
|
+
hints.set(`${lidDecoded.user}@lid`, displayPn);
|
|
99
|
+
}
|
|
100
|
+
debug?.('group-hint', { lid, pn: displayPn });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const normalizeToPnJid = async (jid, hints, signalRepository, debug) => {
|
|
106
|
+
if (!jid || typeof jid !== 'string') {
|
|
107
|
+
return jid;
|
|
108
|
+
}
|
|
109
|
+
if (!isLidUser(jid) && !isHostedLidUser(jid)) {
|
|
110
|
+
return jid;
|
|
111
|
+
}
|
|
112
|
+
debug?.('input', { lid: jid });
|
|
113
|
+
const hinted = hints.get(jid);
|
|
114
|
+
if (hinted) {
|
|
115
|
+
const normalized = toDisplayPnJid(hinted);
|
|
116
|
+
debug?.('hint-exact', { lid: jid, hinted, normalized });
|
|
117
|
+
return normalized;
|
|
118
|
+
}
|
|
119
|
+
const decoded = jidDecode(jid);
|
|
120
|
+
if (decoded?.user) {
|
|
121
|
+
const userHint = hints.get(`${decoded.user}@lid`);
|
|
122
|
+
if (userHint) {
|
|
123
|
+
const normalized = toDisplayPnJid(userHint);
|
|
124
|
+
debug?.('hint-user', { lid: jid, userHint, normalized });
|
|
125
|
+
return normalized;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const mapped = await signalRepository?.lidMapping?.getPNForLID?.(jid);
|
|
129
|
+
if (mapped) {
|
|
130
|
+
const normalized = toDisplayPnJid(mapped);
|
|
131
|
+
debug?.('mapping', { lid: jid, mapped, normalized });
|
|
132
|
+
return normalized;
|
|
133
|
+
}
|
|
134
|
+
const fallback = fallbackPnFromLidJid(jid);
|
|
135
|
+
debug?.('fallback', { lid: jid, fallback });
|
|
136
|
+
return fallback;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const normalizeMentionedJidsToPn = async (node, hints, signalRepository, debug) => {
|
|
140
|
+
if (!node || typeof node !== 'object') {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (Array.isArray(node)) {
|
|
144
|
+
for (const item of node) {
|
|
145
|
+
await normalizeMentionedJidsToPn(item, hints, signalRepository, debug);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (Array.isArray(node.mentionedJid)) {
|
|
150
|
+
const before = [...node.mentionedJid];
|
|
151
|
+
node.mentionedJid = await Promise.all(
|
|
152
|
+
node.mentionedJid.map(jid => normalizeToPnJid(jid, hints, signalRepository, debug))
|
|
153
|
+
);
|
|
154
|
+
debug?.('mentions', { before, after: node.mentionedJid });
|
|
155
|
+
}
|
|
156
|
+
if (typeof node.participant === 'string') {
|
|
157
|
+
const before = node.participant;
|
|
158
|
+
node.participant = await normalizeToPnJid(node.participant, hints, signalRepository, debug);
|
|
159
|
+
if (before !== node.participant) {
|
|
160
|
+
debug?.('participant-field', { before, after: node.participant });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (typeof node.remoteJid === 'string') {
|
|
164
|
+
const before = node.remoteJid;
|
|
165
|
+
node.remoteJid = await normalizeToPnJid(node.remoteJid, hints, signalRepository, debug);
|
|
166
|
+
if (before !== node.remoteJid) {
|
|
167
|
+
debug?.('remoteJid-field', { before, after: node.remoteJid });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const value of Object.values(node)) {
|
|
171
|
+
if (value && typeof value === 'object') {
|
|
172
|
+
await normalizeMentionedJidsToPn(value, hints, signalRepository, debug);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const normalizeMentionedJidsForSend = async (mentions, groupData, signalRepository, logger) => {
|
|
178
|
+
if (!Array.isArray(mentions)) {
|
|
179
|
+
return mentions;
|
|
180
|
+
}
|
|
181
|
+
const debug = createLidPnDebug(logger);
|
|
182
|
+
const hints = new Map();
|
|
183
|
+
mergeGroupDataHints(hints, groupData, debug);
|
|
184
|
+
const normalized = await Promise.all(mentions.map(jid => normalizeToPnJid(jid, hints, signalRepository, debug)));
|
|
185
|
+
debug('send-mentions-result', { before: mentions, after: normalized });
|
|
186
|
+
return normalized;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export const normalizeMessageForDisplayJids = async (messageInfo, signalRepository, logger, groupData) => {
|
|
190
|
+
if (!messageInfo?.key) {
|
|
191
|
+
return messageInfo;
|
|
192
|
+
}
|
|
193
|
+
const debug = createLidPnDebug(logger);
|
|
194
|
+
const hints = buildLidPnHints(messageInfo.key);
|
|
195
|
+
mergeGroupDataHints(hints, groupData, debug);
|
|
196
|
+
const beforeKey = { ...messageInfo.key };
|
|
197
|
+
messageInfo.key.participant = await normalizeToPnJid(messageInfo.key.participant, hints, signalRepository, debug);
|
|
198
|
+
messageInfo.key.participantAlt = await normalizeToPnJid(
|
|
199
|
+
messageInfo.key.participantAlt,
|
|
200
|
+
hints,
|
|
201
|
+
signalRepository,
|
|
202
|
+
debug
|
|
203
|
+
);
|
|
204
|
+
messageInfo.key.remoteJid = await normalizeToPnJid(messageInfo.key.remoteJid, hints, signalRepository, debug);
|
|
205
|
+
messageInfo.key.remoteJidAlt = await normalizeToPnJid(messageInfo.key.remoteJidAlt, hints, signalRepository, debug);
|
|
206
|
+
await normalizeMentionedJidsToPn(messageInfo.message, hints, signalRepository, debug);
|
|
207
|
+
debug('display-key-result', { before: beforeKey, after: messageInfo.key });
|
|
208
|
+
return messageInfo;
|
|
209
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { prepareWAMessageMedia } from './messages.js';
|
|
2
|
+
import { extractImageThumb, getHttpStream } from './messages-media.js';
|
|
3
|
+
const THUMBNAIL_WIDTH_PX = 192;
|
|
4
|
+
/** Fetches an image and generates a thumbnail for it */
|
|
5
|
+
const getCompressedJpegThumbnail = async (url, { thumbnailWidth, fetchOpts }) => {
|
|
6
|
+
const stream = await getHttpStream(url, fetchOpts);
|
|
7
|
+
const result = await extractImageThumb(stream, thumbnailWidth);
|
|
8
|
+
return result;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Given a piece of text, checks for any URL present, generates link preview for the same and returns it
|
|
12
|
+
* Return undefined if the fetch failed or no URL was found
|
|
13
|
+
* @param text first matched URL in text
|
|
14
|
+
* @returns the URL info required to generate link preview
|
|
15
|
+
*/
|
|
16
|
+
export const getUrlInfo = async (text, opts = {
|
|
17
|
+
thumbnailWidth: THUMBNAIL_WIDTH_PX,
|
|
18
|
+
fetchOpts: { timeout: 3000 }
|
|
19
|
+
}) => {
|
|
20
|
+
try {
|
|
21
|
+
// retries
|
|
22
|
+
const retries = 0;
|
|
23
|
+
const maxRetry = 5;
|
|
24
|
+
const { getLinkPreview } = await import('link-preview-js');
|
|
25
|
+
let previewLink = text;
|
|
26
|
+
if (!text.startsWith('https://') && !text.startsWith('http://')) {
|
|
27
|
+
previewLink = 'https://' + previewLink;
|
|
28
|
+
}
|
|
29
|
+
const info = await getLinkPreview(previewLink, {
|
|
30
|
+
...opts.fetchOpts,
|
|
31
|
+
followRedirects: 'follow',
|
|
32
|
+
handleRedirects: (baseURL, forwardedURL) => {
|
|
33
|
+
const urlObj = new URL(baseURL);
|
|
34
|
+
const forwardedURLObj = new URL(forwardedURL);
|
|
35
|
+
if (retries >= maxRetry) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (forwardedURLObj.hostname === urlObj.hostname ||
|
|
39
|
+
forwardedURLObj.hostname === 'www.' + urlObj.hostname ||
|
|
40
|
+
'www.' + forwardedURLObj.hostname === urlObj.hostname) {
|
|
41
|
+
retries + 1;
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
headers: opts.fetchOpts?.headers
|
|
49
|
+
});
|
|
50
|
+
if (info && 'title' in info && info.title) {
|
|
51
|
+
const [image] = info.images;
|
|
52
|
+
const urlInfo = {
|
|
53
|
+
'canonical-url': info.url,
|
|
54
|
+
'matched-text': text,
|
|
55
|
+
title: info.title,
|
|
56
|
+
description: info.description,
|
|
57
|
+
originalThumbnailUrl: image
|
|
58
|
+
};
|
|
59
|
+
if (opts.uploadImage) {
|
|
60
|
+
const { imageMessage } = await prepareWAMessageMedia({ image: { url: image } }, {
|
|
61
|
+
upload: opts.uploadImage,
|
|
62
|
+
mediaTypeOverride: 'thumbnail-link',
|
|
63
|
+
options: opts.fetchOpts
|
|
64
|
+
});
|
|
65
|
+
urlInfo.jpegThumbnail = imageMessage?.jpegThumbnail ? Buffer.from(imageMessage.jpegThumbnail) : undefined;
|
|
66
|
+
urlInfo.highQualityThumbnail = imageMessage || undefined;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
try {
|
|
70
|
+
urlInfo.jpegThumbnail = image ? (await getCompressedJpegThumbnail(image, opts)).buffer : undefined;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
opts.logger?.debug({ err: error.stack, url: previewLink }, 'error in generating thumbnail');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return urlInfo;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (!error.message.includes('receive a valid')) {
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { LTHashAntiTampering } from 'whatsapp-rust-bridge';
|
|
2
|
+
/**
|
|
3
|
+
* LT Hash is a summation based hash algorithm that maintains the integrity of a piece of data
|
|
4
|
+
* over a series of mutations. You can add/remove mutations and it'll return a hash equal to
|
|
5
|
+
* if the same series of mutations was made sequentially.
|
|
6
|
+
*/
|
|
7
|
+
export const LT_HASH_ANTI_TAMPERING = new LTHashAntiTampering();
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Mutex as AsyncMutex } from 'async-mutex';
|
|
2
|
+
export const makeMutex = () => {
|
|
3
|
+
const mutex = new AsyncMutex();
|
|
4
|
+
return {
|
|
5
|
+
mutex(code) {
|
|
6
|
+
return mutex.runExclusive(code);
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export const makeKeyedMutex = () => {
|
|
11
|
+
const map = new Map();
|
|
12
|
+
return {
|
|
13
|
+
async mutex(key, task) {
|
|
14
|
+
let entry = map.get(key);
|
|
15
|
+
if (!entry) {
|
|
16
|
+
entry = { mutex: new AsyncMutex(), refCount: 0 };
|
|
17
|
+
map.set(key, entry);
|
|
18
|
+
}
|
|
19
|
+
entry.refCount++;
|
|
20
|
+
try {
|
|
21
|
+
return await entry.mutex.runExclusive(task);
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
entry.refCount--;
|
|
25
|
+
// only delete it if this is still the current entry
|
|
26
|
+
if (entry.refCount === 0 && map.get(key) === entry) {
|
|
27
|
+
map.delete(key);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
};
|