@lazyneoaz/testfca 1.0.3 → 1.0.5
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/package.json +4 -5
- package/prebuilt/linux-x64-gnu/messagix.so +0 -0
- package/scripts/postinstall.js +98 -0
- package/src/apis/listenMqtt.js +106 -102
- package/src/e2ee/bridge.js +349 -220
- package/src/e2ee/index.js +5 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyneoaz/testfca",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "Advanced Facebook Chat API client for building Messenger bots — supports real-time messaging, thread management, MQTT, session stability, anti-automation protection, and real E2EE via Signal Protocol.",
|
|
6
6
|
"main": "index.js",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"index.js",
|
|
13
13
|
"src/",
|
|
14
|
-
"scripts/",
|
|
15
14
|
"prebuilt/",
|
|
15
|
+
"scripts/",
|
|
16
16
|
"LICENSE",
|
|
17
17
|
"README.md",
|
|
18
18
|
"CHANGELOG.md",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"form-data": "^4.0.4",
|
|
62
62
|
"https-proxy-agent": "^7.0.6",
|
|
63
63
|
"jsonpath-plus": "^10.3.0",
|
|
64
|
-
"koffi": "^3.0.2",
|
|
65
64
|
"lodash": "^4.17.21",
|
|
65
|
+
"meta-messenger.js": "^1.1.3",
|
|
66
66
|
"mqtt": "^4.3.8",
|
|
67
67
|
"node-cron": "^3.0.3",
|
|
68
68
|
"npmlog": "^7.0.1",
|
|
@@ -87,12 +87,11 @@
|
|
|
87
87
|
"node": ">=20.18.1"
|
|
88
88
|
},
|
|
89
89
|
"scripts": {
|
|
90
|
+
"postinstall": "node scripts/postinstall.js",
|
|
90
91
|
"prepack": "echo 'Preparing package for npm...'",
|
|
91
92
|
"test": "echo 'No tests configured yet'",
|
|
92
93
|
"lint": "eslint src/",
|
|
93
94
|
"format": "prettier --write src/**/*.js",
|
|
94
|
-
"postinstall": "node scripts/postinstall.mjs",
|
|
95
|
-
"build:go": "node scripts/build-go.mjs",
|
|
96
95
|
"validate": "npm pack --dry-run"
|
|
97
96
|
},
|
|
98
97
|
"overrides": {
|
|
Binary file
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Postinstall: replace meta-messenger.js's binary with our TLS-fixed build.
|
|
5
|
+
*
|
|
6
|
+
* WHY this is needed:
|
|
7
|
+
* The meta-messenger.js binary lacks InsecureTLS=true in its messagix HTTP
|
|
8
|
+
* client. In sandboxed/containerised environments (Replit, Docker, CI) the
|
|
9
|
+
* Go runtime cannot locate system CA certificates, so every prekey-bundle
|
|
10
|
+
* HTTPS request to Facebook fails silently. Without prekeys, Signal sessions
|
|
11
|
+
* are never established and sendE2EEMessage always times out.
|
|
12
|
+
*
|
|
13
|
+
* Our prebuilt/ ships the same binary compiled with InsecureTLS=true.
|
|
14
|
+
*
|
|
15
|
+
* INSTALL ORDER (guaranteed by npm):
|
|
16
|
+
* 1. meta-messenger.js postinstall runs first (copies/downloads their binary)
|
|
17
|
+
* 2. Our postinstall runs second and overwrites it with ours.
|
|
18
|
+
*
|
|
19
|
+
* PATH RESOLUTION:
|
|
20
|
+
* Uses require.resolve() so the path is correct whether meta-messenger.js is
|
|
21
|
+
* at the project root node_modules/ or nested — works in all npm versions.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
|
|
27
|
+
function platformKey() {
|
|
28
|
+
const p = process.platform;
|
|
29
|
+
const a = process.arch;
|
|
30
|
+
if (p === 'linux' && a === 'x64') return 'linux-x64-gnu';
|
|
31
|
+
if (p === 'linux' && a === 'arm64') return 'linux-arm64-gnu';
|
|
32
|
+
if (p === 'darwin' && a === 'x64') return 'darwin-x64';
|
|
33
|
+
if (p === 'darwin' && a === 'arm64') return 'darwin-arm64';
|
|
34
|
+
if (p === 'win32' && a === 'x64') return 'win32-x64-msvc';
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ext() {
|
|
39
|
+
if (process.platform === 'win32') return '.dll';
|
|
40
|
+
if (process.platform === 'darwin') return '.dylib';
|
|
41
|
+
return '.so';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function findMetaMessengerBuildDir() {
|
|
45
|
+
try {
|
|
46
|
+
// resolve() finds the package regardless of hoisting depth
|
|
47
|
+
const pkgJson = require.resolve('meta-messenger.js/package.json');
|
|
48
|
+
return path.join(path.dirname(pkgJson), 'build');
|
|
49
|
+
} catch (_) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function main() {
|
|
55
|
+
if (process.env.NKXFCA_SKIP_POSTINSTALL === 'true') {
|
|
56
|
+
console.log('[nkxfca] postinstall skipped (NKXFCA_SKIP_POSTINSTALL=true)');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const key = platformKey();
|
|
61
|
+
if (!key) {
|
|
62
|
+
console.log('[nkxfca] postinstall: unsupported platform (' + process.platform + '/' + process.arch + '), skipping binary patch.');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const binaryExt = ext();
|
|
67
|
+
const ourBin = path.join(__dirname, '..', 'prebuilt', key, 'messagix' + binaryExt);
|
|
68
|
+
|
|
69
|
+
if (!fs.existsSync(ourBin)) {
|
|
70
|
+
console.log('[nkxfca] postinstall: no prebuilt for ' + key + ', skipping binary patch.');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const mmBuildDir = findMetaMessengerBuildDir();
|
|
75
|
+
if (!mmBuildDir) {
|
|
76
|
+
console.log('[nkxfca] postinstall: meta-messenger.js not found via require.resolve, skipping patch.');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const mmBin = path.join(mmBuildDir, 'messagix' + binaryExt);
|
|
81
|
+
|
|
82
|
+
// Ensure the build directory exists (meta-messenger.js postinstall creates it,
|
|
83
|
+
// but guard against edge cases where it hasn't run yet)
|
|
84
|
+
try {
|
|
85
|
+
fs.mkdirSync(mmBuildDir, { recursive: true });
|
|
86
|
+
} catch (_) {}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
fs.copyFileSync(ourBin, mmBin);
|
|
90
|
+
console.log('[nkxfca] postinstall: patched meta-messenger.js binary with TLS-fixed build (' + key + ')');
|
|
91
|
+
} catch (err) {
|
|
92
|
+
// Non-fatal: warn but don't fail the install
|
|
93
|
+
console.warn('[nkxfca] postinstall: could not patch binary: ' + err.message);
|
|
94
|
+
console.warn('[nkxfca] E2EE messaging may not work in sandboxed environments.');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
main();
|
package/src/apis/listenMqtt.js
CHANGED
|
@@ -592,130 +592,134 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
|
|
|
592
592
|
}
|
|
593
593
|
while (ctx._e2eePollingActive) {
|
|
594
594
|
try {
|
|
595
|
-
// Yield to event loop before blocking poll
|
|
595
|
+
// Yield to event loop before blocking poll
|
|
596
596
|
await new Promise(resolve => setImmediate(resolve));
|
|
597
597
|
if (!ctx._e2eePollingActive) break;
|
|
598
598
|
|
|
599
|
-
//
|
|
600
|
-
const
|
|
599
|
+
// pollEvents returns { events: [...] } — iterate the array
|
|
600
|
+
const result = await api.e2ee.pollEvents(1000);
|
|
601
601
|
if (!ctx._e2eePollingActive) break;
|
|
602
602
|
|
|
603
|
-
// No
|
|
604
|
-
if (!
|
|
603
|
+
// No events returned (timeout or empty)
|
|
604
|
+
if (!result || !Array.isArray(result.events) || result.events.length === 0) continue;
|
|
605
605
|
|
|
606
|
-
const
|
|
606
|
+
for (const ev of result.events) {
|
|
607
|
+
if (!ctx._e2eePollingActive) break;
|
|
607
608
|
|
|
608
|
-
|
|
609
|
-
if (evType === 'timeout') continue;
|
|
609
|
+
const evType = (ev.type || '').toLowerCase();
|
|
610
610
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
utils.warn("E2EE", "Bridge closed. Stopping DM poll loop.");
|
|
614
|
-
ctx._e2eePollingActive = false;
|
|
615
|
-
break;
|
|
616
|
-
}
|
|
611
|
+
// ── timeout: no event arrived during the poll window ──
|
|
612
|
+
if (evType === 'timeout') continue;
|
|
617
613
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
utils.warn("E2EE", "Bridge error event:", msg);
|
|
622
|
-
if (ev.data && ev.data.code === 1) {
|
|
614
|
+
// ── closed: bridge shut down ──
|
|
615
|
+
if (evType === 'closed') {
|
|
616
|
+
utils.warn("E2EE", "Bridge closed. Stopping DM poll loop.");
|
|
623
617
|
ctx._e2eePollingActive = false;
|
|
624
618
|
break;
|
|
625
619
|
}
|
|
626
|
-
continue;
|
|
627
|
-
}
|
|
628
620
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
621
|
+
// ── permanent error (e.g. session invalid) ──
|
|
622
|
+
if (evType === 'error') {
|
|
623
|
+
const msg = ev.data && ev.data.message ? ev.data.message : JSON.stringify(ev.data);
|
|
624
|
+
utils.warn("E2EE", "Bridge error event:", msg);
|
|
625
|
+
if (ev.data && ev.data.code === 1) {
|
|
626
|
+
ctx._e2eePollingActive = false;
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
634
631
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
if (!d) continue;
|
|
639
|
-
// threadId may be 0 for pure-JID threads — fall back to chatJid
|
|
640
|
-
const chatJid = d.chatJid || '';
|
|
641
|
-
const senderJid = d.senderJid || '';
|
|
642
|
-
const threadID = String(d.threadId || chatJid.replace(/@.*/, '') || '');
|
|
643
|
-
const senderID = String(d.senderId || senderJid.replace(/@.*/, '') || '');
|
|
644
|
-
if (!threadID || !senderID) {
|
|
645
|
-
utils.log("E2EE", "e2eeMessage missing IDs — raw:", JSON.stringify(d).slice(0, 300));
|
|
632
|
+
// ── E2EE connection confirmed by the bridge ──
|
|
633
|
+
if (evType === 'e2eeconnected') {
|
|
634
|
+
utils.log("E2EE", "E2EE fully connected (bridge event).");
|
|
646
635
|
continue;
|
|
647
636
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
threadID
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
637
|
+
|
|
638
|
+
// ── Incoming E2EE DM ─────────────────────────────────
|
|
639
|
+
if (evType === 'e2eemessage') {
|
|
640
|
+
const d = ev.data;
|
|
641
|
+
if (!d) continue;
|
|
642
|
+
// threadId may be 0 for pure-JID threads — fall back to chatJid
|
|
643
|
+
const chatJid = d.chatJid || '';
|
|
644
|
+
const senderJid = d.senderJid || '';
|
|
645
|
+
const threadID = String(d.threadId || chatJid.replace(/@.*/, '') || '');
|
|
646
|
+
const senderID = String(d.senderId || senderJid.replace(/@.*/, '') || '');
|
|
647
|
+
if (!threadID || !senderID) {
|
|
648
|
+
utils.log("E2EE", "e2eeMessage missing IDs — raw:", JSON.stringify(d).slice(0, 300));
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (senderID === ctx.userID.toString() && !ctx.globalOptions.selfListen) continue;
|
|
652
|
+
|
|
653
|
+
utils.log("E2EE", "Incoming E2EE DM from", senderID, "→ thread", threadID, "body:", JSON.stringify(d.text || '').slice(0, 80));
|
|
654
|
+
|
|
655
|
+
const fmtMsg = {
|
|
656
|
+
type: 'message',
|
|
657
|
+
senderID: utils.formatID(senderID),
|
|
658
|
+
body: d.text || '',
|
|
659
|
+
threadID: utils.formatID(threadID),
|
|
660
|
+
messageID: d.id || '',
|
|
661
|
+
isGroup: false,
|
|
662
|
+
attachments: [],
|
|
663
|
+
mentions: {},
|
|
664
|
+
timestamp: Number(d.timestampMs || Date.now()),
|
|
665
|
+
isUnread: true,
|
|
666
|
+
};
|
|
667
|
+
if (ctx.globalOptions.autoMarkDelivery) {
|
|
668
|
+
try { api.markAsDelivered(fmtMsg.threadID, fmtMsg.messageID); } catch (_) {}
|
|
669
|
+
}
|
|
670
|
+
if (ctx.globalOptions.autoMarkRead) {
|
|
671
|
+
try { api.markAsRead(fmtMsg.threadID); } catch (_) {}
|
|
672
|
+
}
|
|
673
|
+
globalCallback(null, fmtMsg);
|
|
674
|
+
continue;
|
|
666
675
|
}
|
|
667
|
-
|
|
668
|
-
|
|
676
|
+
|
|
677
|
+
// ── Incoming regular (non-E2EE) message via bridge ───
|
|
678
|
+
if (evType === 'message') {
|
|
679
|
+
const d = ev.data;
|
|
680
|
+
if (!d) continue;
|
|
681
|
+
const threadID = String(d.threadId || '');
|
|
682
|
+
const senderID = String(d.senderId || '');
|
|
683
|
+
if (!threadID || !senderID) continue;
|
|
684
|
+
if (senderID === ctx.userID.toString() && !ctx.globalOptions.selfListen) continue;
|
|
685
|
+
globalCallback(null, {
|
|
686
|
+
type: 'message',
|
|
687
|
+
senderID: utils.formatID(senderID),
|
|
688
|
+
body: d.text || '',
|
|
689
|
+
threadID: utils.formatID(threadID),
|
|
690
|
+
messageID: d.id || '',
|
|
691
|
+
isGroup: !!(d.isGroup),
|
|
692
|
+
attachments: [],
|
|
693
|
+
mentions: {},
|
|
694
|
+
timestamp: Number(d.timestampMs || Date.now()),
|
|
695
|
+
isUnread: true,
|
|
696
|
+
});
|
|
697
|
+
continue;
|
|
669
698
|
}
|
|
670
|
-
globalCallback(null, fmtMsg);
|
|
671
|
-
continue;
|
|
672
|
-
}
|
|
673
699
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
});
|
|
694
|
-
continue;
|
|
695
|
-
}
|
|
700
|
+
// ── E2EE reaction ────────────────────────────────────
|
|
701
|
+
if (evType === 'e2eereaction') {
|
|
702
|
+
if (!ctx.globalOptions.listenEvents) continue;
|
|
703
|
+
const d = ev.data;
|
|
704
|
+
if (!d) continue;
|
|
705
|
+
const chatJid = d.chatJid || '';
|
|
706
|
+
const senderJid = d.senderJid || '';
|
|
707
|
+
globalCallback(null, {
|
|
708
|
+
type: 'message_reaction',
|
|
709
|
+
threadID: utils.formatID(chatJid.replace(/@.*/, '')),
|
|
710
|
+
messageID: d.messageId || '',
|
|
711
|
+
reaction: d.reaction || '',
|
|
712
|
+
senderID: utils.formatID(senderJid.replace(/@.*/, '')),
|
|
713
|
+
offlineThreadingId: null,
|
|
714
|
+
timestamp: Date.now(),
|
|
715
|
+
isGroup: false,
|
|
716
|
+
});
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
696
719
|
|
|
697
|
-
|
|
698
|
-
if (evType === 'e2eereaction') {
|
|
699
|
-
if (!ctx.globalOptions.listenEvents) continue;
|
|
700
|
-
const d = ev.data;
|
|
701
|
-
if (!d) continue;
|
|
702
|
-
const chatJid = d.chatJid || '';
|
|
703
|
-
const senderJid = d.senderJid || '';
|
|
704
|
-
globalCallback(null, {
|
|
705
|
-
type: 'message_reaction',
|
|
706
|
-
threadID: utils.formatID(chatJid.replace(/@.*/, '')),
|
|
707
|
-
messageID: d.messageId || '',
|
|
708
|
-
reaction: d.reaction || '',
|
|
709
|
-
senderID: utils.formatID(senderJid.replace(/@.*/, '')),
|
|
710
|
-
offlineThreadingId: null,
|
|
711
|
-
timestamp: Date.now(),
|
|
712
|
-
isGroup: false,
|
|
713
|
-
});
|
|
714
|
-
continue;
|
|
720
|
+
// ready / reconnected / disconnected / deviceDataChanged / raw → ignore silently
|
|
715
721
|
}
|
|
716
722
|
|
|
717
|
-
// ready / reconnected / disconnected / deviceDataChanged / raw → ignore silently
|
|
718
|
-
|
|
719
723
|
} catch (pollErr) {
|
|
720
724
|
if (!ctx._e2eePollingActive) break;
|
|
721
725
|
utils.warn("E2EE", "pollEvents error:", pollErr && pollErr.message ? pollErr.message : pollErr);
|
package/src/e2ee/bridge.js
CHANGED
|
@@ -1,275 +1,404 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight E2EE bridge — delegates to the `meta-messenger.js` npm package.
|
|
5
|
+
*
|
|
6
|
+
* Our postinstall replaces meta-messenger.js's binary with our TLS-fixed build
|
|
7
|
+
* so that prekey HTTPS requests succeed in sandboxed environments (Replit, Docker,
|
|
8
|
+
* Railway, etc.). We also patch at runtime (ensureTlsFixedBinary) so the fix
|
|
9
|
+
* applies even when postinstall was skipped (npm ci --ignore-scripts, Railway
|
|
10
|
+
* production installs, etc.).
|
|
11
|
+
*
|
|
12
|
+
* Key design decisions:
|
|
13
|
+
* - enableE2EE: false → we call connectE2EE() explicitly; no auto-start race
|
|
14
|
+
* - e2eeMemoryOnly: false → Signal sessions are persisted to devicePath on disk
|
|
15
|
+
* - devicePath defaults to ".nkxfca_e2ee_device.json" in cwd when not provided
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
3
19
|
const path = require('path');
|
|
4
|
-
const fs = require('fs');
|
|
5
20
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
21
|
+
// ── Runtime binary patcher ────────────────────────────────────────────────────
|
|
22
|
+
// Ensures the TLS-fixed binary is in place before meta-messenger.js is loaded.
|
|
23
|
+
// Runs once; safe to call multiple times.
|
|
24
|
+
|
|
25
|
+
let _patchDone = false;
|
|
26
|
+
|
|
27
|
+
function _platformKey() {
|
|
28
|
+
const p = process.platform;
|
|
29
|
+
const a = process.arch;
|
|
30
|
+
if (p === 'linux' && a === 'x64') return 'linux-x64-gnu';
|
|
31
|
+
if (p === 'linux' && a === 'arm64') return 'linux-arm64-gnu';
|
|
32
|
+
if (p === 'darwin' && a === 'x64') return 'darwin-x64';
|
|
33
|
+
if (p === 'darwin' && a === 'arm64') return 'darwin-arm64';
|
|
34
|
+
if (p === 'win32' && a === 'x64') return 'win32-x64-msvc';
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
10
37
|
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
if (process.platform === '
|
|
14
|
-
|
|
15
|
-
return path.join(base, 'messagix.so');
|
|
38
|
+
function _binaryExt() {
|
|
39
|
+
if (process.platform === 'win32') return '.dll';
|
|
40
|
+
if (process.platform === 'darwin') return '.dylib';
|
|
41
|
+
return '.so';
|
|
16
42
|
}
|
|
17
43
|
|
|
18
|
-
|
|
44
|
+
function ensureTlsFixedBinary() {
|
|
45
|
+
if (_patchDone) return;
|
|
46
|
+
_patchDone = true;
|
|
19
47
|
|
|
20
|
-
|
|
21
|
-
if (initialized) return;
|
|
22
|
-
if (!fs.existsSync(LIB_PATH)) {
|
|
23
|
-
throw new Error(
|
|
24
|
-
`E2EE bridge library not found at ${LIB_PATH}.\n` +
|
|
25
|
-
`Build it with: npm run build:go\n` +
|
|
26
|
-
`Or set MESSAGIX_BUILD_FROM_SOURCE=true and reinstall.`
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
koffi = require('koffi');
|
|
30
|
-
lib = koffi.load(LIB_PATH);
|
|
31
|
-
const mk = (ret, name, args) => lib.func(name, ret, args);
|
|
32
|
-
fns = {
|
|
33
|
-
MxFreeCString: mk('void', 'MxFreeCString', ['char*']),
|
|
34
|
-
MxNewClient: mk('str', 'MxNewClient', ['str']),
|
|
35
|
-
MxConnect: mk('str', 'MxConnect', ['str']),
|
|
36
|
-
MxConnectE2EE: mk('str', 'MxConnectE2EE', ['str']),
|
|
37
|
-
MxDisconnect: mk('str', 'MxDisconnect', ['str']),
|
|
38
|
-
MxIsConnected: mk('str', 'MxIsConnected', ['str']),
|
|
39
|
-
MxSendMessage: mk('str', 'MxSendMessage', ['str']),
|
|
40
|
-
MxSendReaction: mk('str', 'MxSendReaction', ['str']),
|
|
41
|
-
MxEditMessage: mk('str', 'MxEditMessage', ['str']),
|
|
42
|
-
MxUnsendMessage: mk('str', 'MxUnsendMessage', ['str']),
|
|
43
|
-
MxSendTyping: mk('str', 'MxSendTyping', ['str']),
|
|
44
|
-
MxMarkRead: mk('str', 'MxMarkRead', ['str']),
|
|
45
|
-
MxPollEvents: mk('str', 'MxPollEvents', ['str']),
|
|
46
|
-
MxGetDeviceData: mk('str', 'MxGetDeviceData', ['str']),
|
|
47
|
-
MxSendE2EEMessage: mk('str', 'MxSendE2EEMessage', ['str']),
|
|
48
|
-
MxSendE2EEReaction: mk('str', 'MxSendE2EEReaction', ['str']),
|
|
49
|
-
MxSendE2EETyping: mk('str', 'MxSendE2EETyping', ['str']),
|
|
50
|
-
MxEditE2EEMessage: mk('str', 'MxEditE2EEMessage', ['str']),
|
|
51
|
-
MxUnsendE2EEMessage: mk('str', 'MxUnsendE2EEMessage', ['str']),
|
|
52
|
-
MxSendE2EEImage: mk('str', 'MxSendE2EEImage', ['str']),
|
|
53
|
-
MxSendE2EEVideo: mk('str', 'MxSendE2EEVideo', ['str']),
|
|
54
|
-
MxSendE2EEAudio: mk('str', 'MxSendE2EEAudio', ['str']),
|
|
55
|
-
MxSendE2EEDocument: mk('str', 'MxSendE2EEDocument', ['str']),
|
|
56
|
-
MxSendE2EESticker: mk('str', 'MxSendE2EESticker', ['str']),
|
|
57
|
-
MxDownloadE2EEMedia: mk('str', 'MxDownloadE2EEMedia', ['str']),
|
|
58
|
-
MxGetCookies: mk('str', 'MxGetCookies', ['str']),
|
|
59
|
-
MxUploadMedia: mk('str', 'MxUploadMedia', ['str']),
|
|
60
|
-
MxSendImage: mk('str', 'MxSendImage', ['str']),
|
|
61
|
-
MxSendVideo: mk('str', 'MxSendVideo', ['str']),
|
|
62
|
-
MxSendVoice: mk('str', 'MxSendVoice', ['str']),
|
|
63
|
-
MxSendFile: mk('str', 'MxSendFile', ['str']),
|
|
64
|
-
MxSendSticker: mk('str', 'MxSendSticker', ['str']),
|
|
65
|
-
MxGetUserInfo: mk('str', 'MxGetUserInfo', ['str']),
|
|
66
|
-
MxCreateThread: mk('str', 'MxCreateThread', ['str']),
|
|
67
|
-
MxSearchUsers: mk('str', 'MxSearchUsers', ['str']),
|
|
68
|
-
MxRenameThread: mk('str', 'MxRenameThread', ['str']),
|
|
69
|
-
MxMuteThread: mk('str', 'MxMuteThread', ['str']),
|
|
70
|
-
MxDeleteThread: mk('str', 'MxDeleteThread', ['str']),
|
|
71
|
-
MxSetGroupPhoto: mk('str', 'MxSetGroupPhoto', ['str']),
|
|
72
|
-
MxRegisterPushNotifications: mk('str', 'MxRegisterPushNotifications', ['str']),
|
|
73
|
-
};
|
|
74
|
-
initialized = true;
|
|
75
|
-
}
|
|
48
|
+
if (process.env.NKXFCA_SKIP_POSTINSTALL === 'true') return;
|
|
76
49
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
typeof v === 'bigint' ? Number(v) : v
|
|
80
|
-
);
|
|
81
|
-
}
|
|
50
|
+
const key = _platformKey();
|
|
51
|
+
if (!key) return;
|
|
82
52
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
53
|
+
const ext = _binaryExt();
|
|
54
|
+
// bridge.js lives at src/e2ee/bridge.js → prebuilt/ is two levels up
|
|
55
|
+
const ourBin = path.join(__dirname, '..', '..', 'prebuilt', key, 'messagix' + ext);
|
|
56
|
+
if (!fs.existsSync(ourBin)) return;
|
|
86
57
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const data = deserialize(out);
|
|
92
|
-
if (!data.ok) throw new Error(data.error || 'Unknown E2EE bridge error');
|
|
93
|
-
return data.data;
|
|
94
|
-
}
|
|
58
|
+
try {
|
|
59
|
+
const pkgJson = require.resolve('meta-messenger.js/package.json');
|
|
60
|
+
const buildDir = path.join(path.dirname(pkgJson), 'build');
|
|
61
|
+
const mmBin = path.join(buildDir, 'messagix' + ext);
|
|
95
62
|
|
|
96
|
-
|
|
97
|
-
return new Promise((resolve, reject) => {
|
|
98
|
-
setImmediate(() => {
|
|
99
|
-
try { resolve(call(fn, payload)); }
|
|
100
|
-
catch (err) { reject(err); }
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
}
|
|
63
|
+
fs.mkdirSync(buildDir, { recursive: true });
|
|
104
64
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
65
|
+
// Only copy if sizes differ (avoids unnecessary writes on every boot)
|
|
66
|
+
let needsCopy = true;
|
|
67
|
+
if (fs.existsSync(mmBin)) {
|
|
68
|
+
needsCopy = fs.statSync(ourBin).size !== fs.statSync(mmBin).size;
|
|
69
|
+
}
|
|
109
70
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
71
|
+
if (needsCopy) {
|
|
72
|
+
fs.copyFileSync(ourBin, mmBin);
|
|
73
|
+
fs.chmodSync(mmBin, 0o755);
|
|
74
|
+
console.log('[nkxfca] runtime patch: applied TLS-fixed binary (' + key + ')');
|
|
75
|
+
}
|
|
76
|
+
} catch (err) {
|
|
77
|
+
// Non-fatal — warn so the user knows why E2EE may not work
|
|
78
|
+
console.warn('[nkxfca] runtime patch failed (' + err.message + '). E2EE TLS errors may occur in sandboxed environments.');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
113
81
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
82
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
let _Client = null;
|
|
85
|
+
let _loadError = null;
|
|
86
|
+
|
|
87
|
+
function loadClient() {
|
|
88
|
+
if (_Client) return _Client;
|
|
89
|
+
if (_loadError) throw _loadError;
|
|
90
|
+
|
|
91
|
+
// Patch binary BEFORE the first require('meta-messenger.js') so the
|
|
92
|
+
// TLS-fixed .so/.dylib/.dll is loaded by Node rather than the original.
|
|
93
|
+
ensureTlsFixedBinary();
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const mm = require('meta-messenger.js');
|
|
97
|
+
_Client = mm.Client || mm.default?.Client;
|
|
98
|
+
if (!_Client) throw new Error('meta-messenger.js: Client class not found in exports');
|
|
99
|
+
return _Client;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
_loadError = new Error(
|
|
102
|
+
'E2EE requires meta-messenger.js and its native binary.\n' +
|
|
103
|
+
'Install with: npm install meta-messenger.js\n' +
|
|
104
|
+
'Error: ' + err.message
|
|
105
|
+
);
|
|
106
|
+
throw _loadError;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
117
109
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
110
|
+
const EVENT_TYPES = [
|
|
111
|
+
'message', 'messageEdit', 'messageUnsend', 'reaction',
|
|
112
|
+
'typing', 'readReceipt', 'e2eeMessage', 'e2eeReaction',
|
|
113
|
+
'e2eeReceipt', 'e2eeConnected', 'ready', 'reconnected',
|
|
114
|
+
'disconnected', 'error', 'deviceDataChanged', 'raw',
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
// Registry: handle (string) -> { client, eventBuffer, resolveWaiter, lastDeviceData }
|
|
118
|
+
const registry = new Map();
|
|
119
|
+
let seq = 1;
|
|
120
|
+
|
|
121
|
+
function newClient(cfg) {
|
|
122
|
+
const Client = loadClient();
|
|
123
|
+
const cookies = cfg.cookies || {};
|
|
124
|
+
|
|
125
|
+
const opts = {
|
|
126
|
+
platform: cfg.platform || 'facebook',
|
|
127
|
+
logLevel: cfg.logLevel || 'warn',
|
|
128
|
+
enableE2EE: false, // we call connectE2EE() explicitly
|
|
129
|
+
e2eeMemoryOnly: false, // always persist sessions to disk
|
|
130
|
+
autoReconnect: cfg.autoReconnect !== false,
|
|
131
|
+
};
|
|
121
132
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
133
|
+
// Device storage: prefer deviceData (in-memory JSON) > devicePath > default file
|
|
134
|
+
if (cfg.deviceData) {
|
|
135
|
+
opts.deviceData = cfg.deviceData;
|
|
136
|
+
} else {
|
|
137
|
+
opts.devicePath = cfg.devicePath || '.nkxfca_e2ee_device.json';
|
|
138
|
+
}
|
|
125
139
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
140
|
+
const client = new Client(cookies, opts);
|
|
141
|
+
const handle = String(seq++);
|
|
142
|
+
const entry = { client, eventBuffer: [], resolveWaiter: null, lastDeviceData: null };
|
|
143
|
+
|
|
144
|
+
for (const type of EVENT_TYPES) {
|
|
145
|
+
client.on(type, (data) => {
|
|
146
|
+
if (type === 'deviceDataChanged' && data?.deviceData) {
|
|
147
|
+
entry.lastDeviceData = data.deviceData;
|
|
148
|
+
}
|
|
149
|
+
entry.eventBuffer.push({ type, data: data ?? null, timestamp: Date.now() });
|
|
150
|
+
if (entry.resolveWaiter) {
|
|
151
|
+
const resolve = entry.resolveWaiter;
|
|
152
|
+
entry.resolveWaiter = null;
|
|
153
|
+
resolve();
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
130
157
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
158
|
+
registry.set(handle, entry);
|
|
159
|
+
return { handle };
|
|
160
|
+
}
|
|
134
161
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
162
|
+
function getEntry(handle) {
|
|
163
|
+
const entry = registry.get(String(handle));
|
|
164
|
+
if (!entry) throw new Error(`E2EE bridge: unknown handle "${handle}"`);
|
|
165
|
+
return entry;
|
|
166
|
+
}
|
|
138
167
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
168
|
+
async function connect(handle) {
|
|
169
|
+
return getEntry(handle).client.connect();
|
|
170
|
+
}
|
|
142
171
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Initiate E2EE handshake. Returns after the background connect is kicked
|
|
174
|
+
* off — listen for the 'e2eeConnected' event (via pollEvents) to know when
|
|
175
|
+
* the channel is fully ready.
|
|
176
|
+
*/
|
|
177
|
+
async function connectE2EE(handle) {
|
|
178
|
+
return getEntry(handle).client.connectE2EE();
|
|
179
|
+
}
|
|
146
180
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
181
|
+
function disconnect(handle) {
|
|
182
|
+
const entry = registry.get(String(handle));
|
|
183
|
+
if (!entry) return;
|
|
184
|
+
try { entry.client.disconnect(); } catch (_) {}
|
|
185
|
+
registry.delete(String(handle));
|
|
186
|
+
}
|
|
150
187
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
188
|
+
function isConnected(handle) {
|
|
189
|
+
try {
|
|
190
|
+
const { client } = getEntry(handle);
|
|
191
|
+
return { connected: !!client.isConnected, e2eeConnected: !!client.isE2EEConnected };
|
|
192
|
+
} catch (_) {
|
|
193
|
+
return { connected: false, e2eeConnected: false };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
154
196
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
197
|
+
function getDeviceData(handle) {
|
|
198
|
+
return { deviceData: getEntry(handle).lastDeviceData };
|
|
199
|
+
}
|
|
158
200
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
201
|
+
async function pollEvents(handle, timeoutMs = 5000) {
|
|
202
|
+
const entry = getEntry(handle);
|
|
203
|
+
if (entry.eventBuffer.length > 0) {
|
|
204
|
+
return { events: entry.eventBuffer.splice(0) };
|
|
205
|
+
}
|
|
206
|
+
await new Promise((resolve) => {
|
|
207
|
+
const timer = setTimeout(() => {
|
|
208
|
+
entry.resolveWaiter = null;
|
|
209
|
+
resolve();
|
|
210
|
+
}, timeoutMs);
|
|
211
|
+
entry.resolveWaiter = () => {
|
|
212
|
+
clearTimeout(timer);
|
|
213
|
+
resolve();
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
return { events: entry.eventBuffer.splice(0) };
|
|
217
|
+
}
|
|
162
218
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
219
|
+
function toBuffer(data) {
|
|
220
|
+
if (Buffer.isBuffer(data)) return data;
|
|
221
|
+
if (typeof data === 'string') return Buffer.from(data, 'base64');
|
|
222
|
+
return Buffer.from(data);
|
|
223
|
+
}
|
|
166
224
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
225
|
+
// Convert JID string ("12345@msgr") or plain number/string to BigInt
|
|
226
|
+
function toBigInt(val) {
|
|
227
|
+
if (typeof val === 'bigint') return val;
|
|
228
|
+
return BigInt(String(val).split('@')[0]);
|
|
229
|
+
}
|
|
170
230
|
|
|
171
|
-
|
|
172
|
-
return callAsync('MxSendE2EETyping', { handle, chatJid, isTyping: !!isTyping });
|
|
173
|
-
},
|
|
231
|
+
// ── E2EE operations ────────────────────────────────────────────────────────────
|
|
174
232
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
233
|
+
async function sendE2EEMessage(handle, chatJid, text, replyToId, replyToSenderJid) {
|
|
234
|
+
const opts = replyToId ? { replyToId, replyToSenderJid } : undefined;
|
|
235
|
+
return getEntry(handle).client.sendE2EEMessage(chatJid, text, opts);
|
|
236
|
+
}
|
|
178
237
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
238
|
+
async function sendE2EEReaction(handle, chatJid, messageId, senderJid, emoji) {
|
|
239
|
+
return getEntry(handle).client.sendE2EEReaction(chatJid, messageId, senderJid, emoji);
|
|
240
|
+
}
|
|
182
241
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
242
|
+
async function sendE2EETyping(handle, chatJid, isTyping) {
|
|
243
|
+
return getEntry(handle).client.sendE2EETyping(chatJid, isTyping);
|
|
244
|
+
}
|
|
186
245
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
246
|
+
async function editE2EEMessage(handle, chatJid, messageId, newText) {
|
|
247
|
+
return getEntry(handle).client.editE2EEMessage(chatJid, messageId, newText);
|
|
248
|
+
}
|
|
190
249
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
250
|
+
async function unsendE2EEMessage(handle, chatJid, messageId) {
|
|
251
|
+
return getEntry(handle).client.unsendE2EEMessage(chatJid, messageId);
|
|
252
|
+
}
|
|
194
253
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
254
|
+
async function sendE2EEImage(handle, options) {
|
|
255
|
+
const { chatJid, data, mimeType, ...rest } = options;
|
|
256
|
+
return getEntry(handle).client.sendE2EEImage(chatJid, toBuffer(data), mimeType, rest);
|
|
257
|
+
}
|
|
198
258
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
259
|
+
async function sendE2EEVideo(handle, options) {
|
|
260
|
+
const { chatJid, data, mimeType, ...rest } = options;
|
|
261
|
+
return getEntry(handle).client.sendE2EEVideo(chatJid, toBuffer(data), mimeType, rest);
|
|
262
|
+
}
|
|
202
263
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
264
|
+
async function sendE2EEAudio(handle, options) {
|
|
265
|
+
const { chatJid, data, mimeType, ...rest } = options;
|
|
266
|
+
return getEntry(handle).client.sendE2EEAudio(chatJid, toBuffer(data), mimeType, rest);
|
|
267
|
+
}
|
|
206
268
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
269
|
+
async function sendE2EEDocument(handle, options) {
|
|
270
|
+
const { chatJid, data, filename, mimeType, ...rest } = options;
|
|
271
|
+
return getEntry(handle).client.sendE2EEDocument(chatJid, toBuffer(data), filename, mimeType, rest);
|
|
272
|
+
}
|
|
210
273
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
274
|
+
async function sendE2EESticker(handle, options) {
|
|
275
|
+
const { chatJid, data, mimeType, ...rest } = options;
|
|
276
|
+
return getEntry(handle).client.sendE2EESticker(chatJid, toBuffer(data), mimeType, rest);
|
|
277
|
+
}
|
|
214
278
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
279
|
+
async function downloadE2EEMedia(handle, options) {
|
|
280
|
+
return getEntry(handle).client.downloadE2EEMedia(options);
|
|
281
|
+
}
|
|
218
282
|
|
|
219
|
-
|
|
220
|
-
return callAsync('MxSendVideo', { handle, options });
|
|
221
|
-
},
|
|
283
|
+
// ── Regular (non-E2EE) Messenger methods ──────────────────────────────────────
|
|
222
284
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
285
|
+
async function uploadMedia(handle, options) {
|
|
286
|
+
const { chatJid, data, filename, mimeType, isVoice } = options;
|
|
287
|
+
return getEntry(handle).client.uploadMedia(
|
|
288
|
+
toBigInt(chatJid), toBuffer(data), filename || 'file', mimeType || 'application/octet-stream', !!isVoice
|
|
289
|
+
);
|
|
290
|
+
}
|
|
226
291
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
292
|
+
async function sendImage(handle, options) {
|
|
293
|
+
const { chatJid, data, filename, caption, replyToId } = options;
|
|
294
|
+
const opts = {};
|
|
295
|
+
if (caption) opts.caption = caption;
|
|
296
|
+
if (replyToId) opts.replyToId = replyToId;
|
|
297
|
+
return getEntry(handle).client.sendImage(
|
|
298
|
+
toBigInt(chatJid), toBuffer(data), filename || 'image.jpg', Object.keys(opts).length ? opts : undefined
|
|
299
|
+
);
|
|
300
|
+
}
|
|
230
301
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
302
|
+
async function sendVideo(handle, options) {
|
|
303
|
+
const { chatJid, data, filename, caption, replyToId } = options;
|
|
304
|
+
const opts = {};
|
|
305
|
+
if (caption) opts.caption = caption;
|
|
306
|
+
if (replyToId) opts.replyToId = replyToId;
|
|
307
|
+
return getEntry(handle).client.sendVideo(
|
|
308
|
+
toBigInt(chatJid), toBuffer(data), filename || 'video.mp4', Object.keys(opts).length ? opts : undefined
|
|
309
|
+
);
|
|
310
|
+
}
|
|
234
311
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
312
|
+
async function sendVoice(handle, options) {
|
|
313
|
+
const { chatJid, data, filename, replyToId } = options;
|
|
314
|
+
const opts = replyToId ? { replyToId } : undefined;
|
|
315
|
+
return getEntry(handle).client.sendVoice(
|
|
316
|
+
toBigInt(chatJid), toBuffer(data), filename || 'audio.ogg', opts
|
|
317
|
+
);
|
|
318
|
+
}
|
|
238
319
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
320
|
+
async function sendFile(handle, options) {
|
|
321
|
+
const { chatJid, data, filename, mimeType, caption, replyToId } = options;
|
|
322
|
+
const opts = {};
|
|
323
|
+
if (caption) opts.caption = caption;
|
|
324
|
+
if (replyToId) opts.replyToId = replyToId;
|
|
325
|
+
return getEntry(handle).client.sendFile(
|
|
326
|
+
toBigInt(chatJid), toBuffer(data), filename || 'file', mimeType || 'application/octet-stream',
|
|
327
|
+
Object.keys(opts).length ? opts : undefined
|
|
328
|
+
);
|
|
329
|
+
}
|
|
242
330
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
331
|
+
async function sendSticker(handle, options) {
|
|
332
|
+
const { chatJid, stickerId, replyToId } = options;
|
|
333
|
+
const opts = replyToId ? { replyToId } : undefined;
|
|
334
|
+
return getEntry(handle).client.sendSticker(
|
|
335
|
+
toBigInt(chatJid), toBigInt(stickerId), opts
|
|
336
|
+
);
|
|
337
|
+
}
|
|
246
338
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
339
|
+
async function getUserInfo(handle, userId) {
|
|
340
|
+
return getEntry(handle).client.getUserInfo(toBigInt(userId));
|
|
341
|
+
}
|
|
250
342
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
343
|
+
async function createThread(handle, userId) {
|
|
344
|
+
return getEntry(handle).client.createThread(toBigInt(userId));
|
|
345
|
+
}
|
|
254
346
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
347
|
+
async function searchUsers(handle, query) {
|
|
348
|
+
return getEntry(handle).client.searchUsers(query);
|
|
349
|
+
}
|
|
258
350
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
351
|
+
async function markRead(handle, chatJid, watermarkTs) {
|
|
352
|
+
return getEntry(handle).client.markAsRead(toBigInt(chatJid), watermarkTs);
|
|
353
|
+
}
|
|
262
354
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
355
|
+
function getCookies(handle) {
|
|
356
|
+
return { cookies: {} };
|
|
357
|
+
}
|
|
266
358
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
lib = null;
|
|
271
|
-
fns = null;
|
|
272
|
-
initialized = false;
|
|
273
|
-
}
|
|
359
|
+
function unload() {
|
|
360
|
+
for (const entry of registry.values()) {
|
|
361
|
+
try { entry.client.disconnect(); } catch (_) {}
|
|
274
362
|
}
|
|
363
|
+
registry.clear();
|
|
364
|
+
_Client = null;
|
|
365
|
+
_loadError = null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
module.exports = {
|
|
369
|
+
isAvailable() {
|
|
370
|
+
try { loadClient(); return true; } catch (_) { return false; }
|
|
371
|
+
},
|
|
372
|
+
newClient,
|
|
373
|
+
connect,
|
|
374
|
+
connectE2EE,
|
|
375
|
+
disconnect,
|
|
376
|
+
isConnected,
|
|
377
|
+
getDeviceData,
|
|
378
|
+
pollEvents,
|
|
379
|
+
// E2EE operations
|
|
380
|
+
sendE2EEMessage,
|
|
381
|
+
sendE2EEReaction,
|
|
382
|
+
sendE2EETyping,
|
|
383
|
+
editE2EEMessage,
|
|
384
|
+
unsendE2EEMessage,
|
|
385
|
+
sendE2EEImage,
|
|
386
|
+
sendE2EEVideo,
|
|
387
|
+
sendE2EEAudio,
|
|
388
|
+
sendE2EEDocument,
|
|
389
|
+
sendE2EESticker,
|
|
390
|
+
downloadE2EEMedia,
|
|
391
|
+
// Regular Messenger operations
|
|
392
|
+
uploadMedia,
|
|
393
|
+
sendImage,
|
|
394
|
+
sendVideo,
|
|
395
|
+
sendVoice,
|
|
396
|
+
sendFile,
|
|
397
|
+
sendSticker,
|
|
398
|
+
getUserInfo,
|
|
399
|
+
createThread,
|
|
400
|
+
searchUsers,
|
|
401
|
+
markRead,
|
|
402
|
+
getCookies,
|
|
403
|
+
unload,
|
|
275
404
|
};
|
package/src/e2ee/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const fs = require('fs');
|
|
5
4
|
const bridge = require('./bridge');
|
|
6
5
|
|
|
7
6
|
const DEFAULT_DEVICE_PATH = path.join(process.cwd(), '.nkxfca_e2ee_device.json');
|
|
@@ -19,20 +18,10 @@ function extractCookies(jar) {
|
|
|
19
18
|
return cookies;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
function loadSavedDeviceData(devicePath) {
|
|
28
|
-
try {
|
|
29
|
-
if (fs.existsSync(devicePath)) {
|
|
30
|
-
return fs.readFileSync(devicePath, 'utf8');
|
|
31
|
-
}
|
|
32
|
-
} catch (_) {}
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Return the bridge handle for this context, creating one on first call.
|
|
23
|
+
* meta-messenger.js handles device data persistence automatically via devicePath.
|
|
24
|
+
*/
|
|
36
25
|
async function getOrCreateHandle(ctx, options) {
|
|
37
26
|
if (ctx._e2eeBridgeHandle != null) return ctx._e2eeBridgeHandle;
|
|
38
27
|
|
|
@@ -41,7 +30,7 @@ async function getOrCreateHandle(ctx, options) {
|
|
|
41
30
|
|
|
42
31
|
const cfg = {
|
|
43
32
|
cookies,
|
|
44
|
-
platform:
|
|
33
|
+
platform: 'facebook',
|
|
45
34
|
logLevel: 'warn',
|
|
46
35
|
devicePath,
|
|
47
36
|
...(options || {}),
|