toru-fca 1.0.7 → 1.1.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/CHANGELOG.md +114 -54
- package/checkUpdate.js +31 -12
- package/config.json +15 -25
- package/examples/basic-bot.js +71 -67
- package/fca-config.example.json +35 -26
- package/index.js +241 -258
- package/package.json +19 -8
- package/src/listenMqtt.js +46 -8
- package/src/logout.js +4 -7
- package/src/refreshFb_dtsg.js +41 -83
- package/src/sendMessage.js +3 -2
- package/src/sendTypingIndicator.js +60 -46
- package/src/sendTypingIndicatorV2.js +15 -34
- package/src/stopListenMqtt.js +20 -10
- package/utils.js +14 -2
- package/src/data/getThreadInfo.json +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,54 +1,114 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
## v1.0
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- **
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- **
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
- `
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
-
|
|
1
|
+
# toru-fca Changelog
|
|
2
|
+
|
|
3
|
+
## v1.1.0 — Audit, Repair & Stability Upgrade
|
|
4
|
+
|
|
5
|
+
> Full professional audit and repair of all critical, high, and medium issues.
|
|
6
|
+
|
|
7
|
+
### 🔴 Critical Fixes
|
|
8
|
+
|
|
9
|
+
#### `sendMessage.js` — Wrong global config key
|
|
10
|
+
- **Bug**: `global._alphaFcaConfig` was read to check `inbox.enabled`. This global is never set — toru-fca uses `global._toruFcaConfig`. The inbox-disabled guard silently never fired.
|
|
11
|
+
- **Fix**: Changed to `global._toruFcaConfig`. Inbox guard now works correctly.
|
|
12
|
+
|
|
13
|
+
#### `index.js` — Triple-wrapping of `api.sendMessage`
|
|
14
|
+
- **Bug**: `patchSmooth()` and `initFacebookProtection()` each independently re-wrapped `api.sendMessage`, creating three nested layers. This caused:
|
|
15
|
+
- Inconsistent Promise/callback contract on synchronous throws from inner layers
|
|
16
|
+
- `api.send` alias reassigned twice, creating divergence risk
|
|
17
|
+
- Undocumented stacked delays (300ms + 50–200ms + typing duration)
|
|
18
|
+
- **Fix**: Replaced both separate wrappers with a single unified `patchSendWrapper()` that applies human jitter → smooth delay → original `sendMessage` in one clean Promise chain. `api.send` is now assigned exactly once.
|
|
19
|
+
|
|
20
|
+
#### `index.js` / `config.json` — E2EE enabled by default without native binary
|
|
21
|
+
- **Bug**: `config.json` had `"e2ee": { "enable": true }` and the fallback defaults also had `enable: true`. The `lib/index.mjs` ESM bundle throws `Error: Native library not found at .../build/messagix.so` on load because the `build/` directory is not included in the package. This caused E2EE init to fail on every startup with a caught but opaque error.
|
|
22
|
+
- **Fix**: `e2ee.enable` defaults to `false` in both `config.json` and the fallback defaults. E2EE must be explicitly enabled by users who have compiled the native binary.
|
|
23
|
+
|
|
24
|
+
#### `listenMqtt.js` — Module-scope `getSeqID` mutation (cross-instance contamination)
|
|
25
|
+
- **Bug**: `var getSeqID = function(){};` was declared at module scope and then reassigned inside the exported factory. Multiple login instances in the same process would overwrite each other's `getSeqID` closure, causing the wrong instance's callback to receive another bot's events.
|
|
26
|
+
- **Fix**: `getSeqID` and `form` are now declared inside the exported factory function (one per instance). Each login gets its own isolated closure.
|
|
27
|
+
|
|
28
|
+
#### Version inconsistency across all files
|
|
29
|
+
- **Bug**: `package.json` said `1.0.9`, `config.json` said `1.0.8`, `index.js`/`checkUpdate.js`/MQTT banner all said `1.0.6`. Target is `1.1.0`.
|
|
30
|
+
- **Fix**: All version strings unified to `1.1.0` across `package.json`, `config.json`, `checkUpdate.js`, `index.js` header, and the MQTT connect banner.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
### 🔴 High-Priority Fixes
|
|
35
|
+
|
|
36
|
+
#### `sendTypingIndicator.js` — Local counter always sends `request_id: 1`
|
|
37
|
+
- **Bug**: `let count_req = 0` was declared inside the async function, so `++count_req` always yielded `1`. Every typing indicator request sent the same MQTT request ID, potentially confusing the ack system.
|
|
38
|
+
- **Fix**: Now uses `ctx.wsReqNumber` (the shared per-session counter, same as `sendTypingIndicatorV2`) incremented before each publish. Request IDs are now unique across all `ls_req` calls.
|
|
39
|
+
|
|
40
|
+
#### `sendTypingIndicatorV2.js` — Redundant duplicate implementation
|
|
41
|
+
- **Bug**: Two separate typing indicator implementations with inconsistent counter strategies.
|
|
42
|
+
- **Fix**: `sendTypingIndicatorV2` now delegates to `sendTypingIndicator`. Both `api.sendTypingIndicator` and `api.sendTypingIndicatorV2` use the same fixed implementation.
|
|
43
|
+
|
|
44
|
+
#### `stopListenMqtt.js` — Dead-connection watchdog not cleared on stop
|
|
45
|
+
- **Bug**: `stopListenMqtt()` ended the MQTT client but did not clear `_deadTimer` (the 8-minute watchdog). The watchdog was a local variable inside `listenMqtt()` — `stopListenMqtt.js` had no reference to it. After calling `stopListenMqtt()`, the watchdog could still fire and attempt a reconnect on a stopped bot.
|
|
46
|
+
- **Fix**: Watchdog timer now stored on `ctx._deadTimer` (accessible to all modules with a `ctx` reference). Both `stopListenMqtt()` and `stopListening()` now clear `ctx._deadTimer` before teardown.
|
|
47
|
+
|
|
48
|
+
#### `listenMqtt.js` — `stopListening` referenced undefined `_deadTimer`
|
|
49
|
+
- **Bug**: `stopListening` in `MessageEmitter` tried `if (typeof _deadTimer !== 'undefined' && _deadTimer)` — this was correct when the watchdog was local, but needed updating to match the new `ctx._deadTimer` approach.
|
|
50
|
+
- **Fix**: Updated to `if (ctx._deadTimer)` consistently.
|
|
51
|
+
|
|
52
|
+
#### `utils.js` — `parseAndCheckLogin` retry sends wrong body on 5xx
|
|
53
|
+
- **Bug**: Line 2611: `defaultFuncs.post(url, ctx.jar, data.request.formData)` — for standard POST requests `data.request.formData` is `undefined` (that property is only set for multipart uploads). All non-upload 5xx retries silently sent an empty body.
|
|
54
|
+
- **Fix**: Retry now uses `data.request.form || data.request.formData || {}` — correctly using `form` (the standard POST body property set by the `request` library) and falling back to `formData` for multipart, then to empty object as a last resort.
|
|
55
|
+
|
|
56
|
+
#### `index.js` — `forceLogout` used fb_dtsg as the `h` URL parameter
|
|
57
|
+
- **Bug**: `logout.php?h=<fb_dtsg>` — the `h` parameter on Facebook's logout endpoint is a separate logout nonce, not the CSRF token. Passing `fb_dtsg` there caused logout requests to silently fail.
|
|
58
|
+
- **Fix**: POSTs to `logout.php` (no `h` param) with `fb_dtsg` in the body only. Also adds graceful handling: marks `ctx.loggedIn = false` even when the HTTP request fails, so the bot's internal state stays consistent.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
### 🟡 Medium Fixes
|
|
63
|
+
|
|
64
|
+
#### `refreshFb_dtsg.js` — Broad regex pattern ordered too early
|
|
65
|
+
- **Bug**: Pattern `/"token":"([^"]+)"/` (matches any JSON `token` key) was #3 in the list, potentially matching unrelated tokens before the specific DTSG patterns.
|
|
66
|
+
- **Fix**: Moved broad pattern to last position. Added minimum length check (`m[1].length > 4`) to filter out short false-match values.
|
|
67
|
+
|
|
68
|
+
#### `index.js` — `sessionGuard` jar monkey-patch fragile against internal API changes
|
|
69
|
+
- **Bug**: `jar._jar` is a private internal property of `tough-cookie`. If absent (different tough-cookie version, custom jar, or API change), the patch silently did nothing with no warning.
|
|
70
|
+
- **Fix**: Wrapped in try/catch with a `log.verbose` warning when `jar._jar` is not available. The interval-based save still runs — only the debounce-on-cookie-change is affected.
|
|
71
|
+
|
|
72
|
+
#### `examples/basic-bot.js` — Shutdown called `api.stopListenMqtt()` without clearing watchdog
|
|
73
|
+
- **Bug**: `api.stopListenMqtt()` was called in the shutdown handler. While this now clears `ctx._deadTimer` (fixed above), `api.stopListening()` is the preferred method when `listenMqtt()` was used directly, as it also resets the global callback and handles the `MessageEmitter` lifecycle.
|
|
74
|
+
- **Fix**: Shutdown now calls `api.stopListening(callback)` first, with `api.stopListenMqtt()` as a fallback. Also added guard against double-shutdown (`_shutdownCalled` flag). Stops session guard timer properly.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### 🟢 Low / Optimization
|
|
79
|
+
|
|
80
|
+
#### `checkUpdate.js` — Added hard abort guard on version check HTTP request
|
|
81
|
+
- Added a `setTimeout(destroy, 8000)` as a belt-and-suspenders guard alongside the existing 5000ms socket timeout, preventing the rare case where DNS resolution hangs beyond the socket timeout window.
|
|
82
|
+
|
|
83
|
+
#### `config.json` — Bengali/mixed-language notes replaced with English
|
|
84
|
+
- All `_note` fields now in English for international maintainability.
|
|
85
|
+
|
|
86
|
+
#### `fca-config.example.json` — Updated to v1.1.0 with accurate E2EE guidance
|
|
87
|
+
- Added clear note that E2EE requires native binary. All fields documented.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### Files Modified
|
|
92
|
+
|
|
93
|
+
| File | Changes |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `package.json` | Version → 1.1.0 |
|
|
96
|
+
| `config.json` | Version → 1.1.0, e2ee.enable → false, notes in English |
|
|
97
|
+
| `checkUpdate.js` | CURRENT_VERSION → 1.1.0, hard abort guard |
|
|
98
|
+
| `index.js` | Header version, fallback config version/e2ee, unified sendMessage wrapper, forceLogout fix, sessionGuard defensive patch, logout message updated |
|
|
99
|
+
| `src/sendMessage.js` | Fixed `global._alphaFcaConfig` → `global._toruFcaConfig` |
|
|
100
|
+
| `src/sendTypingIndicator.js` | Fixed local counter (always sent request_id:1), proper error handling, non-fatal when MQTT not connected |
|
|
101
|
+
| `src/sendTypingIndicatorV2.js` | Delegates to sendTypingIndicator; removed duplicate logic |
|
|
102
|
+
| `src/stopListenMqtt.js` | Clears ctx._deadTimer before MQTT teardown |
|
|
103
|
+
| `src/listenMqtt.js` | getSeqID/form moved to instance scope; _deadTimer → ctx._deadTimer; banner version updated; stopListening uses ctx._deadTimer |
|
|
104
|
+
| `src/refreshFb_dtsg.js` | Regex ordering fixed (broad pattern last), min-length guard |
|
|
105
|
+
| `examples/basic-bot.js` | Proper shutdown (stopListening), double-shutdown guard, version updated |
|
|
106
|
+
| `fca-config.example.json` | Version → 1.1.0, E2EE docs updated |
|
|
107
|
+
| `utils.js` | parseAndCheckLogin retry body fix (form vs formData) |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## v1.0.9 (previous)
|
|
112
|
+
- Dead connection watchdog added
|
|
113
|
+
- Reaction detection works indefinitely (no longer stops after ~1hr)
|
|
114
|
+
- Browser logout free, anti-automation, GoatBot ready
|
package/checkUpdate.js
CHANGED
|
@@ -1,24 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const CURRENT_VERSION = "1.0
|
|
3
|
+
const CURRENT_VERSION = "1.1.0";
|
|
4
4
|
|
|
5
5
|
async function checkForFCAUpdate() {
|
|
6
6
|
try {
|
|
7
7
|
const https = require("https");
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
|
|
9
|
+
const data = await new Promise(function(resolve) {
|
|
10
|
+
var req = https.get(
|
|
11
|
+
"https://registry.npmjs.org/toru-fca/latest",
|
|
12
|
+
{ timeout: 5000 },
|
|
13
|
+
function(res) {
|
|
14
|
+
var body = "";
|
|
15
|
+
res.on("data", function(c) { body += c; });
|
|
16
|
+
res.on("end", function() {
|
|
17
|
+
try { resolve(JSON.parse(body)); } catch (_) { resolve(null); }
|
|
18
|
+
});
|
|
19
|
+
res.on("error", function() { resolve(null); });
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
req.on("error", function() { resolve(null); });
|
|
23
|
+
// Destroy on timeout to avoid hanging socket
|
|
24
|
+
req.on("timeout", function() { req.destroy(); resolve(null); });
|
|
25
|
+
// Hard abort after 8s as a belt-and-suspenders guard
|
|
26
|
+
var hard = setTimeout(function() { try { req.destroy(); } catch (_) {} resolve(null); }, 8000);
|
|
27
|
+
if (hard.unref) hard.unref();
|
|
16
28
|
});
|
|
29
|
+
|
|
17
30
|
if (data && data.version && data.version !== CURRENT_VERSION) {
|
|
18
|
-
|
|
19
|
-
log.warn(
|
|
31
|
+
var log = require("npmlog");
|
|
32
|
+
log.warn(
|
|
33
|
+
"toru-fca",
|
|
34
|
+
"Update available: v" + CURRENT_VERSION + " \u2192 v" + data.version +
|
|
35
|
+
" \u2014 npm update toru-fca"
|
|
36
|
+
);
|
|
20
37
|
}
|
|
21
|
-
} catch (_) {
|
|
38
|
+
} catch (_) {
|
|
39
|
+
// Non-fatal; version check is best-effort only
|
|
40
|
+
}
|
|
22
41
|
}
|
|
23
42
|
|
|
24
43
|
module.exports = { checkForFCAUpdate, CURRENT_VERSION };
|
package/config.json
CHANGED
|
@@ -1,61 +1,51 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_info": "toru-fca v1.0
|
|
3
|
-
|
|
4
|
-
"version": "1.0.6",
|
|
5
|
-
|
|
2
|
+
"_info": "toru-fca v1.1.0 — Main Config",
|
|
3
|
+
"version": "1.1.0",
|
|
6
4
|
"antiLogout": {
|
|
7
5
|
"enabled": true,
|
|
8
6
|
"refreshIntervalMs": 1200000,
|
|
9
|
-
"_note": "
|
|
7
|
+
"_note": "fb_dtsg refreshed every 20 minutes to keep session alive and prevent browser logout."
|
|
10
8
|
},
|
|
11
|
-
|
|
12
9
|
"sessionGuard": {
|
|
13
10
|
"enabled": true,
|
|
14
11
|
"intervalMs": 180000,
|
|
15
12
|
"debounceMs": 20000,
|
|
16
13
|
"backupEnabled": true,
|
|
17
|
-
"_note": "
|
|
14
|
+
"_note": "Appstate auto-saved every 3 minutes. Keeps a .bak backup. Cookie expiry is extended."
|
|
18
15
|
},
|
|
19
|
-
|
|
20
16
|
"automation": {
|
|
21
17
|
"enabled": false,
|
|
22
|
-
"_note": "KEEP FALSE.
|
|
18
|
+
"_note": "KEEP FALSE. Disables background automation. Protects against Facebook restrictions."
|
|
23
19
|
},
|
|
24
|
-
|
|
25
20
|
"smooth": {
|
|
26
21
|
"enabled": true,
|
|
27
22
|
"messageDelay": 300,
|
|
28
|
-
"_note": "Shows typing indicator before sending.
|
|
23
|
+
"_note": "Shows typing indicator before sending. 300ms delay for human-like behavior."
|
|
29
24
|
},
|
|
30
|
-
|
|
31
25
|
"connection": {
|
|
32
26
|
"minDelayMs": 3000,
|
|
33
27
|
"maxDelayMs": 60000,
|
|
34
|
-
"_note": "MQTT reconnect backoff range
|
|
28
|
+
"_note": "MQTT reconnect exponential backoff range."
|
|
35
29
|
},
|
|
36
|
-
|
|
37
30
|
"e2ee": {
|
|
38
|
-
"enable":
|
|
31
|
+
"enable": false,
|
|
39
32
|
"saveType": "memory",
|
|
40
33
|
"devicePath": "./data/e2ee-device.json",
|
|
41
34
|
"autoReconnect": true,
|
|
42
35
|
"logLevel": "none",
|
|
43
|
-
"_note": "E2EE Labyrinth bridge.
|
|
36
|
+
"_note": "E2EE Labyrinth bridge. Requires native build/messagix binary. Set enable:true only if binary is present."
|
|
44
37
|
},
|
|
45
|
-
|
|
46
|
-
"inbox": {
|
|
47
|
-
"enabled": true,
|
|
48
|
-
"replyToInbox": true,
|
|
49
|
-
"_note": "Inbox always active. replyToInbox=true routes bot reply to bot inbox (not group). Required for bot message listening."
|
|
50
|
-
},
|
|
51
|
-
|
|
52
38
|
"facebookProtection": {
|
|
53
39
|
"enabled": true,
|
|
54
40
|
"blockAutomationDetection": true,
|
|
55
41
|
"humanLikeDelay": true,
|
|
56
|
-
"_note": "
|
|
42
|
+
"_note": "Sets online=false, updatePresence=false. Adds 50-200ms micro-delay. Reduces account restriction risk."
|
|
43
|
+
},
|
|
44
|
+
"inbox": {
|
|
45
|
+
"enabled": true,
|
|
46
|
+
"replyToInbox": true,
|
|
47
|
+
"_note": "Bot replies in both inbox/DM and group threads."
|
|
57
48
|
},
|
|
58
|
-
|
|
59
49
|
"enableTypingIndicator": false,
|
|
60
50
|
"typingDuration": 4000
|
|
61
51
|
}
|
package/examples/basic-bot.js
CHANGED
|
@@ -1,118 +1,122 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* toru-fca v1.0
|
|
2
|
+
* toru-fca v1.1.0 — basic-bot.js (GoatBot compatible example)
|
|
3
3
|
*
|
|
4
|
-
* Features
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* - Graceful shutdown
|
|
12
|
-
* - GoatBot compatible
|
|
4
|
+
* Features:
|
|
5
|
+
* ✅ Cookie login — browser logout prevention
|
|
6
|
+
* ✅ Inbox + Group — bot replies in both DM and group threads
|
|
7
|
+
* ✅ Session guard — appstate auto-saved every 3 minutes
|
|
8
|
+
* ✅ Anti-logout — fb_dtsg refreshed every 20 minutes
|
|
9
|
+
* ✅ Facebook protection — automation detection guard
|
|
10
|
+
* ✅ Graceful shutdown — cleans up timers, MQTT, and session on exit
|
|
13
11
|
*/
|
|
14
12
|
|
|
15
13
|
"use strict";
|
|
16
14
|
|
|
17
|
-
const login = require("../index.js");
|
|
15
|
+
const login = require("../index.js");
|
|
18
16
|
const fs = require("fs");
|
|
19
17
|
const path = require("path");
|
|
20
18
|
|
|
21
|
-
// ─── Config ──────────────────────────────────────────────────────────────────
|
|
22
19
|
const APPSTATE_FILE = path.join(__dirname, "..", "appstate.json");
|
|
23
20
|
|
|
24
|
-
//
|
|
21
|
+
// ── Load appState (Cookie) ────────────────────────────────────────────────────
|
|
25
22
|
let appState;
|
|
26
23
|
try {
|
|
27
24
|
appState = JSON.parse(fs.readFileSync(APPSTATE_FILE, "utf8"));
|
|
28
25
|
} catch (err) {
|
|
29
|
-
console.error("[toru-fca] Could not load appstate.json:", err.message);
|
|
30
|
-
console.error(" Place your Facebook cookie appstate in: " + APPSTATE_FILE);
|
|
26
|
+
console.error("[toru-fca] \u274C Could not load appstate.json:", err.message);
|
|
31
27
|
process.exit(1);
|
|
32
28
|
}
|
|
33
29
|
|
|
34
|
-
//
|
|
30
|
+
// ── Login options (GoatBot compatible) ───────────────────────────────────────
|
|
35
31
|
const options = {
|
|
36
|
-
listenEvents
|
|
37
|
-
selfListen
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
listenEvents : true,
|
|
33
|
+
selfListen : false, // bot does not listen to its own messages
|
|
34
|
+
autoReconnect : true,
|
|
35
|
+
online : false, // reduces automation detection risk
|
|
36
|
+
updatePresence : false,
|
|
37
|
+
forceLogin : false, // browser logout protection
|
|
38
|
+
autoMarkRead : false,
|
|
41
39
|
autoMarkDelivery: false,
|
|
42
|
-
|
|
43
|
-
online : false, // offline mode — automation detection এড়ায়
|
|
44
|
-
logLevel : "verbose"
|
|
40
|
+
logLevel : "verbose"
|
|
45
41
|
};
|
|
46
42
|
|
|
47
|
-
//
|
|
43
|
+
// ── Login ─────────────────────────────────────────────────────────────────────
|
|
48
44
|
login({ appState }, options, function (err, api) {
|
|
49
45
|
if (err) {
|
|
50
|
-
console.error("[toru-fca]
|
|
46
|
+
console.error("[toru-fca] \u274C Login failed:", err.message || err);
|
|
51
47
|
process.exit(1);
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
const
|
|
55
|
-
console.log("[toru-fca]
|
|
50
|
+
const botID = api.getCurrentUserID ? api.getCurrentUserID() : "unknown";
|
|
51
|
+
console.log("[toru-fca] \u2705 Bot running as:", botID);
|
|
56
52
|
|
|
57
|
-
//
|
|
58
|
-
api.sessionGuard(APPSTATE_FILE, {
|
|
59
|
-
interval: 3 * 60 * 1000, // 3 min
|
|
60
|
-
debounce: 20 * 1000, // 20 sec debounce
|
|
61
|
-
backup: true // .bak file রাখে
|
|
62
|
-
});
|
|
63
|
-
console.log("[toru-fca] 🔒 Session guard active — auto-saving appstate.");
|
|
64
|
-
|
|
65
|
-
// ── E2EE Bridge (config.json e e2ee.enable:true থাকলে) ─────────────────
|
|
66
|
-
if (api.connectE2EE) {
|
|
67
|
-
api.connectE2EE(function (err, event) {
|
|
68
|
-
if (err) { console.error("[E2EE]", err.message || err); return; }
|
|
69
|
-
if (event && event.type) console.log("[E2EE]", event.type);
|
|
70
|
-
}).catch(e => console.error("[E2EE] connect failed:", e.message));
|
|
71
|
-
}
|
|
53
|
+
// Session guard — auto-saves appstate every 3 minutes
|
|
54
|
+
const guard = api.sessionGuard(APPSTATE_FILE, { interval: 3 * 60 * 1000, backup: true });
|
|
72
55
|
|
|
73
|
-
// ── Main
|
|
56
|
+
// ── Main listener ─────────────────────────────────────────────────────────
|
|
74
57
|
api.listenMqtt(function (err, event) {
|
|
75
58
|
if (err) {
|
|
76
59
|
console.error("[listen]", err);
|
|
77
60
|
return;
|
|
78
61
|
}
|
|
79
62
|
|
|
80
|
-
//
|
|
63
|
+
// Respond in both Group and Inbox/DM threads
|
|
81
64
|
if (event.type === "message") {
|
|
82
|
-
const { threadID, senderID, body, isGroup
|
|
83
|
-
const
|
|
84
|
-
console.log(
|
|
65
|
+
const { threadID, senderID, body, isGroup } = event;
|
|
66
|
+
const tag = isGroup ? "[Group]" : "[Inbox/DM]";
|
|
67
|
+
console.log(tag, senderID + ":", body);
|
|
85
68
|
|
|
86
|
-
//
|
|
69
|
+
// Simple echo bot
|
|
87
70
|
if (body && body.toLowerCase() === "ping") {
|
|
88
|
-
api.sendMessage("pong!
|
|
89
|
-
if (err) console.error("[sendMessage]", err);
|
|
90
|
-
});
|
|
71
|
+
api.sendMessage("\uD83C\uDFD3 pong! \u2014 toru-fca v1.1.0", threadID);
|
|
91
72
|
}
|
|
92
73
|
}
|
|
93
74
|
|
|
94
|
-
//
|
|
95
|
-
if (event.type === "
|
|
96
|
-
console.log(
|
|
75
|
+
// Reaction events
|
|
76
|
+
if (event.type === "message_reaction") {
|
|
77
|
+
console.log(
|
|
78
|
+
"[Reaction]",
|
|
79
|
+
event.senderID,
|
|
80
|
+
event.off ? "removed reaction" : "reacted:",
|
|
81
|
+
event.reaction
|
|
82
|
+
);
|
|
97
83
|
}
|
|
98
|
-
});
|
|
99
84
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (!health.mqtt || !health.mqtt.connected) {
|
|
104
|
-
console.warn("[health] MQTT not connected — autoReconnect will restore.");
|
|
85
|
+
// Group events (join, leave, name change, etc.)
|
|
86
|
+
if (event.type === "event") {
|
|
87
|
+
console.log("[Event]", event.logMessageType || event.type);
|
|
105
88
|
}
|
|
106
|
-
}
|
|
89
|
+
});
|
|
107
90
|
|
|
108
|
-
// ── Graceful shutdown
|
|
91
|
+
// ── Graceful shutdown ─────────────────────────────────────────────────────
|
|
92
|
+
var _shutdownCalled = false;
|
|
109
93
|
function shutdown() {
|
|
94
|
+
if (_shutdownCalled) return;
|
|
95
|
+
_shutdownCalled = true;
|
|
110
96
|
console.log("[toru-fca] Shutting down...");
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (api.
|
|
114
|
-
|
|
97
|
+
|
|
98
|
+
// Stop anti-logout timer
|
|
99
|
+
if (api.stopAntiLogout) api.stopAntiLogout();
|
|
100
|
+
|
|
101
|
+
// Stop session guard timer
|
|
102
|
+
if (guard && typeof guard.stop === "function") guard.stop();
|
|
103
|
+
|
|
104
|
+
// FIX v1.1.0: use api.stopListening() (from MessageEmitter) which correctly
|
|
105
|
+
// clears the dead-connection watchdog AND the MQTT client.
|
|
106
|
+
// api.stopListenMqtt() also works but stopListening() is preferred here
|
|
107
|
+
// because it was returned by the listen() call and has direct scope access.
|
|
108
|
+
if (typeof api.stopListening === "function") {
|
|
109
|
+
api.stopListening(function() {
|
|
110
|
+
setTimeout(function() { process.exit(0); }, 500);
|
|
111
|
+
});
|
|
112
|
+
} else if (api.stopListenMqtt) {
|
|
113
|
+
api.stopListenMqtt();
|
|
114
|
+
setTimeout(function() { process.exit(0); }, 500);
|
|
115
|
+
} else {
|
|
116
|
+
setTimeout(function() { process.exit(0); }, 500);
|
|
117
|
+
}
|
|
115
118
|
}
|
|
119
|
+
|
|
116
120
|
process.once("SIGINT", shutdown);
|
|
117
121
|
process.once("SIGTERM", shutdown);
|
|
118
122
|
});
|
package/fca-config.example.json
CHANGED
|
@@ -1,56 +1,66 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "
|
|
2
|
+
"_comment": "toru-fca v1.1.0 — Example Config. Copy to config.json and customize.",
|
|
3
3
|
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
|
|
6
6
|
"automation": {
|
|
7
7
|
"enabled": false,
|
|
8
|
-
"_comment": "KEEP FALSE. Enabling this allows background task automation
|
|
8
|
+
"_comment": "KEEP FALSE. Enabling this allows background task automation. Keep false to protect your account."
|
|
9
9
|
},
|
|
10
10
|
|
|
11
11
|
"inbox": {
|
|
12
12
|
"enabled": true,
|
|
13
|
-
"
|
|
13
|
+
"replyToInbox": true,
|
|
14
|
+
"_comment": "Bot replies in both inbox/DM and group threads."
|
|
14
15
|
},
|
|
15
16
|
|
|
16
17
|
"smooth": {
|
|
17
18
|
"enabled": true,
|
|
18
|
-
"messageDelay":
|
|
19
|
-
"_comment": "
|
|
19
|
+
"messageDelay": 300,
|
|
20
|
+
"_comment": "Adds a short delay before sending. Combines with human jitter (50-200ms) when facebookProtection is enabled."
|
|
20
21
|
},
|
|
21
22
|
|
|
22
23
|
"connection": {
|
|
23
|
-
"minDelayMs":
|
|
24
|
-
"maxDelayMs":
|
|
25
|
-
"_comment": "
|
|
24
|
+
"minDelayMs": 3000,
|
|
25
|
+
"maxDelayMs": 60000,
|
|
26
|
+
"_comment": "MQTT reconnect exponential backoff range in milliseconds."
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
"antiLogout": {
|
|
30
|
+
"enabled": true,
|
|
31
|
+
"refreshIntervalMs": 1200000,
|
|
32
|
+
"_comment": "Refreshes fb_dtsg CSRF token every 20 minutes to keep session alive."
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
"sessionGuard": {
|
|
36
|
+
"enabled": true,
|
|
37
|
+
"intervalMs": 180000,
|
|
38
|
+
"debounceMs": 20000,
|
|
39
|
+
"backupEnabled": true,
|
|
40
|
+
"_comment": "Auto-saves appstate.json every 3 minutes. Keeps a .bak backup. Extends cookie expiry."
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
"facebookProtection": {
|
|
44
|
+
"enabled": true,
|
|
45
|
+
"blockAutomationDetection": true,
|
|
46
|
+
"humanLikeDelay": true,
|
|
47
|
+
"_comment": "Sets online=false, updatePresence=false. Adds 50-200ms human jitter on sendMessage."
|
|
26
48
|
},
|
|
27
49
|
|
|
28
50
|
"e2ee": {
|
|
29
|
-
"enable":
|
|
51
|
+
"enable": false,
|
|
30
52
|
"saveType": "memory",
|
|
31
53
|
"devicePath": "./data/e2ee-device.json",
|
|
32
54
|
"autoReconnect": true,
|
|
33
55
|
"logLevel": "none",
|
|
34
|
-
"
|
|
35
|
-
"enable": "Set to true to activate E2EE Labyrinth bridge.",
|
|
36
|
-
"saveType": "'memory' = keys lost on restart (safe). 'path' = persist keys to disk (survives restart).",
|
|
37
|
-
"devicePath": "Used only when saveType='path'. Relative to your bot's CWD.",
|
|
38
|
-
"autoReconnect": "E2EE bridge auto-reconnects on disconnect.",
|
|
39
|
-
"logLevel": "'none' = silent. 'error'/'warn'/'info' = verbose."
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
"imgKeys": {
|
|
44
|
-
"imgbb": null,
|
|
45
|
-
"ik_pub": null,
|
|
46
|
-
"ik_priv": null,
|
|
47
|
-
"_comment": "Optional image hosting API keys. Leave null to disable image hosting."
|
|
56
|
+
"_comment": "E2EE Labyrinth bridge. REQUIRES native binary in build/ directory. Set enable:true only when binary is present. saveType: 'memory' (keys lost on restart) or 'path' (persisted to disk)."
|
|
48
57
|
},
|
|
49
58
|
|
|
50
59
|
"enableTypingIndicator": false,
|
|
51
60
|
"typingDuration": 4000,
|
|
52
61
|
|
|
53
62
|
"_listenOptions": {
|
|
63
|
+
"_comment": "These go in the options param of login(), not in this file. Shown here for reference.",
|
|
54
64
|
"selfListen": true,
|
|
55
65
|
"listenEvents": true,
|
|
56
66
|
"listenTyping": false,
|
|
@@ -58,7 +68,6 @@
|
|
|
58
68
|
"autoMarkDelivery": false,
|
|
59
69
|
"autoMarkRead": false,
|
|
60
70
|
"autoReconnect": true,
|
|
61
|
-
"online": false
|
|
62
|
-
"_comment": "These go in the options param of login(), not in this file. Shown here for reference."
|
|
71
|
+
"online": false
|
|
63
72
|
}
|
|
64
73
|
}
|