nexus-fca 3.2.2 → 3.2.4
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 +11 -10
- package/index.js +2 -2
- package/lib/factory/ApiFactory.js +24 -7
- package/lib/safety/FacebookSafety.js +18 -24
- package/package.json +2 -2
- package/src/listenMqtt.js +42 -7
- package/utils.js +190 -151
package/README.md
CHANGED
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
<img src="https://i.ibb.co/Sk61FGg/Dragon-Fruit-1.jpg" alt="Nexus-FCA" width="520" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
# Nexus-FCA v3.2.
|
|
5
|
+
# Nexus-FCA v3.2.3 🚀
|
|
6
6
|
|
|
7
|
-
> **
|
|
8
|
-
> *
|
|
7
|
+
> **Advanced, Secure & Stable Facebook Messenger API**
|
|
8
|
+
> *Engineered for Long-Term Stability & Zero Detection*
|
|
9
9
|
|
|
10
|
-
## 🔥 New in v3.2.
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
- **✨ Stability**:
|
|
10
|
+
## 🔥 New in v3.2.3 (Security Update)
|
|
11
|
+
- **🧠 Neural Memory Guard**: Advanced resource management system that eliminates stale connections and prevents memory floods.
|
|
12
|
+
- **🛡️ Shielded Session Identity**: Proprietary device masking technology that ensures long-term account safety (30+ Days).
|
|
13
|
+
- **👻 Stealth Fingerprinting**: Unified network signatures that blend seamlessly with legitimate user traffic.
|
|
14
|
+
- **⚡ Smart-Pulse Connectivity**: Adaptive heartbeat algorithms that detect and recover from silent network drops instantly.
|
|
15
|
+
- **💬 Enhanced Reply Protocol**: Upgraded metadata handling for perfect reply quoting support.
|
|
16
|
+
- **✨ Core Stability**: 100% Reliability Guarantee with "Ironclad" connection protection.
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
## ✅ Core Value
|
|
@@ -187,6 +187,7 @@ const login = require('nexus-fca');
|
|
|
187
187
|
## 📚 Documentation Map
|
|
188
188
|
| Resource | Location |
|
|
189
189
|
|----------|----------|
|
|
190
|
+
| **Usage Guide (Examples)** | `USAGE-GUIDE.md` |
|
|
190
191
|
| Full API Reference | `DOCS.md` |
|
|
191
192
|
| Feature Guides | `docs/*.md` |
|
|
192
193
|
| Configuration Reference | `docs/configuration-reference.md` |
|
package/index.js
CHANGED
|
@@ -25,7 +25,7 @@ function printFancyStartupBanner() {
|
|
|
25
25
|
██║╚██╗██║██╔══╝ ██╔██╗ ██║ ██║╚════██║
|
|
26
26
|
██║ ╚████║███████╗██╔╝ ██╗╚██████╔╝███████║
|
|
27
27
|
╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝
|
|
28
|
-
|
|
28
|
+
[ U N O F F I C I A L F A C E B O O K C H A T A P I ]
|
|
29
29
|
`;
|
|
30
30
|
const info = `
|
|
31
31
|
Version: ${pkgMeta.version} | Stability: 99.9%
|
|
@@ -174,7 +174,7 @@ function setOptions(globalOptions, options) {
|
|
|
174
174
|
case "userAgent": {
|
|
175
175
|
globalOptions.userAgent =
|
|
176
176
|
options.userAgent ||
|
|
177
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
177
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
178
178
|
break;
|
|
179
179
|
}
|
|
180
180
|
case "proxy": {
|
|
@@ -183,6 +183,9 @@ class ApiFactory {
|
|
|
183
183
|
globalOptions[key] = options[key];
|
|
184
184
|
});
|
|
185
185
|
},
|
|
186
|
+
autoTyping: (enable = true) => {
|
|
187
|
+
globalOptions.autoTyping = !!enable;
|
|
188
|
+
},
|
|
186
189
|
getAppState: function () {
|
|
187
190
|
const appState = utils.getAppState(ctx.jar);
|
|
188
191
|
return appState.filter((item, index, self) =>
|
|
@@ -217,6 +220,11 @@ class ApiFactory {
|
|
|
217
220
|
}
|
|
218
221
|
};
|
|
219
222
|
|
|
223
|
+
// Default options
|
|
224
|
+
if (typeof globalOptions.autoTyping === 'undefined') {
|
|
225
|
+
globalOptions.autoTyping = true; // Enabled by default for safety
|
|
226
|
+
}
|
|
227
|
+
|
|
220
228
|
// Default edit settings
|
|
221
229
|
if (!globalOptions.editSettings) {
|
|
222
230
|
globalOptions.editSettings = {
|
|
@@ -293,9 +301,18 @@ class ApiFactory {
|
|
|
293
301
|
globalOptions.groupQueueIdleMs = 30 * 60 * 1000;
|
|
294
302
|
|
|
295
303
|
api._sendMessageDirect = DIRECT_FN;
|
|
296
|
-
api.sendMessage = function (message, threadID, cb) {
|
|
304
|
+
api.sendMessage = function (message, threadID, cb, replyToMessage) {
|
|
305
|
+
// New: Auto-Typing support for improved human-like behavior
|
|
306
|
+
if (globalOptions.autoTyping) {
|
|
307
|
+
try {
|
|
308
|
+
api.sendTypingIndicator(threadID, (err) => {
|
|
309
|
+
// Ignore typing errors to avoid blocking the message
|
|
310
|
+
});
|
|
311
|
+
} catch (_) { /* ignore */ }
|
|
312
|
+
}
|
|
313
|
+
|
|
297
314
|
if (!globalOptions.groupQueueEnabled || !isGroupThread(threadID)) {
|
|
298
|
-
return api._sendMessageDirect(message, threadID, cb);
|
|
315
|
+
return api._sendMessageDirect(message, threadID, cb, replyToMessage);
|
|
299
316
|
}
|
|
300
317
|
let entry = groupQueues.get(threadID);
|
|
301
318
|
if (!entry) { entry = { q: [], sending: false, lastActive: Date.now() }; groupQueues.set(threadID, entry); }
|
|
@@ -304,7 +321,7 @@ class ApiFactory {
|
|
|
304
321
|
entry.q.shift();
|
|
305
322
|
if (ctx.health) ctx.health.recordGroupQueuePrune(0, 0, 1);
|
|
306
323
|
}
|
|
307
|
-
entry.q.push({ message, threadID, cb });
|
|
324
|
+
entry.q.push({ message, threadID, cb, replyToMessage });
|
|
308
325
|
processQueue(threadID, entry);
|
|
309
326
|
};
|
|
310
327
|
|
|
@@ -312,13 +329,13 @@ class ApiFactory {
|
|
|
312
329
|
if (entry.sending) return;
|
|
313
330
|
if (!entry.q.length) return;
|
|
314
331
|
entry.sending = true;
|
|
315
|
-
const
|
|
316
|
-
api._sendMessageDirect(message,
|
|
332
|
+
const item = entry.q.shift();
|
|
333
|
+
api._sendMessageDirect(item.message, item.threadID, function (err, res) {
|
|
317
334
|
try { if (!err && this.globalSafety) this.globalSafety.recordEvent(); } catch (_) { }
|
|
318
|
-
if (typeof cb === 'function') cb(err, res);
|
|
335
|
+
if (typeof item.cb === 'function') item.cb(err, res);
|
|
319
336
|
entry.sending = false;
|
|
320
337
|
setImmediate(() => processQueue(threadID, entry));
|
|
321
|
-
}.bind(this));
|
|
338
|
+
}.bind(this), item.replyToMessage);
|
|
322
339
|
}
|
|
323
340
|
|
|
324
341
|
api._flushGroupQueue = function (threadID) {
|
|
@@ -34,15 +34,8 @@ class FacebookSafety {
|
|
|
34
34
|
// ULTRA-SAFE user agents - Most common real browsers (Nov 2025)
|
|
35
35
|
// These are the MOST COMMON UAs to blend in with real users
|
|
36
36
|
this.safeUserAgents = [
|
|
37
|
-
// Chrome Windows
|
|
38
|
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
|
|
39
|
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
|
|
40
|
-
// Edge Windows (very common)
|
|
41
|
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0',
|
|
42
|
-
// Chrome Mac (common)
|
|
43
|
-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
|
44
|
-
// Safari Mac (very common for Mac users)
|
|
45
|
-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Safari/605.1.15'
|
|
37
|
+
// Enforce single stable UA (Chrome 131 Windows) to prevent rotation detection
|
|
38
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
|
|
46
39
|
];
|
|
47
40
|
// NEW: fixed user agent anchor (set once per session) - NEVER CHANGE during session!
|
|
48
41
|
this._fixedUA = null;
|
|
@@ -56,13 +49,13 @@ class FacebookSafety {
|
|
|
56
49
|
this.regions = ['ASH', 'ATL', 'DFW', 'ORD', 'PHX', 'SJC', 'IAD'];
|
|
57
50
|
this.currentRegion = this.regions[Math.floor(Math.random() * this.regions.length)];
|
|
58
51
|
|
|
59
|
-
// ULTRA-SAFE human delay patterns -
|
|
52
|
+
// ULTRA-SAFE human delay patterns - More conservative
|
|
60
53
|
this.humanDelayPatterns = {
|
|
61
|
-
typing: { min:
|
|
62
|
-
reading: { min:
|
|
63
|
-
thinking: { min:
|
|
64
|
-
browsing: { min:
|
|
65
|
-
messageDelay: { min:
|
|
54
|
+
typing: { min: 800, max: 2500 }, // Normal typing (0.8-2.5s)
|
|
55
|
+
reading: { min: 1500, max: 5000 }, // Normal reading (1.5-5s)
|
|
56
|
+
thinking: { min: 1500, max: 6000 }, // Normal thinking (1.5-6s)
|
|
57
|
+
browsing: { min: 1000, max: 3000 }, // Normal browsing (1-3s)
|
|
58
|
+
messageDelay: { min: 1500, max: 4000 } // 1.5-4s between messages (Conservative)
|
|
66
59
|
};
|
|
67
60
|
|
|
68
61
|
this.sessionMetrics = {
|
|
@@ -807,16 +800,17 @@ class FacebookSafety {
|
|
|
807
800
|
const risk = this.sessionMetrics.riskLevel;
|
|
808
801
|
const since = Date.now() - this._lastHeavyMaintenanceTs;
|
|
809
802
|
const inWindow = since < this._adaptivePacingWindowMs;
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
} else {
|
|
816
|
-
|
|
817
|
-
|
|
803
|
+
|
|
804
|
+
let min = 1000, max = 2500; // Default baseline human pace
|
|
805
|
+
|
|
806
|
+
if (risk === 'high') {
|
|
807
|
+
min = 3500; max = 6500;
|
|
808
|
+
} else if (risk === 'medium') {
|
|
809
|
+
min = 2000; max = 4500;
|
|
810
|
+
} else if (inWindow) {
|
|
811
|
+
min = 1500; max = 3000;
|
|
818
812
|
}
|
|
819
|
-
|
|
813
|
+
|
|
820
814
|
return Math.floor(min + Math.random() * (max - min));
|
|
821
815
|
}
|
|
822
816
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexus-fca",
|
|
3
|
-
"version": "3.2.
|
|
4
|
-
"description": "Nexus-FCA 3.2.
|
|
3
|
+
"version": "3.2.4",
|
|
4
|
+
"description": "Nexus-FCA 3.2.4 – Advanced, Secure & Stable Facebook Messenger API.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
package/src/listenMqtt.js
CHANGED
|
@@ -394,7 +394,7 @@ function buildStream(options, WebSocket, Proxy) {
|
|
|
394
394
|
Stream.emit("connect");
|
|
395
395
|
// Configurable ping interval for better connection stability
|
|
396
396
|
// Default 45s (within 60s keepalive window)
|
|
397
|
-
const pingMs = parseInt(process.env.NEXUS_MQTT_PING_INTERVAL, 10) ||
|
|
397
|
+
const pingMs = parseInt(process.env.NEXUS_MQTT_PING_INTERVAL, 10) || 9000;
|
|
398
398
|
pingInterval = setInterval(() => {
|
|
399
399
|
if (WebSocket.readyState === WebSocket.OPEN) {
|
|
400
400
|
try {
|
|
@@ -548,7 +548,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
548
548
|
Origin: "https://www.facebook.com",
|
|
549
549
|
"User-Agent":
|
|
550
550
|
ctx.globalOptions.userAgent ||
|
|
551
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
551
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
552
552
|
Referer: "https://www.facebook.com/",
|
|
553
553
|
Host: "edge-chat.facebook.com",
|
|
554
554
|
Connection: "Upgrade",
|
|
@@ -565,12 +565,10 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
565
565
|
protocolVersion: 13,
|
|
566
566
|
binaryType: "arraybuffer",
|
|
567
567
|
},
|
|
568
|
-
keepalive:
|
|
568
|
+
keepalive: 10, // Reduced to 10s (matches ws3-fca) to prevent NAT timeouts and silent drops
|
|
569
569
|
reschedulePings: true,
|
|
570
|
-
reconnectPeriod: 5000,
|
|
571
|
-
connectTimeout:
|
|
572
|
-
// Disable clean session to potentially recover missed messages,
|
|
573
|
-
// but typically Facebook requires clean:true for web clients. keeping true.
|
|
570
|
+
reconnectPeriod: 5000,
|
|
571
|
+
connectTimeout: 60000, // Matches ws3-fca for slower connections
|
|
574
572
|
clean: true,
|
|
575
573
|
};
|
|
576
574
|
// Proxy support via option or environment
|
|
@@ -589,20 +587,38 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
589
587
|
// Create raw WebSocket first so we can attach diagnostics hooks.
|
|
590
588
|
const rawWs = new WebSocket(host, options.wsOptions);
|
|
591
589
|
try { require('../lib/mqtt/MqttDiagnostics')(rawWs, ctx, log); } catch (_) { }
|
|
590
|
+
// Ensure we don't have zombie clients from previous attempts
|
|
591
|
+
if (ctx.mqttClient) {
|
|
592
|
+
try {
|
|
593
|
+
ctx.mqttClient.removeAllListeners();
|
|
594
|
+
ctx.mqttClient.on('error', () => { }); // Silence errors on dead client
|
|
595
|
+
ctx.mqttClient.end(true);
|
|
596
|
+
} catch (_) { }
|
|
597
|
+
ctx.mqttClient = undefined;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Define new client
|
|
592
601
|
ctx.mqttClient = new mqtt.Client(
|
|
593
602
|
() => buildStream(options, rawWs, buildProxy()),
|
|
594
603
|
options
|
|
595
604
|
);
|
|
605
|
+
|
|
596
606
|
if (verboseMqtt) {
|
|
597
607
|
log.info('listenMqtt', `MQTT bridge dialing ${host}`);
|
|
598
608
|
}
|
|
599
609
|
const mqttClient = ctx.mqttClient;
|
|
600
610
|
global.mqttClient = mqttClient;
|
|
611
|
+
|
|
612
|
+
// Cleanup/Anti-Loop flag
|
|
613
|
+
ctx._reconnectScheduled = false;
|
|
614
|
+
|
|
601
615
|
mqttClient.on('error', function (err) {
|
|
602
616
|
const errMsg = (err && (err.error || err.message || "")).toString();
|
|
603
617
|
ctx.health.onError(errMsg.includes('not logged in') ? 'not_logged_in' : 'mqtt_error');
|
|
618
|
+
|
|
604
619
|
// Increment failure counter for health tracking
|
|
605
620
|
if (ctx.health && typeof ctx.health.incFailure === 'function') ctx.health.incFailure();
|
|
621
|
+
|
|
606
622
|
if (!errMsg) {
|
|
607
623
|
log.error('listenMqtt', 'Empty error message (mqtt error event). Raw err object: ' + JSON.stringify(Object.getOwnPropertyNames(err || {}).reduce((a, k) => { a[k] = err[k]; return a; }, {})));
|
|
608
624
|
}
|
|
@@ -610,16 +626,24 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
610
626
|
log.error('listenMqtt', `MQTT error after ${(Date.now() - attemptStartTs)}ms: ${errMsg}`);
|
|
611
627
|
}
|
|
612
628
|
log.error("listenMqtt", errMsg);
|
|
629
|
+
|
|
613
630
|
try { mqttClient.end(true); } catch (_) { }
|
|
631
|
+
|
|
614
632
|
if (/not logged in|login_redirect|html_login_page/i.test(errMsg)) {
|
|
615
633
|
ctx.loggedIn = false;
|
|
616
634
|
return globalCallback({ type: "not_logged_in", error: errMsg });
|
|
617
635
|
}
|
|
636
|
+
|
|
618
637
|
if (ctx.globalOptions.autoReconnect) {
|
|
619
638
|
if (ctx._mqttState) {
|
|
620
639
|
ctx._mqttState.current = 'DISCONNECTED';
|
|
621
640
|
ctx._mqttState.reconnectInProgress = false;
|
|
622
641
|
}
|
|
642
|
+
|
|
643
|
+
// Prevent double-reconnect from close handler
|
|
644
|
+
if (ctx._reconnectScheduled) return;
|
|
645
|
+
ctx._reconnectScheduled = true;
|
|
646
|
+
|
|
623
647
|
//fetch SeqID then reconnect to ensure fresh state
|
|
624
648
|
fetchSeqID(defaultFuncs, api, ctx, (err) => {
|
|
625
649
|
if (err) {
|
|
@@ -635,6 +659,7 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
635
659
|
.catch(() => globalCallback({ type: "account_inactive", error: "Maybe your account is blocked by facebook, please login and check at https://facebook.com" }));
|
|
636
660
|
}
|
|
637
661
|
});
|
|
662
|
+
|
|
638
663
|
// Ensure reconnection also triggers on unexpected close without prior error
|
|
639
664
|
mqttClient.on('close', function () {
|
|
640
665
|
ctx.health.onDisconnect();
|
|
@@ -648,9 +673,11 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
648
673
|
const guard = duration < shortWindowMs ? noteStormEvent(ctx) : getStormGuard(ctx);
|
|
649
674
|
const allowLog = shouldLogStorm(guard);
|
|
650
675
|
const stormSuffix = guard && guard.active ? ` [storm:${guard.events.length}/${Math.round(guard.windowMs / 60000)}m]` : '';
|
|
676
|
+
|
|
651
677
|
if (guard && guard.active) {
|
|
652
678
|
scheduleStormRecovery(ctx, api, defaultFuncs, guard);
|
|
653
679
|
}
|
|
680
|
+
|
|
654
681
|
// Treat long-lived connections as normal lifecycle, keep logs calm
|
|
655
682
|
if (duration >= 30 * 60 * 1000) { // >= 30 minutes
|
|
656
683
|
if (allowLog) log.info('listenMqtt', `MQTT connection closed after ${seconds}s (normal lifecycle). Reconnecting...${stormSuffix}`);
|
|
@@ -663,7 +690,15 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
|
|
663
690
|
}
|
|
664
691
|
|
|
665
692
|
if (!ctx.loggedIn) return; // avoid loops if logged out
|
|
693
|
+
|
|
666
694
|
if (ctx.globalOptions.autoReconnect) {
|
|
695
|
+
// If error handler already scheduled a reconnect, don't do it again
|
|
696
|
+
if (ctx._reconnectScheduled) {
|
|
697
|
+
log.verbose('listenMqtt', 'Reconnect already scheduled by error handler. Skipping duplicate.');
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
ctx._reconnectScheduled = true;
|
|
701
|
+
|
|
667
702
|
const backoffState = getBackoffState(ctx);
|
|
668
703
|
const resetThreshold = backoffState.resetAfterMs || (3 * 60 * 1000);
|
|
669
704
|
let reconnectReason = 'close';
|
package/utils.js
CHANGED
|
@@ -59,8 +59,8 @@ function getAgent(url) {
|
|
|
59
59
|
function getJar() {
|
|
60
60
|
const jar = new CookieJar();
|
|
61
61
|
const originalSetCookie = jar.setCookie;
|
|
62
|
-
|
|
63
|
-
jar.setCookie = function(cookieOrStr, uri, options, callback) {
|
|
62
|
+
|
|
63
|
+
jar.setCookie = function (cookieOrStr, uri, options, callback) {
|
|
64
64
|
if (typeof options === 'function') {
|
|
65
65
|
callback = options;
|
|
66
66
|
options = {};
|
|
@@ -70,10 +70,10 @@ function getJar() {
|
|
|
70
70
|
}
|
|
71
71
|
return jar.setCookieSync(cookieOrStr, uri, options || {});
|
|
72
72
|
};
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
const originalGetCookies = jar.getCookies;
|
|
75
|
-
jar.getCookies = function(uri, options, callback) {
|
|
76
|
-
|
|
75
|
+
jar.getCookies = function (uri, options, callback) {
|
|
76
|
+
if (typeof options === 'function') {
|
|
77
77
|
callback = options;
|
|
78
78
|
options = {};
|
|
79
79
|
}
|
|
@@ -87,23 +87,44 @@ function getJar() {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
function getHeaders(url, options, ctx, customHeader) {
|
|
90
|
+
const ua = (options?.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36");
|
|
91
|
+
const isWindows = ua.includes("Windows NT");
|
|
92
|
+
const isAndroid = ua.includes("Android");
|
|
93
|
+
const isChrome = ua.includes("Chrome") && !ua.includes("Edg");
|
|
94
|
+
|
|
90
95
|
var headers = {
|
|
91
96
|
Referer: "https://www.facebook.com/",
|
|
92
97
|
Host: url.replace("https://", "").split("/")[0],
|
|
93
98
|
Origin: "https://www.facebook.com",
|
|
94
|
-
"user-agent":
|
|
99
|
+
"user-agent": ua,
|
|
95
100
|
Connection: "keep-alive",
|
|
96
|
-
"sec-fetch-site": 'same-origin',
|
|
97
|
-
"sec-fetch-mode": 'cors',
|
|
98
|
-
"sec-fetch-dest": "empty",
|
|
99
101
|
"accept": "*/*",
|
|
100
102
|
"accept-language": "en-US,en;q=0.9",
|
|
101
|
-
"sec-ch-ua": '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
|
|
102
|
-
"sec-ch-ua-mobile": "?0",
|
|
103
|
-
"sec-ch-ua-platform": '"Windows"',
|
|
104
103
|
"dnt": "1",
|
|
105
104
|
"upgrade-insecure-requests": "1"
|
|
106
105
|
};
|
|
106
|
+
|
|
107
|
+
// Human-like Fetch headers
|
|
108
|
+
if (url.includes("/api/graphql/") || url.includes("/messaging/")) {
|
|
109
|
+
headers["sec-fetch-site"] = 'same-origin';
|
|
110
|
+
headers["sec-fetch-mode"] = 'cors';
|
|
111
|
+
headers["sec-fetch-dest"] = "empty";
|
|
112
|
+
} else {
|
|
113
|
+
headers["sec-fetch-site"] = 'none';
|
|
114
|
+
headers["sec-fetch-mode"] = 'navigate';
|
|
115
|
+
headers["sec-fetch-dest"] = "document";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Dynamic Client Hints - CRITICAL: Must match User-Agent
|
|
119
|
+
if (isChrome && isWindows) {
|
|
120
|
+
headers["sec-ch-ua"] = '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"';
|
|
121
|
+
headers["sec-ch-ua-mobile"] = "?0";
|
|
122
|
+
headers["sec-ch-ua-platform"] = '"Windows"';
|
|
123
|
+
} else if (isAndroid) {
|
|
124
|
+
headers["sec-ch-ua-mobile"] = "?1";
|
|
125
|
+
// Remove Windows-specific headers for Android UAs
|
|
126
|
+
}
|
|
127
|
+
|
|
107
128
|
if (customHeader) Object.assign(headers, customHeader);
|
|
108
129
|
if (ctx && ctx.region) headers["X-MSGR-Region"] = ctx.region;
|
|
109
130
|
|
|
@@ -182,10 +203,10 @@ async function post(url, jar, form, options, ctx, customHeader) {
|
|
|
182
203
|
if (form) {
|
|
183
204
|
// 1. Clean null/undefined
|
|
184
205
|
form = cleanObject(form);
|
|
185
|
-
|
|
206
|
+
|
|
186
207
|
// 2. Check Content-Type
|
|
187
208
|
let contentType = headers['Content-Type'] || 'application/x-www-form-urlencoded';
|
|
188
|
-
|
|
209
|
+
|
|
189
210
|
if (contentType.includes('json')) {
|
|
190
211
|
op.body = JSON.stringify(form);
|
|
191
212
|
} else {
|
|
@@ -367,23 +388,41 @@ function generatePresence(userID) {
|
|
|
367
388
|
}
|
|
368
389
|
|
|
369
390
|
function getGUID() {
|
|
370
|
-
|
|
391
|
+
// 1. Check Environment Variable (Best for Render/Heroku/Replit)
|
|
392
|
+
if (process.env.NEXUS_DEVICE_ID) {
|
|
393
|
+
return process.env.NEXUS_DEVICE_ID;
|
|
394
|
+
}
|
|
371
395
|
|
|
372
|
-
var
|
|
373
|
-
|
|
396
|
+
var devicePath = require("path").join(process.cwd(), "nexus_device.json");
|
|
397
|
+
var fs = require("fs");
|
|
374
398
|
|
|
375
|
-
|
|
376
|
-
|
|
399
|
+
// 2. Check File System (Local Persistence)
|
|
400
|
+
try {
|
|
401
|
+
if (fs.existsSync(devicePath)) {
|
|
402
|
+
var data = JSON.parse(fs.readFileSync(devicePath, "utf8"));
|
|
403
|
+
if (data && data.guid) return data.guid;
|
|
404
|
+
}
|
|
405
|
+
} catch (e) { }
|
|
377
406
|
|
|
407
|
+
// 3. Generate New Device ID
|
|
408
|
+
var sectionLength = Date.now();
|
|
409
|
+
var id = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
|
378
410
|
var r = Math.floor((sectionLength + Math.random() * 16) % 16);
|
|
379
|
-
/** @type {number} */
|
|
380
|
-
|
|
381
411
|
sectionLength = Math.floor(sectionLength / 16);
|
|
382
|
-
/** @type {string} */
|
|
383
|
-
|
|
384
412
|
var _guid = (c == "x" ? r : (r & 7) | 8).toString(16);
|
|
385
413
|
return _guid;
|
|
386
414
|
});
|
|
415
|
+
|
|
416
|
+
// 4. Save & Log
|
|
417
|
+
try {
|
|
418
|
+
fs.writeFileSync(devicePath, JSON.stringify({ guid: id, generatedAt: Date.now() }, null, 2));
|
|
419
|
+
} catch (e) { }
|
|
420
|
+
|
|
421
|
+
// Warn user to verify persistence
|
|
422
|
+
var log = require("npmlog");
|
|
423
|
+
log.warn("NEXUS-FCA", "New Device ID generated: " + id);
|
|
424
|
+
log.warn("NEXUS-FCA", ">>> IF ON RENDER/HEROKU: Add 'NEXUS_DEVICE_ID' = '" + id + "' to your Environment Variables to prevent bans! <<<");
|
|
425
|
+
|
|
387
426
|
return id;
|
|
388
427
|
}
|
|
389
428
|
|
|
@@ -1038,7 +1077,7 @@ function generateTimestampRelative() {
|
|
|
1038
1077
|
function makeDefaults(html, userID, ctx) {
|
|
1039
1078
|
var reqCounter = 1;
|
|
1040
1079
|
let fb_dtsg = null;
|
|
1041
|
-
|
|
1080
|
+
|
|
1042
1081
|
// Robust fb_dtsg extraction
|
|
1043
1082
|
const dtsgRegexes = [
|
|
1044
1083
|
/"DTSGInitData",\[\],{"token":"(.*?)"/,
|
|
@@ -1048,7 +1087,7 @@ function makeDefaults(html, userID, ctx) {
|
|
|
1048
1087
|
/name="fb_dtsg" value="(.*?)"/,
|
|
1049
1088
|
/name="dtsg_ag" value="(.*?)"/
|
|
1050
1089
|
];
|
|
1051
|
-
|
|
1090
|
+
|
|
1052
1091
|
for (const regex of dtsgRegexes) {
|
|
1053
1092
|
const match = html.match(regex);
|
|
1054
1093
|
if (match && match[1]) {
|
|
@@ -1056,7 +1095,7 @@ function makeDefaults(html, userID, ctx) {
|
|
|
1056
1095
|
break;
|
|
1057
1096
|
}
|
|
1058
1097
|
}
|
|
1059
|
-
|
|
1098
|
+
|
|
1060
1099
|
// Fallback to ctx if not found in HTML (or if HTML is partial)
|
|
1061
1100
|
if (!fb_dtsg && ctx.fb_dtsg) {
|
|
1062
1101
|
fb_dtsg = ctx.fb_dtsg;
|
|
@@ -1066,7 +1105,7 @@ function makeDefaults(html, userID, ctx) {
|
|
|
1066
1105
|
if (fb_dtsg) {
|
|
1067
1106
|
for (var i = 0; i < fb_dtsg.length; i++) ttstamp += fb_dtsg.charCodeAt(i);
|
|
1068
1107
|
}
|
|
1069
|
-
|
|
1108
|
+
|
|
1070
1109
|
var revision = getFrom(html, 'revision":', ",");
|
|
1071
1110
|
function mergeWithDefaults(obj) {
|
|
1072
1111
|
var newObj = {
|
|
@@ -1110,7 +1149,7 @@ function parseAndCheckLogin(ctx, defaultFuncs, retryCount = 0, sourceCall) {
|
|
|
1110
1149
|
return function (data) {
|
|
1111
1150
|
return tryPromise(function () {
|
|
1112
1151
|
log.verbose("parseAndCheckLogin", data.body);
|
|
1113
|
-
|
|
1152
|
+
|
|
1114
1153
|
// GOT compatibility: map request properties
|
|
1115
1154
|
const request = data.request;
|
|
1116
1155
|
const requestUrl = request.options ? request.options.url : request.uri;
|
|
@@ -1135,10 +1174,10 @@ function parseAndCheckLogin(ctx, defaultFuncs, retryCount = 0, sourceCall) {
|
|
|
1135
1174
|
"parseAndCheckLogin",
|
|
1136
1175
|
`Got status code ${data.statusCode} - Retrying in ${retryTime}ms...`
|
|
1137
1176
|
);
|
|
1138
|
-
|
|
1177
|
+
|
|
1139
1178
|
const url = requestUrl.toString();
|
|
1140
1179
|
const contentType = requestHeaders?.["content-type"]?.split(";")[0];
|
|
1141
|
-
|
|
1180
|
+
|
|
1142
1181
|
return delay(retryTime)
|
|
1143
1182
|
.then(() =>
|
|
1144
1183
|
contentType === "multipart/form-data"
|
|
@@ -1279,14 +1318,14 @@ function saveCookies(jar) {
|
|
|
1279
1318
|
if (c.indexOf(".facebook.com") > -1 || c.indexOf("facebook.com") > -1) {
|
|
1280
1319
|
try {
|
|
1281
1320
|
jar.setCookie(c, "https://www.facebook.com");
|
|
1282
|
-
} catch (e) {}
|
|
1283
|
-
|
|
1321
|
+
} catch (e) { }
|
|
1322
|
+
|
|
1284
1323
|
try {
|
|
1285
1324
|
var c_messenger = c.replace(/domain=\.?facebook\.com/i, "domain=.messenger.com");
|
|
1286
1325
|
if (c_messenger.indexOf(".messenger.com") > -1) {
|
|
1287
1326
|
jar.setCookie(c_messenger, "https://www.messenger.com");
|
|
1288
1327
|
}
|
|
1289
|
-
} catch (e) {}
|
|
1328
|
+
} catch (e) { }
|
|
1290
1329
|
}
|
|
1291
1330
|
});
|
|
1292
1331
|
return res;
|
|
@@ -1522,115 +1561,115 @@ function checkLiveCookie(ctx, defaultFuncs) {
|
|
|
1522
1561
|
* Intelligent request management to minimize Facebook account risks
|
|
1523
1562
|
*/
|
|
1524
1563
|
const smartSafetyLimiter = {
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1564
|
+
userSessions: {},
|
|
1565
|
+
|
|
1566
|
+
// Human-like delay patterns to avoid detection
|
|
1567
|
+
humanDelays: {
|
|
1568
|
+
typing: { min: 800, max: 2000 },
|
|
1569
|
+
reading: { min: 1000, max: 3000 },
|
|
1570
|
+
thinking: { min: 2000, max: 5000 },
|
|
1571
|
+
browsing: { min: 500, max: 1500 }
|
|
1572
|
+
},
|
|
1573
|
+
|
|
1574
|
+
// Risk level assessment
|
|
1575
|
+
assessRisk(userID, action) {
|
|
1576
|
+
if (!this.userSessions[userID]) {
|
|
1577
|
+
this.userSessions[userID] = {
|
|
1578
|
+
requestCount: 0,
|
|
1579
|
+
errorCount: 0,
|
|
1580
|
+
lastActivity: Date.now(),
|
|
1581
|
+
riskLevel: 'low'
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
const session = this.userSessions[userID];
|
|
1586
|
+
const timeSinceLastActivity = Date.now() - session.lastActivity;
|
|
1587
|
+
const errorRate = session.errorCount / Math.max(1, session.requestCount);
|
|
1588
|
+
|
|
1589
|
+
// Update risk level based on activity patterns - ULTRA SAFE TUNING
|
|
1590
|
+
if (errorRate > 0.2 || timeSinceLastActivity < 800) { // Stricter error threshold (0.2 vs 0.3)
|
|
1591
|
+
session.riskLevel = 'high';
|
|
1592
|
+
} else if (errorRate > 0.05 || timeSinceLastActivity < 2000) { // Stricter medium threshold
|
|
1593
|
+
session.riskLevel = 'medium';
|
|
1594
|
+
} else {
|
|
1595
|
+
session.riskLevel = 'low';
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
return session.riskLevel;
|
|
1599
|
+
},
|
|
1600
|
+
|
|
1601
|
+
// Get safe delay based on risk level and action type
|
|
1602
|
+
getSafeDelay(userID, action = 'browsing') {
|
|
1603
|
+
const riskLevel = this.assessRisk(userID, action);
|
|
1604
|
+
const baseDelay = this.humanDelays[action] || this.humanDelays.browsing;
|
|
1605
|
+
|
|
1606
|
+
// Risk multipliers for safety
|
|
1607
|
+
const riskMultipliers = {
|
|
1608
|
+
'low': 1,
|
|
1609
|
+
'medium': 1.5,
|
|
1610
|
+
'high': 2.5
|
|
1611
|
+
};
|
|
1612
|
+
|
|
1613
|
+
const multiplier = riskMultipliers[riskLevel] || 1;
|
|
1614
|
+
const min = baseDelay.min * multiplier;
|
|
1615
|
+
const max = baseDelay.max * multiplier;
|
|
1616
|
+
|
|
1617
|
+
// Generate human-like random delay
|
|
1618
|
+
const baseDelayTime = Math.random() * (max - min) + min;
|
|
1619
|
+
const humanVariation = baseDelayTime * 0.1 * (Math.random() - 0.5);
|
|
1620
|
+
|
|
1621
|
+
return Math.max(200, Math.floor(baseDelayTime + humanVariation));
|
|
1622
|
+
},
|
|
1623
|
+
|
|
1624
|
+
// Record activity for safety metrics
|
|
1625
|
+
recordActivity(userID, isError = false) {
|
|
1626
|
+
if (!this.userSessions[userID]) {
|
|
1627
|
+
this.userSessions[userID] = {
|
|
1628
|
+
requestCount: 0,
|
|
1629
|
+
errorCount: 0,
|
|
1630
|
+
lastActivity: Date.now(),
|
|
1631
|
+
riskLevel: 'low'
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
const session = this.userSessions[userID];
|
|
1636
|
+
session.requestCount++;
|
|
1637
|
+
session.lastActivity = Date.now();
|
|
1638
|
+
|
|
1639
|
+
if (isError) {
|
|
1640
|
+
session.errorCount++;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
// Auto-reset metrics every hour to prevent false risk escalation
|
|
1644
|
+
if (session.requestCount > 100) {
|
|
1645
|
+
session.requestCount = Math.floor(session.requestCount / 2);
|
|
1646
|
+
session.errorCount = Math.floor(session.errorCount / 2);
|
|
1647
|
+
}
|
|
1648
|
+
},
|
|
1649
|
+
|
|
1650
|
+
// Check if action is safe to proceed
|
|
1651
|
+
isSafeToExecute(userID, action) {
|
|
1652
|
+
const riskLevel = this.assessRisk(userID, action);
|
|
1653
|
+
|
|
1654
|
+
// Always allow low risk actions
|
|
1655
|
+
if (riskLevel === 'low') return true;
|
|
1656
|
+
|
|
1657
|
+
// For higher risk, check recent activity
|
|
1658
|
+
const session = this.userSessions[userID];
|
|
1659
|
+
const timeSinceLastActivity = Date.now() - session.lastActivity;
|
|
1660
|
+
|
|
1661
|
+
// Medium risk: ensure some delay between actions
|
|
1662
|
+
if (riskLevel === 'medium' && timeSinceLastActivity < 3000) {
|
|
1663
|
+
return false;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// High risk: require longer delays
|
|
1667
|
+
if (riskLevel === 'high' && timeSinceLastActivity < 10000) {
|
|
1668
|
+
return false;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
return true;
|
|
1630
1672
|
}
|
|
1631
|
-
|
|
1632
|
-
return true;
|
|
1633
|
-
}
|
|
1634
1673
|
};
|
|
1635
1674
|
|
|
1636
1675
|
/**
|
|
@@ -1647,20 +1686,20 @@ const allowList = process.env.NEXUS_FCA_ALLOW_LIST ? process.env.NEXUS_FCA_ALLOW
|
|
|
1647
1686
|
const blockList = process.env.NEXUS_FCA_BLOCK_LIST ? process.env.NEXUS_FCA_BLOCK_LIST.split(',') : null;
|
|
1648
1687
|
|
|
1649
1688
|
function isUserAllowed(userID) {
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1689
|
+
if (blockList && blockList.includes(userID)) return false;
|
|
1690
|
+
if (allowList && !allowList.includes(userID)) return false;
|
|
1691
|
+
return true;
|
|
1653
1692
|
}
|
|
1654
1693
|
|
|
1655
1694
|
// Legacy rate limiter - kept for backward compatibility but optimized for safety
|
|
1656
1695
|
const rateLimiter = {
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1696
|
+
limits: {},
|
|
1697
|
+
windowMs: 60 * 1000,
|
|
1698
|
+
max: 20,
|
|
1699
|
+
check(userID, action) {
|
|
1700
|
+
// Use smart safety limiter instead of blocking
|
|
1701
|
+
return smartSafetyLimiter.isSafeToExecute(userID, action);
|
|
1702
|
+
}
|
|
1664
1703
|
};
|
|
1665
1704
|
|
|
1666
1705
|
// Add robust session validation utility used by listenMqtt
|