@eryxenx/fca 1.0.6 → 1.0.8

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 CHANGED
@@ -1,51 +1,51 @@
1
1
  <div align="center">
2
2
 
3
- # 💬 @eryxenx/fca
3
+ # 💬 @eryxenx/fca
4
4
 
5
5
  **Unofficial Facebook Messenger Bot API for Node.js**
6
- NEXCA MQTT · Signal Protocol E2EE (mautrix-go powered) · sessionGuard · 90+ API Methods · Zero TypeScript
6
+ NEXCA MQTT · Signal Protocol E2EE (mautrix-go powered) · sessionGuard · 90+ API Methods · Zero TypeScript
7
7
 
8
8
  [![npm](https://img.shields.io/npm/v/@eryxenx/fca?color=blue)](https://www.npmjs.com/package/@eryxenx/fca)
9
9
  [![license](https://img.shields.io/badge/license-MIT-green)](./LICENSE-MIT)
10
10
  [![node](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
11
11
 
12
- [Features](#-features) • [Installation](#-installation) • [Quick Start](#-quick-start) • [E2EE](#-e2ee--encrypted-conversations) • [sessionGuard](#-sessionguard) • [sendBroadcast](#-sendbroadcast) • [API Reference](#-api-reference)
12
+ [Features](#-features) [Installation](#-installation) [Quick Start](#-quick-start) [E2EE](#-e2ee--encrypted-conversations) [sessionGuard](#-sessionguard) [sendBroadcast](#-sendbroadcast) [API Reference](#-api-reference)
13
13
 
14
14
  </div>
15
15
 
16
16
  ---
17
17
 
18
- ## âš¡ Why @eryxenx/fca?
18
+ ## Why @eryxenx/fca?
19
19
 
20
- - ✅ **NEXCA MQTT** — stable connection core, autoReconnect, jitter
21
- - ✅ **Signal Protocol E2EE** — Facebook real encrypted conversations support. Reliable media/reaction/unsend delivery is powered by a bundled **mautrix-go / mautrix-meta** native engine (© Tulir Asokan, MPL-2.0) — NEXCA's own hand-written E2EE encoder produced protocol-valid messages that Facebook accepted but Messenger clients failed to render for attachments/reactions/unsend, so this fork routes those through the native engine instead. Text messaging still uses the original engine (already reliable). The native engine is opt-in (`FCA_E2EE_NATIVE=1`) — see [Native media engine](#native-media-engine).
22
- - ✅ **sessionGuard** — appstate corruption and silent logout protection, auto-backup
23
- - ✅ **sendBroadcast** — rate-limited multi-thread broadcast
24
- - ✅ **Fixed MQTT subscribe race condition** — no more "Connection refused: No subscription existed"
25
- - ✅ **`isActiveClient()` guard** — stale MQTT client events no longer processed
26
- - ✅ **`connectTimeout` extended** — no premature logout on slow networks
27
- - ✅ **autoReconnect** — auto-reconnect on connection drop
28
- - ✅ **GoatBot compatible** — all API signatures unchanged (`threadID` optional etc.)
29
- - ✅ **90+ API methods** — sendMessage, editMessage, setMessageReaction, getThreadInfo and more
20
+ - **NEXCA MQTT** stable connection core, autoReconnect, jitter
21
+ - **Signal Protocol E2EE** Facebook real encrypted conversations support. Reliable media/reaction/unsend delivery is powered by a bundled **mautrix-go / mautrix-meta** native engine (© Tulir Asokan, MPL-2.0) NEXCA's own hand-written E2EE encoder produced protocol-valid messages that Facebook accepted but Messenger clients failed to render for attachments/reactions/unsend, so this fork routes those through the native engine instead. Text messaging still uses the original engine (already reliable). The native engine is opt-in (`FCA_E2EE_NATIVE=1`) see [Native media engine](#native-media-engine).
22
+ - **sessionGuard** appstate corruption and silent logout protection, auto-backup
23
+ - **sendBroadcast** rate-limited multi-thread broadcast
24
+ - **Fixed MQTT subscribe race condition** no more "Connection refused: No subscription existed"
25
+ - **`isActiveClient()` guard** stale MQTT client events no longer processed
26
+ - **`connectTimeout` extended** no premature logout on slow networks
27
+ - **autoReconnect** auto-reconnect on connection drop
28
+ - **GoatBot compatible** all API signatures unchanged (`threadID` optional etc.)
29
+ - **90+ API methods** sendMessage, editMessage, setMessageReaction, getThreadInfo and more
30
30
 
31
31
  ---
32
32
 
33
- ## ✨ Features
33
+ ## Features
34
34
 
35
- - ✅ Full Messenger API — messages, reactions, attachments, stickers, polls, pins
36
- - ✅ NEXCA MQTT — stable connection, autoReconnect, jitter, isActiveClient guard
37
- - ✅ E2EE — Signal Protocol encrypted threads, auto-connects after login (listenE2EE, e2ee.*)
38
- - ✅ E2EE media engine — native mautrix-go engine (opt-in via `FCA_E2EE_NATIVE=1`) for reliable image/video/audio/document sending, reactions, and unsend in encrypted threads (falls back to the built-in JS engine automatically)
39
- - ✅ E2EE incoming media — attachments on encrypted messages (including replies) are auto-decrypted and served locally, so existing commands work unmodified
40
- - ✅ sessionGuard — appstate auto-save, corruption guard, .bak backup
41
- - ✅ sendBroadcast — parallel/sequential multi-thread sending with rate limit
42
- - ✅ MessengerBot — Discord.js/Telegraf style (.command, .hears, .launch)
43
- - ✅ createFcaClient — namespaced facade (client.messages, client.threads etc.)
44
- - ✅ GoatBot / Mirai compatible — drop-in replacement
35
+ - Full Messenger API messages, reactions, attachments, stickers, polls, pins
36
+ - NEXCA MQTT stable connection, autoReconnect, jitter, isActiveClient guard
37
+ - E2EE Signal Protocol encrypted threads, auto-connects after login (listenE2EE, e2ee.*)
38
+ - E2EE media engine native mautrix-go engine (opt-in via `FCA_E2EE_NATIVE=1`) for reliable image/video/audio/document sending, reactions, and unsend in encrypted threads (falls back to the built-in JS engine automatically)
39
+ - E2EE incoming media attachments on encrypted messages (including replies) are auto-decrypted and served locally, so existing commands work unmodified
40
+ - sessionGuard appstate auto-save, corruption guard, .bak backup
41
+ - sendBroadcast parallel/sequential multi-thread sending with rate limit
42
+ - MessengerBot Discord.js/Telegraf style (.command, .hears, .launch)
43
+ - createFcaClient namespaced facade (client.messages, client.threads etc.)
44
+ - GoatBot / Mirai compatible drop-in replacement
45
45
 
46
46
  ---
47
47
 
48
- ## 📦 Installation
48
+ ## 📦 Installation
49
49
 
50
50
  ```bash
51
51
  npm install @eryxenx/fca
@@ -53,13 +53,13 @@ npm install @eryxenx/fca
53
53
 
54
54
  Node.js >= 18 required.
55
55
 
56
- The E2EE media engine ships a precompiled native binary (`src/api/socket/e2ee/native/build/`) for **Linux (.so)** and **Windows (.dll)**, loaded via the `koffi` FFI. `npm install` pulls in `koffi` and `yumi-json-bigint` automatically — no extra setup needed. Note the native engine only runs when `FCA_E2EE_NATIVE=1` is set (see [Native media engine](#native-media-engine)); on platforms other than Linux/Windows x64 (e.g. macOS, ARM) the binary fails to load and E2EE media/reactions/unsend automatically fall back to the built-in JS engine (text messaging is unaffected either way).
56
+ The E2EE media engine ships a precompiled native binary (`src/api/socket/e2ee/native/build/`) for **Linux (.so)** and **Windows (.dll)**, loaded via the `koffi` FFI. `npm install` pulls in `koffi` and `yumi-json-bigint` automatically no extra setup needed. Note the native engine only runs when `FCA_E2EE_NATIVE=1` is set (see [Native media engine](#native-media-engine)); on platforms other than Linux/Windows x64 (e.g. macOS, ARM) the binary fails to load and E2EE media/reactions/unsend automatically fall back to the built-in JS engine (text messaging is unaffected either way).
57
57
 
58
58
  The E2EE socket's User-Agent defaults to desktop Windows Chrome 139 to match the rest of the client; override with `FCA_E2EE_UA` if you run with a different UA.
59
59
 
60
60
  ---
61
61
 
62
- ## 🚀 Quick Start
62
+ ## 🚀 Quick Start
63
63
 
64
64
  ### Classic (GoatBot compatible)
65
65
 
@@ -83,14 +83,14 @@ login({ appState: require("./account.json") }, { listenEvents: true }, (err, api
83
83
 
84
84
  ### With E2EE (regular + encrypted threads)
85
85
 
86
- `connectE2EE()` runs **automatically** right after login — you never call it yourself. Calling it again in your own code opens a second, racing connection on the same bridge. Just check `api.e2ee.isConnected()` if you need to know the status.
86
+ `connectE2EE()` runs **automatically** right after login you never call it yourself. Calling it again in your own code opens a second, racing connection on the same bridge. Just check `api.e2ee.isConnected()` if you need to know the status.
87
87
 
88
88
  ```javascript
89
89
  login({ appState: require("./account.json") }, { listenEvents: true }, (err, api) => {
90
90
  if (err) throw err;
91
91
 
92
92
  api.sessionGuard("./account.json");
93
- // E2EE is already connecting in the background here — do not call connectE2EE().
93
+ // E2EE is already connecting in the background here do not call connectE2EE().
94
94
 
95
95
  api.listenE2EE((err, event) => {
96
96
  if (err) throw err;
@@ -118,35 +118,35 @@ login({ appState }, options, (err, api) => {
118
118
  debounce: 30 * 1000
119
119
  });
120
120
 
121
- // Nothing else needed for E2EE — it auto-connects inside @eryxenx/fca
121
+ // Nothing else needed for E2EE it auto-connects inside @eryxenx/fca
122
122
  // right after login. Do NOT call api.connectE2EE() here.
123
123
 
124
124
  api.listenMqtt(callback);
125
125
  });
126
126
  ```
127
127
 
128
- **This is the only setup needed — nothing else in `login.js` (or anywhere in GoatBot) has to change for E2EE.** Once login succeeds, `api.e2ee` is already connecting in the background, and every existing GoatBot call — `api.sendMessage(...)` (with or without `attachment`), `api.setMessageReaction(...)`, `api.unsendMessage(...)` — automatically detects when a thread is an encrypted DM and routes through the E2EE engine internally. Commands don't need to know or care whether a thread is encrypted; they call the same functions either way.
128
+ **This is the only setup needed nothing else in `login.js` (or anywhere in GoatBot) has to change for E2EE.** Once login succeeds, `api.e2ee` is already connecting in the background, and every existing GoatBot call `api.sendMessage(...)` (with or without `attachment`), `api.setMessageReaction(...)`, `api.unsendMessage(...)` automatically detects when a thread is an encrypted DM and routes through the E2EE engine internally. Commands don't need to know or care whether a thread is encrypted; they call the same functions either way.
129
129
 
130
130
  #### Adding this to your own login.js
131
131
 
132
- Your bot's actual `login.js` will look different from the snippet above (dashboard setup, database sync, custom logging, etc.) — you're not replacing the file, just adding/checking two things inside your existing login callback:
132
+ Your bot's actual `login.js` will look different from the snippet above (dashboard setup, database sync, custom logging, etc.) you're not replacing the file, just adding/checking two things inside your existing login callback:
133
133
 
134
134
  | Step | What to do |
135
135
  |---|---|
136
136
  | 1. sessionGuard | Add `api.sessionGuard(accountPath, { interval, debounce })` once, right after login succeeds, if it's not already there. |
137
- | 2. E2EE | Nothing to add — it's automatic. If your `login.js` already has an explicit `await api.connectE2EE()` call, **delete it** — it's redundant with the automatic connection and the two can race. |
137
+ | 2. E2EE | Nothing to add it's automatic. If your `login.js` already has an explicit `await api.connectE2EE()` call, **delete it** it's redundant with the automatic connection and the two can race. |
138
138
 
139
- To check whether E2EE is already wired into your bot, search for `e2ee.isConnected` in your `login.js` — if it's there, setup is already done.
139
+ To check whether E2EE is already wired into your bot, search for `e2ee.isConnected` in your `login.js` if it's there, setup is already done.
140
140
 
141
141
  ---
142
142
 
143
- ## 🔐 E2EE — Encrypted Conversations
143
+ ## 🔐 E2EE Encrypted Conversations
144
144
 
145
- Uses Facebook's real Signal Protocol infrastructure — same as the official Messenger app.
145
+ Uses Facebook's real Signal Protocol infrastructure same as the official Messenger app.
146
146
 
147
147
  ### Setup
148
148
 
149
- E2EE connects automatically after login (creating `.nexca/e2ee_device.json` on first run) — there's nothing to call manually. Just check status once connected:
149
+ E2EE connects automatically after login (creating `.nexca/e2ee_device.json` on first run) there's nothing to call manually. Just check status once connected:
150
150
 
151
151
  ```javascript
152
152
  console.log(api.e2ee.isConnected()); // true (may need a moment right after login)
@@ -171,7 +171,7 @@ api.listenE2EE((err, event) => {
171
171
  ```javascript
172
172
  await api.e2ee.sendMessage(threadID, "Hello!");
173
173
  await api.e2ee.sendMessage(threadID, { body: "Photo!", attachment: fs.createReadStream("photo.jpg") });
174
- await api.e2ee.sendReaction(threadID, messageID, "❤️");
174
+ await api.e2ee.sendReaction(threadID, messageID, "❤️");
175
175
  await api.e2ee.sendTyping(threadID, true);
176
176
  await api.e2ee.unsendMessage(messageID, threadID);
177
177
  await api.e2ee.editMessage(threadID, messageID, "Updated!");
@@ -181,20 +181,20 @@ await api.e2ee.disconnect();
181
181
 
182
182
  ### Native media engine
183
183
 
184
- Sending image/video/audio/document attachments, reactions, and unsend in E2EE threads can be routed through a precompiled **mautrix-go / mautrix-meta** binary (`src/api/socket/e2ee/native/`) via the `koffi` FFI, instead of this project's own hand-written Signal Protocol encoder. This project's own encoder builds protocol-valid messages that Facebook's servers accept, but real Messenger clients silently fail to render — the native engine avoids that entirely.
184
+ Sending image/video/audio/document attachments, reactions, and unsend in E2EE threads can be routed through a precompiled **mautrix-go / mautrix-meta** binary (`src/api/socket/e2ee/native/`) via the `koffi` FFI, instead of this project's own hand-written Signal Protocol encoder. This project's own encoder builds protocol-valid messages that Facebook's servers accept, but real Messenger clients silently fail to render the native engine avoids that entirely.
185
185
 
186
- **⚠️ The native engine is DISABLED by default.** Both the vendored JS engine and the native engine register with Facebook as the *same* E2EE device (same device id), and Facebook's server keeps only one live E2EE socket per device — so when both run at once they kick each other off in a loop (a fresh handshake every few seconds, which looks like automated behavior to Facebook). The vendored JS engine is therefore given exclusive use of the socket unless you opt in:
186
+ **⚠️ The native engine is DISABLED by default.** Both the vendored JS engine and the native engine register with Facebook as the *same* E2EE device (same device id), and Facebook's server keeps only one live E2EE socket per device so when both run at once they kick each other off in a loop (a fresh handshake every few seconds, which looks like automated behavior to Facebook). The vendored JS engine is therefore given exclusive use of the socket unless you opt in:
187
187
 
188
- - Set `FCA_E2EE_NATIVE=1` in the environment to enable the native engine. With it on you get E2EE group text receive/send, native media sends, native reactions/unsend/edit/markRead — but you also accept the device-kick socket churn described above (the JS engine reconnects with exponential backoff, so it stays usable, just noisy).
188
+ - Set `FCA_E2EE_NATIVE=1` in the environment to enable the native engine. With it on you get E2EE group text receive/send, native media sends, native reactions/unsend/edit/markRead but you also accept the device-kick socket churn described above (the JS engine reconnects with exponential backoff, so it stays usable, just noisy).
189
189
  - With it off (default): E2EE DM text + DM media work through the vendored engine. E2EE **group** send/receive, and E2EE markRead, are unavailable; reactions/unsend/edit in E2EE DMs fall back to the vendored engine.
190
190
 
191
191
  - Text messages always use @eryxenx/fca's own E2EE engine (already reliable, no native dependency).
192
192
  - If the native engine fails to load or a send fails (unsupported platform, binary mismatch, etc.), @eryxenx/fca automatically falls back to its own engine and logs `[native-media] ... falling back to legacy vendor engine`.
193
- - See `src/api/socket/e2ee/native/NOTICE.md` for the required MPL-2.0 attribution to the upstream mautrix-go/mautrix-meta project (© Tulir Asokan and contributors) — do not remove it if you redistribute this fork.
193
+ - See `src/api/socket/e2ee/native/NOTICE.md` for the required MPL-2.0 attribution to the upstream mautrix-go/mautrix-meta project (© Tulir Asokan and contributors) do not remove it if you redistribute this fork.
194
194
 
195
195
  ### Receiving E2EE media (attachments & replies)
196
196
 
197
- Incoming encrypted image/video/audio/document messages are automatically decrypted and served over a short-lived local URL (`http://127.0.0.1:<port>/<token>`, 15 min TTL), and populated into `event.attachments` / `event.messageReply.attachments` — same shape as normal (non-E2EE) attachments. Reply-based commands (e.g. "reply to an image with `/imgur`") work the same in E2EE threads as in normal ones, no command code changes needed.
197
+ Incoming encrypted image/video/audio/document messages are automatically decrypted and served over a short-lived local URL (`http://127.0.0.1:<port>/<token>`, 15 min TTL), and populated into `event.attachments` / `event.messageReply.attachments` same shape as normal (non-E2EE) attachments. Reply-based commands (e.g. "reply to an image with `/imgur`") work the same in E2EE threads as in normal ones, no command code changes needed.
198
198
 
199
199
  ```javascript
200
200
  if (event.messageReply && event.messageReply.attachments.length) {
@@ -202,11 +202,11 @@ if (event.messageReply && event.messageReply.attachments.length) {
202
202
  }
203
203
  ```
204
204
 
205
- If media resolution fails, check the console for `[media-resolve]` / `[media-decode]` errors — this usually means the CDN download host (`FB_E2EE_MEDIA_DOWNLOAD_HOST` env var, default `rupload.facebook.com`) needs adjusting for your account/region.
205
+ If media resolution fails, check the console for `[media-resolve]` / `[media-decode]` errors this usually means the CDN download host (`FB_E2EE_MEDIA_DOWNLOAD_HOST` env var, default `rupload.facebook.com`) needs adjusting for your account/region.
206
206
 
207
207
  ---
208
208
 
209
- ## 🛡️ sessionGuard
209
+ ## 🛡️ sessionGuard
210
210
 
211
211
  Protects your appstate from corruption and silent logouts.
212
212
 
@@ -223,8 +223,8 @@ api.sessionGuard("./account.json", {
223
223
  What it does:
224
224
  - Auto-saves appstate every N minutes
225
225
  - Saves after every successful sendMessage (debounced)
226
- - Corruption guard — never overwrites a larger appstate with a smaller one
227
- - Auto-backup — writes `.bak` before every overwrite
226
+ - Corruption guard never overwrites a larger appstate with a smaller one
227
+ - Auto-backup writes `.bak` before every overwrite
228
228
 
229
229
  ```javascript
230
230
  api.saveSession(); // force save now
@@ -234,7 +234,7 @@ api.stopSessionGuard(); // stop the timer
234
234
 
235
235
  ---
236
236
 
237
- ## 📡 sendBroadcast
237
+ ## 📡 sendBroadcast
238
238
 
239
239
  Rate-limited multi-thread broadcast.
240
240
 
@@ -255,7 +255,7 @@ console.log(result.sent.length + "/" + result.total + " delivered");
255
255
 
256
256
  ---
257
257
 
258
- ## 🤖 MessengerBot
258
+ ## 🤖 MessengerBot
259
259
 
260
260
  Discord.js/Telegraf style high-level bot class.
261
261
 
@@ -267,8 +267,8 @@ const bot = await createMessengerBot(
267
267
  { commandPrefix: "/", stopOnSignals: true }
268
268
  );
269
269
 
270
- bot.command("ping", async ctx => await ctx.replyAsync("pong 🏓"));
271
- bot.hears(/hello/i, async ctx => await ctx.replyAsync("Hi! 👋"));
270
+ bot.command("ping", async ctx => await ctx.replyAsync("pong 🏓"));
271
+ bot.hears(/hello/i, async ctx => await ctx.replyAsync("Hi! 👋"));
272
272
  bot.on("messageCreate", event => console.log(event.body));
273
273
 
274
274
  await bot.launch({ stopOnSignals: true });
@@ -276,7 +276,7 @@ await bot.launch({ stopOnSignals: true });
276
276
 
277
277
  ---
278
278
 
279
- ## 🎯 createFcaClient
279
+ ## 🎯 createFcaClient
280
280
 
281
281
  Namespaced facade grouping all API methods by domain.
282
282
 
@@ -285,7 +285,7 @@ const { createFcaClient } = require("@eryxenx/fca");
285
285
  const client = createFcaClient(api);
286
286
 
287
287
  await client.messages.send("Hello!", threadID);
288
- await client.messages.react("❤️", messageID, threadID);
288
+ await client.messages.react("❤️", messageID, threadID);
289
289
  await client.threads.getInfo(threadID);
290
290
  await client.users.getInfo(userID);
291
291
  await client.account.refreshDtsg();
@@ -293,7 +293,7 @@ await client.account.refreshDtsg();
293
293
 
294
294
  ---
295
295
 
296
- ## 📖 API Reference
296
+ ## 📖 API Reference
297
297
 
298
298
  ### Sending Messages
299
299
 
@@ -320,7 +320,7 @@ api.shareContact("Meet my friend!", userID, threadID);
320
320
  api.editMessage("Updated text", messageID);
321
321
  api.unsendMessage(messageID);
322
322
  api.deleteMessage([messageID]);
323
- api.setMessageReaction("😍", messageID, threadID); // threadID optional
323
+ api.setMessageReaction("😍", messageID, threadID); // threadID optional
324
324
  api.setMessageReaction("", messageID); // remove reaction
325
325
  api.getMessage(threadID, messageID);
326
326
  api.forwardAttachment(attachmentID, [userID]);
@@ -356,7 +356,7 @@ api.searchForThread("query");
356
356
  ```javascript
357
357
  api.setTitle("New Name", threadID);
358
358
  api.changeThreadColor("#0084FF", threadID);
359
- api.changeThreadEmoji("🔥", threadID);
359
+ api.changeThreadEmoji("🔥", threadID);
360
360
  api.changeNickname("The Boss", threadID, userID);
361
361
  api.changeGroupImage(fs.createReadStream("group.jpg"), threadID);
362
362
  api.changeAdminStatus(threadID, userID, true);
@@ -420,7 +420,7 @@ api.uploadImageToImgbb(imageUrl);
420
420
 
421
421
  ---
422
422
 
423
- ## 📋 Login Options
423
+ ## 📋 Login Options
424
424
 
425
425
  | Option | Type | Default | Description |
426
426
  |--------|------|---------|-------------|
@@ -433,23 +433,23 @@ api.uploadImageToImgbb(imageUrl);
433
433
  | `autoReconnect` | boolean | true | Auto-reconnect MQTT on disconnect |
434
434
  | `online` | boolean | false | Appear as online to others |
435
435
  | `emitReady` | boolean | false | Emit ready event when MQTT connected |
436
- | `proxy` | string | — | HTTP proxy URL |
436
+ | `proxy` | string | | HTTP proxy URL |
437
437
  | `userAgent` | string | Safari UA | Override HTTP User-Agent |
438
438
 
439
439
  ---
440
440
 
441
- ## 📄 License
441
+ ## 📄 License
442
442
 
443
443
  MIT License
444
444
 
445
445
  **@eryxenx/fca** by [EryXenX (Mohammad Akash)](https://github.com/EryXenX)
446
446
 
447
447
  > The E2EE native media engine (`src/api/socket/e2ee/native/`) bundles a
448
- > precompiled binary built from **mautrix-go** / **mautrix-meta**, © Tulir
448
+ > precompiled binary built from **mautrix-go** / **mautrix-meta**, © Tulir
449
449
  > Asokan and contributors, licensed under **MPL-2.0** (not MIT). See
450
- > `src/api/socket/e2ee/native/NOTICE.md` for full attribution — this notice
450
+ > `src/api/socket/e2ee/native/NOTICE.md` for full attribution this notice
451
451
  > must be preserved in any redistribution of this fork.
452
- NEXCA MQTT core by [Deku](https://github.com/dekuzxc) — MIT License
452
+ NEXCA MQTT core by [Deku](https://github.com/dekuzxc) MIT License
453
453
 
454
454
  > Unauthorized copying or redistribution without credit is prohibited.
455
455
 
@@ -457,6 +457,6 @@ NEXCA MQTT core by [Deku](https://github.com/dekuzxc) — MIT License
457
457
 
458
458
  <div align="center">
459
459
 
460
- Made with ❤️ by EryXenX
460
+ Made with ❤️ by EryXenX
461
461
 
462
- </div>
462
+ </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eryxenx/fca",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Facebook Chat API by EryXenX | Stable • Auto Re-login • Full E2EE Support — send messages, media, reactions & more in encrypted chats, hassle-free",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
 
3
3
  const log = require("../../../func/logAdapter");
4
- const { getFrom } = require("../../utils/constants");
5
4
  const { get } = require("../../utils/request")
6
5
  const { getType } = require("../../utils/format");
6
+ const { extractDtsg } = require("../../utils/loginParser/extractDtsg");
7
7
  module.exports = function (defaultFuncs, api, ctx) {
8
8
  return function refreshFb_dtsg(obj, callback) {
9
9
  if (typeof obj === "function") {
@@ -24,8 +24,7 @@ module.exports = function (defaultFuncs, api, ctx) {
24
24
  }
25
25
  if (Object.keys(obj).length === 0) {
26
26
  get("https://www.facebook.com/", ctx.jar, null, ctx.globalOptions, { noRef: true }).then(({ data }) => {
27
- const fb_dtsg = getFrom(data, '["DTSGInitData",[],{"token":"', '","');
28
- const jazoest = getFrom(data, "jazoest=", '",');
27
+ const { fb_dtsg, jazoest } = extractDtsg(data);
29
28
  if (!fb_dtsg) throw new Error("Could not find fb_dtsg in HTML after requesting Facebook.");
30
29
  Object.assign(ctx, { fb_dtsg, jazoest });
31
30
  callback(null, {
@@ -22,6 +22,15 @@ const nativeMediaBridge = require("./native/nativeMediaBridge");
22
22
  const { decodeIncomingMedia } = require("./mediaDecode");
23
23
  const localMediaServer = require("./localMediaServer");
24
24
 
25
+ // Native media engine (image/video/audio send, reactions, unsend, markRead,
26
+ // group receive) is ON by default — matches pre-1.0.7 behavior. Set
27
+ // FCA_E2EE_NATIVE=0 to force it off (falls back to the legacy vendor engine)
28
+ // if the native/vendored device-kick conflict described below becomes an
29
+ // issue for your deployment.
30
+ function _nativeMediaEnabled() {
31
+ return process.env.FCA_E2EE_NATIVE !== "0";
32
+ }
33
+
25
34
  // Simple FIFO-bounded Map.set — evicts the oldest entry once maxSize is
26
35
  // exceeded, so long-lived per-message caches (media buffers, thread/sender
27
36
  // lookups) can't grow unbounded over a multi-hour/overnight process
@@ -377,11 +386,11 @@ class E2EEBridge {
377
386
  // loop (a fresh Noise handshake every few seconds). Until the two
378
387
  // engines can use distinct device ids, the native engine must stay
379
388
  // OFF for the vendored socket to stay alive. Opt back in with
380
- // FCA_E2EE_NATIVE=1 (group E2EE receive + native media sends) if you
389
+ // FCA_E2EE_NATIVE=0 (group E2EE receive + native media sends) if you
381
390
  // accept the resulting socket churn.
382
- const _nativeEnabled = process.env.FCA_E2EE_NATIVE === "1";
391
+ const _nativeEnabled = _nativeMediaEnabled();
383
392
  if (!_nativeEnabled) {
384
- logger.info("E2EE", "[native-group] native engine disabled (FCA_E2EE_NATIVE!=1) — vendored socket kept exclusive to avoid server-side device kicks");
393
+ logger.info("E2EE", "[native-group] native engine disabled (FCA_E2EE_NATIVE=0) — vendored socket kept exclusive to avoid server-side device kicks");
385
394
  }
386
395
  try {
387
396
  if (!_nativeEnabled) throw { _skipNative: true };
@@ -511,11 +520,11 @@ class E2EEBridge {
511
520
  const isGroup = this._knownE2EEGroups.has(String(threadId));
512
521
 
513
522
  if (!attachment && isGroup) {
514
- if (process.env.FCA_E2EE_NATIVE !== "1") {
523
+ if (!_nativeMediaEnabled()) {
515
524
  // Native engine disabled (default) — it shares the same E2EE
516
525
  // device id as the vendored socket and triggers server-side
517
526
  // device kicks. Group E2EE text send unavailable in this mode.
518
- throw new Error("E2EE group text send requires the native engine (FCA_E2EE_NATIVE=1), which is disabled to protect the vendored socket from server-side device kicks.");
527
+ throw new Error("E2EE group text send requires the native engine, which is disabled (FCA_E2EE_NATIVE=0) to protect the vendored socket from server-side device kicks.");
519
528
  }
520
529
  // Vendor's own group Sender-Key encryption is broken (see native-group
521
530
  // listener above) — group text sends go through native instead.
@@ -593,8 +602,8 @@ class E2EEBridge {
593
602
  const appState = this.api.getAppState();
594
603
  let result;
595
604
  try {
596
- if (process.env.FCA_E2EE_NATIVE !== "1") {
597
- throw new Error("native engine disabled (FCA_E2EE_NATIVE!=1) — see native-group note");
605
+ if (!_nativeMediaEnabled()) {
606
+ throw new Error("native engine disabled (FCA_E2EE_NATIVE=0) — see native-group note");
598
607
  }
599
608
  result = await nativeMediaBridge.sendMedia(appState, threadId, mediaType, data, mimeType, {
600
609
  caption: text || undefined,
@@ -690,7 +699,7 @@ class E2EEBridge {
690
699
 
691
700
  async markRead(threadId, watermarkTs) {
692
701
  this.ensureConnected();
693
- if (process.env.FCA_E2EE_NATIVE === "1")
702
+ if (_nativeMediaEnabled())
694
703
  return nativeMediaBridge.markRead(this.api.getAppState(), threadId, watermarkTs);
695
704
  // Native disabled: read receipts aren't available through the vendored
696
705
  // engine — treat as a no-op rather than waking the native engine.
@@ -711,8 +720,8 @@ class E2EEBridge {
711
720
  this._knownE2EEGroups = this._knownE2EEGroups || new Set();
712
721
  const isGroup = this._knownE2EEGroups.has(String(threadId));
713
722
  try {
714
- if (process.env.FCA_E2EE_NATIVE !== "1")
715
- throw new Error("native engine disabled (FCA_E2EE_NATIVE!=1)");
723
+ if (!_nativeMediaEnabled())
724
+ throw new Error("native engine disabled (FCA_E2EE_NATIVE=0)");
716
725
  return await nativeMediaBridge.sendReaction(this.api.getAppState(), threadId, messageId, senderJid, reaction, isGroup);
717
726
  } catch (err) {
718
727
  if (isGroup) {
@@ -734,8 +743,8 @@ class E2EEBridge {
734
743
  this._knownE2EEGroups = this._knownE2EEGroups || new Set();
735
744
  const isGroup = this._knownE2EEGroups.has(String(threadId));
736
745
  try {
737
- if (process.env.FCA_E2EE_NATIVE !== "1")
738
- throw new Error("native engine disabled (FCA_E2EE_NATIVE!=1)");
746
+ if (!_nativeMediaEnabled())
747
+ throw new Error("native engine disabled (FCA_E2EE_NATIVE=0)");
739
748
  return await nativeMediaBridge.unsendMessage(this.api.getAppState(), threadId, messageId, isGroup);
740
749
  } catch (err) {
741
750
  if (isGroup) {
@@ -752,8 +761,8 @@ class E2EEBridge {
752
761
  this._knownE2EEGroups = this._knownE2EEGroups || new Set();
753
762
  const isGroup = this._knownE2EEGroups.has(String(threadId));
754
763
  if (isGroup) {
755
- if (process.env.FCA_E2EE_NATIVE !== "1")
756
- throw new Error("E2EE group edit requires the native engine (FCA_E2EE_NATIVE=1), which is disabled.");
764
+ if (!_nativeMediaEnabled())
765
+ throw new Error("E2EE group edit requires the native engine, which is disabled (FCA_E2EE_NATIVE=0).");
757
766
  // Vendor's group Sender-Key encryption is broken, so route
758
767
  // group edits through native (it doesn't need a chatJid — the
759
768
  // messageId alone identifies which chat/message to edit).
@@ -947,4 +956,4 @@ function _getImageDimensions(buf, mimeType) {
947
956
  }
948
957
  } catch (_) {}
949
958
  return null;
950
- }
959
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ // Multi-pattern fb_dtsg / jazoest extractor.
4
+ // Facebook serves the token in different shapes depending on surface, A/B
5
+ // bucket and login state (DTSGInitialData / DTSGInitData blobs, escaped JSON
6
+ // inside <script>, plain hidden inputs, async_get_token, ...). Matching only
7
+ // one shape is why refreshes intermittently fail with "Could not find
8
+ // fb_dtsg in HTML". Pattern order is first-match-wins.
9
+
10
+ const DTSG_PATTERNS = [
11
+ /\["DTSGInitialData",\[\],\{"token":"([^"]+)"\}\]/,
12
+ /\["DTSGInitData",\[\],\{"token":"([^"]+)"/,
13
+ /"DTSGInitData",\[\],\{"token":"([^"]+)"/,
14
+ /"dtsg":\{"token":"([^"]+)"/,
15
+ /\{\\"token\\":\\"([^\\]+)\\"/,
16
+ /,\{"token":"([^"]+)"\},\d+\]/,
17
+ /"async_get_token":"([^"]+)"/,
18
+ /name="fb_dtsg"\s+value="([^"]+)"/,
19
+ ];
20
+
21
+ const JAZOEST_PATTERNS = [
22
+ /name="jazoest"\s+value="([^"]+)"/,
23
+ /jazoest=(\d+)/,
24
+ /"jazoest":"?(\d+)"?/,
25
+ ];
26
+
27
+ function firstMatch(html, patterns) {
28
+ for (const pattern of patterns) {
29
+ const m = html.match(pattern);
30
+ if (m && m[1]) return m[1];
31
+ }
32
+ return null;
33
+ }
34
+
35
+ function extractDtsg(html) {
36
+ if (!html) return { fb_dtsg: null, jazoest: null };
37
+ const raw = String(html);
38
+ return {
39
+ fb_dtsg: firstMatch(raw, DTSG_PATTERNS),
40
+ jazoest: firstMatch(raw, JAZOEST_PATTERNS),
41
+ };
42
+ }
43
+
44
+ module.exports = { extractDtsg, DTSG_PATTERNS, JAZOEST_PATTERNS };