hridoy-ultimate 9.0.0 → 9.0.2

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +346 -69
  2. package/{docs/UPSTREAM_API_DOCS.md → DOCS.md} +3 -3
  3. package/README.md +536 -161
  4. package/func/checkUpdate.js +1 -0
  5. package/index.d.ts +13 -74
  6. package/index.js +0 -6
  7. package/module/config.js +2 -5
  8. package/module/login.js +1 -2
  9. package/module/loginHelper.js +9 -41
  10. package/module/options.js +6 -6
  11. package/package.json +44 -39
  12. package/src/api/action/logout.js +0 -19
  13. package/src/api/messaging/sendMessage.js +1 -1
  14. package/src/api/messaging/sessionGuard.js +8 -0
  15. package/src/api/messaging/setMessageReaction.js +2 -7
  16. package/src/api/socket/core/connectMqtt.js +72 -99
  17. package/src/api/socket/core/emitAuth.js +17 -9
  18. package/src/api/socket/e2ee/index.js +15 -33
  19. package/src/api/socket/e2ee/native/NOTICE.md +2 -2
  20. package/src/api/socket/e2ee/native/lib/index.mjs +1 -2
  21. package/src/api/socket/e2ee/native/nativeMediaBridge.js +4 -4
  22. package/src/api/socket/listenE2EE.js +1 -3
  23. package/src/api/socket/listenMqtt.js +60 -67
  24. package/src/utils/antiSuspension.js +3 -3
  25. package/src/utils/fingerprint.js +35 -16
  26. package/src/utils/headers.js +1 -1
  27. package/src/utils/versionCheck.js +47 -0
  28. package/LICENSE +0 -23
  29. package/MERGE_REPORT.md +0 -54
  30. package/THIRD_PARTY_NOTICES.md +0 -36
  31. package/docs/TEST_RESULTS.md +0 -68
  32. package/scripts/run-tests.js +0 -19
  33. package/scripts/syntax-check.js +0 -22
  34. package/src/api/socket/core/lifecycle.js +0 -117
  35. package/src/api/socket/e2ee/proto/proto-writer.ts +0 -76
  36. package/src/utils/typingIndicator.js +0 -202
  37. package/test/helpers/stub-modules.js +0 -122
  38. package/test/integration/logout.test.js +0 -69
  39. package/test/integration/mqtt-lifecycle.test.js +0 -195
  40. package/test/integration/package.test.js +0 -116
  41. package/test/integration/typing-wiring.test.js +0 -71
  42. package/test/unit/fingerprint.test.js +0 -19
  43. package/test/unit/humanizer.test.js +0 -32
  44. package/test/unit/lifecycle.test.js +0 -92
  45. package/test/unit/parsers.test.js +0 -43
  46. package/test/unit/typing.test.js +0 -136
@@ -5,14 +5,9 @@
5
5
  */
6
6
  const { formatID } = require("../../../utils/format");
7
7
  const { getDefaultUserAgent } = require("../../../utils/fingerprint");
8
- const lifecycle = require("./lifecycle");
9
8
 
10
- // Was 5000 in the supplied sources; the 3.1.0 changelog entry of the upstream
11
- // lineage reports 5000ms causing needless reconnect cycles on slow links.
12
- // Configurable through listenMqtt({ tmsWaitTimeoutMs, connectTimeoutMs }).
13
- const T_MS_WAIT_TIMEOUT_MS = 8000;
14
- const CONNECT_TIMEOUT_MS = 12000;
15
- const SUBACK_TIMEOUT_MS = 5000;
9
+ const DEFAULT_RECONNECT_DELAY_MS = 2000;
10
+ const T_MS_WAIT_TIMEOUT_MS = 5000;
16
11
 
17
12
  module.exports = function createListenMqtt(deps) {
18
13
  const { WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy,
@@ -21,33 +16,29 @@ module.exports = function createListenMqtt(deps) {
21
16
 
22
17
  return function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
23
18
 
24
- // Tear down a previous client if listenMqtt() is entered twice (duplicate
25
- // listener / reconnect race) so we never keep two live sockets.
26
- if (ctx.mqttClient) {
27
- const stale = ctx.mqttClient;
28
- ctx.mqttClient = undefined;
29
- if (ctx._rTimeout) { clearTimeout(ctx._rTimeout); ctx._rTimeout = null; }
30
- lifecycle.detachClient(stale);
31
- try { stale.end(true); } catch (_) { /* already closed */ }
32
- logger("mqtt: replaced a still-registered client before reconnecting", "warn");
33
- }
34
- ctx._stopped = false;
35
-
36
- function giveUp(reason) {
37
- const msg = `mqtt reconnect attempts exhausted (${reason}); call listen again to restart`;
38
- logger(msg, "error");
39
- ctx._stopped = true;
40
- lifecycle.clearListenerTimers(ctx, false);
41
- globalCallback({ type: "stop_listen", error: msg }, null);
19
+ function scheduleReconnect(delayMs) {
20
+ const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || DEFAULT_RECONNECT_DELAY_MS;
21
+ const ms = typeof delayMs === "number" ? delayMs : d;
22
+ if (ctx._reconnectTimer) {
23
+ logger("mqtt reconnect already scheduled", "warn");
24
+ return; // debounce
25
+ }
26
+ if (ctx._ending) {
27
+ logger("mqtt reconnect skipped - ending", "warn");
28
+ return;
29
+ }
30
+ logger(`mqtt will reconnect in ${ms}ms`, "warn");
31
+ ctx._reconnectTimer = setTimeout(() => {
32
+ ctx._reconnectTimer = null;
33
+ if (!ctx._ending) {
34
+ listenMqtt(defaultFuncs, api, ctx, globalCallback);
35
+ }
36
+ }, ms);
42
37
  }
43
- function scheduleReconnect(reason) {
44
- const res = lifecycle.scheduleReconnect(ctx, logger, function () {
45
- listenMqtt(defaultFuncs, api, ctx, globalCallback);
46
- }, reason);
47
- if (res === "exhausted") giveUp(reason);
48
- else if (res === "stopped") logger("mqtt reconnect skipped - stopped", "info");
49
- return res;
38
+ function isEndingLikeError(msg) {
39
+ return /No subscription existed|client disconnecting|socket hang up|ECONNRESET/i.test(msg || "");
50
40
  }
41
+
51
42
  // Default to online unless explicitly disabled — GoatBot's optionsFca
52
43
  // has no `online` key, which used to make this undefined (falsy), so the
53
44
  // account sat permanently "inactive" while actively sending messages.
@@ -87,7 +78,7 @@ module.exports = function createListenMqtt(deps) {
87
78
  // Must match the HTTP layer's Accept-Language (src/utils/headers.js)
88
79
  // — a hardcoded different value here made the WS handshake and the
89
80
  // HTTP requests disagree about the same "browser".
90
- "Accept-Language": ctx.globalOptions.acceptLanguage || "en-US,en;q=0.9,vi;q=0.8",
81
+ "Accept-Language": ctx.globalOptions.acceptLanguage || "en-US,en;q=0.9",
91
82
  "Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits"
92
83
  },
93
84
  origin: "https://www.facebook.com",
@@ -97,7 +88,7 @@ module.exports = function createListenMqtt(deps) {
97
88
  keepalive: 30,
98
89
  reschedulePings: true,
99
90
  reconnectPeriod: 0,
100
- connectTimeout: (ctx._mqttOpt && ctx._mqttOpt.connectTimeoutMs) || CONNECT_TIMEOUT_MS
91
+ connectTimeout: 5000
101
92
  };
102
93
  if (ctx.globalOptions.proxy !== undefined) {
103
94
  const agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
@@ -109,11 +100,8 @@ module.exports = function createListenMqtt(deps) {
109
100
  options
110
101
  );
111
102
  const mqttClient = ctx.mqttClient;
112
- // A late event from a replaced/stopped client must never touch current state.
113
- const isActive = function () { return ctx.mqttClient === mqttClient; };
114
103
 
115
104
  mqttClient.on("error", function (err) {
116
- if (!isActive()) return;
117
105
  const msg = String(err && err.message ? err.message : err || "");
118
106
  if ((ctx._ending || ctx._cycling) && /No subscription existed|client disconnecting/i.test(msg)) {
119
107
  logger(`mqtt expected during shutdown: ${msg}`, "info");
@@ -145,80 +133,67 @@ module.exports = function createListenMqtt(deps) {
145
133
  if (ctx._ending || ctx._cycling) return;
146
134
 
147
135
  if (ctx.globalOptions.autoReconnect && !ctx._ending) {
148
- scheduleReconnect("error: " + msg);
136
+ const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || DEFAULT_RECONNECT_DELAY_MS;
137
+ logger(`mqtt autoReconnect listenMqtt() in ${d}ms`, "warn");
138
+ scheduleReconnect(d);
149
139
  } else {
150
140
  globalCallback({ type: "stop_listen", error: msg || "Connection refused" }, null);
151
141
  }
152
142
  });
153
143
 
154
144
  mqttClient.on("connect", function () {
155
- if (!isActive()) return;
145
+ if (process.env.OnStatus === undefined) {
146
+ logger("fca-unofficial", "info");
147
+ process.env.OnStatus = true;
148
+ }
156
149
  ctx._cycling = false;
157
150
 
158
- // Subscribe first and publish only after every SUBACK (or a bounded
159
- // timeout). Publishing before the subscriptions exist can make the
160
- // server close the socket with "No subscription existed". This ordering
161
- // is protocol-level behaviour of mqtt.js; NOT verified against live
162
- // Facebook servers (see README "Known limitations").
163
- let started = false;
164
- const startSync = function () {
165
- if (started || !isActive()) return;
166
- started = true;
167
- clearTimeout(subTimer);
168
- const queue = {
169
- sync_api_version: 11, max_deltas_able_to_process: 100, delta_batch_size: 500,
170
- encoding: "JSON", entity_fbid: ctx.userID, initial_titan_sequence_id: ctx.lastSeqId, device_params: null
171
- };
172
- const topic = ctx.syncToken ? "/messenger_sync_get_diffs" : "/messenger_sync_create_queue";
173
- if (ctx.syncToken) { queue.last_seq_id = ctx.lastSeqId; queue.sync_token = ctx.syncToken; }
174
- mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
175
- mqttClient.publish("/foreground_state", JSON.stringify({ foreground: chatOn }), { qos: 1 });
176
- mqttClient.publish("/set_client_settings", JSON.stringify({ make_user_available_when_in_foreground: true }), { qos: 1 });
151
+ topics.forEach(t => mqttClient.subscribe(t));
177
152
 
178
- const waitMs = (ctx._mqttOpt && ctx._mqttOpt.tmsWaitTimeoutMs) || T_MS_WAIT_TIMEOUT_MS;
179
- let rTimeout = setTimeout(function () {
180
- rTimeout = null;
181
- if (!isActive() || ctx._ending) {
182
- logger("mqtt t_ms timeout skipped - ending/replaced", "warn");
183
- return;
184
- }
185
- logger("mqtt t_ms timeout, cycling", "warn");
186
- try { mqttClient.end(true); } catch (_) { /* already closed */ }
187
- scheduleReconnect("t_ms timeout");
188
- }, waitMs);
189
- ctx._rTimeout = rTimeout;
190
153
 
191
- ctx.tmsWait = function () {
192
- if (rTimeout) { clearTimeout(rTimeout); rTimeout = null; }
193
- if (ctx._rTimeout) ctx._rTimeout = null;
194
- // First real sync response: the connection is healthy, reset backoff.
195
- ctx._reconnectAttempt = 0;
196
- if (ctx.globalOptions.emitReady) globalCallback({ type: "ready", error: null });
197
- delete ctx.tmsWait;
198
- };
154
+ const queue = {
155
+ sync_api_version: 11, max_deltas_able_to_process: 100, delta_batch_size: 500,
156
+ encoding: "JSON", entity_fbid: ctx.userID, initial_titan_sequence_id: ctx.lastSeqId, device_params: null
199
157
  };
200
-
201
- let pending = topics.length;
202
- const subTimer = setTimeout(function () {
203
- logger("mqtt SUBACK wait timed out, continuing", "warn");
204
- startSync();
205
- }, SUBACK_TIMEOUT_MS);
206
- if (!pending) return startSync();
207
- topics.forEach(function (t) {
158
+ const topic = ctx.syncToken ? "/messenger_sync_get_diffs" : "/messenger_sync_create_queue";
159
+ if (ctx.syncToken) { queue.last_seq_id = ctx.lastSeqId; queue.sync_token = ctx.syncToken; }
160
+ mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
161
+ mqttClient.publish("/foreground_state", JSON.stringify({ foreground: chatOn }), { qos: 1 });
162
+ mqttClient.publish("/set_client_settings", JSON.stringify({ make_user_available_when_in_foreground: true }), { qos: 1 });
163
+ const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || DEFAULT_RECONNECT_DELAY_MS;
164
+ let rTimeout = setTimeout(function () {
165
+ rTimeout = null;
166
+ if (ctx._ending) {
167
+ logger("mqtt t_ms timeout skipped - ending", "warn");
168
+ return;
169
+ }
170
+ logger(`mqtt t_ms timeout, cycling in ${d}ms`, "warn");
208
171
  try {
209
- mqttClient.subscribe(t, function (subErr) {
210
- if (subErr) logger(`mqtt subscribe ${t} failed: ${subErr && subErr.message ? subErr.message : String(subErr)}`, "warn");
211
- if (--pending === 0) startSync();
212
- });
213
- } catch (e) {
214
- logger(`mqtt subscribe ${t} threw: ${e && e.message ? e.message : String(e)}`, "warn");
215
- if (--pending === 0) startSync();
172
+ if (mqttClient && mqttClient.connected) {
173
+ mqttClient.end(true);
174
+ }
175
+ } catch (_) { }
176
+ scheduleReconnect(d);
177
+ }, T_MS_WAIT_TIMEOUT_MS);
178
+
179
+ // Store timeout reference for cleanup
180
+ ctx._rTimeout = rTimeout;
181
+
182
+ ctx.tmsWait = function () {
183
+ if (rTimeout) {
184
+ clearTimeout(rTimeout);
185
+ rTimeout = null;
216
186
  }
217
- });
187
+ if (ctx._rTimeout) {
188
+ delete ctx._rTimeout;
189
+ }
190
+ if (ctx.globalOptions.emitReady) globalCallback({ type: "ready", error: null });
191
+ delete ctx.tmsWait;
192
+ };
218
193
  });
219
194
 
220
195
  mqttClient.on("message", function (topic, message) {
221
- if (!isActive() || ctx._ending) return; // stale client or shutting down
196
+ if (ctx._ending) return; // Ignore messages if ending
222
197
  ctx.lastMqttActivity = Date.now(); // packet-level liveness for the heartbeat
223
198
  try {
224
199
  let jsonMessage = Buffer.isBuffer(message) ? Buffer.from(message).toString() : message;
@@ -278,26 +253,24 @@ module.exports = function createListenMqtt(deps) {
278
253
  });
279
254
 
280
255
  mqttClient.on("close", function () {
281
- if (!isActive()) return;
282
256
  if (ctx._ending || ctx._cycling) {
283
257
  logger("mqtt close expected", "info");
284
258
  return;
285
259
  }
286
260
  logger("mqtt connection closed", "warn");
287
261
  if (ctx.globalOptions.autoReconnect && !ctx._ending) {
288
- scheduleReconnect("socket closed");
262
+ scheduleReconnect();
289
263
  }
290
264
  });
291
265
 
292
266
  mqttClient.on("disconnect", () => {
293
- if (!isActive()) return;
294
267
  if (ctx._ending || ctx._cycling) {
295
268
  logger("mqtt disconnect expected", "info");
296
269
  return;
297
270
  }
298
271
  logger("mqtt disconnected", "warn");
299
272
  if (ctx.globalOptions.autoReconnect && !ctx._ending) {
300
- scheduleReconnect("server disconnect");
273
+ scheduleReconnect();
301
274
  }
302
275
  });
303
276
  };
@@ -3,14 +3,21 @@
3
3
  * Emits account_inactive to the global callback and cleans up MQTT/timers/scheduler.
4
4
  * Used when login is invalid, blocked, or session is lost.
5
5
  */
6
- const lifecycle = require("./lifecycle");
7
-
8
6
  module.exports = function createEmitAuth({ logger }) {
9
7
  return function emitAuth(ctx, api, globalCallback, reason, detail) {
10
- // Clean up all timers (listener + per-login dtsg refresh) and mark stopped
11
- // so no pending reconnect can revive a dead session.
12
- ctx._stopped = true;
13
- lifecycle.clearListenerTimers(ctx, true);
8
+ // Clean up all timers
9
+ try {
10
+ if (ctx._autoCycleTimer) {
11
+ clearInterval(ctx._autoCycleTimer);
12
+ ctx._autoCycleTimer = null;
13
+ }
14
+ } catch (_) { }
15
+ try {
16
+ if (ctx._reconnectTimer) {
17
+ clearTimeout(ctx._reconnectTimer);
18
+ ctx._reconnectTimer = null;
19
+ }
20
+ } catch (_) { }
14
21
 
15
22
  try {
16
23
  ctx._ending = true;
@@ -20,9 +27,10 @@ module.exports = function createEmitAuth({ logger }) {
20
27
  // Clean up MQTT client
21
28
  try {
22
29
  if (ctx.mqttClient) {
23
- const c = ctx.mqttClient;
24
- lifecycle.detachClient(c);
25
- c.end(true);
30
+ ctx.mqttClient.removeAllListeners();
31
+ if (ctx.mqttClient.connected) {
32
+ ctx.mqttClient.end(true);
33
+ }
26
34
  }
27
35
  } catch (_) { }
28
36
 
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Wraps the `fb-messenger-e2ee` npm package (real dependency, not a bundled
7
7
  * vendor copy) and exposes it through NEXCA's own api.e2ee / connectE2EE /
8
- * listenE2EE surface, so the rest of fca-eryxenx and GoatBot never need to
8
+ * listenE2EE surface, so the rest of hridoy-ultimate and GoatBot never need to
9
9
  * know which E2EE engine is underneath.
10
10
  *
11
11
 
@@ -17,36 +17,18 @@
17
17
  */
18
18
 
19
19
  const path = require("path");
20
- const fs = require("fs");
21
20
  const logger = require("../../../utils/nexca-logger");
22
21
  const nativeMediaBridge = require("./native/nativeMediaBridge");
23
22
  const { decodeIncomingMedia } = require("./mediaDecode");
24
23
  const localMediaServer = require("./localMediaServer");
25
24
 
26
- // Native media engine (image/video/audio send, group receive/send, reactions,
27
- // unsend, markRead) is OPT-IN in hridoy-ultimate. It needs a prebuilt
28
- // "messagix" shared library (mautrix, MPL-2.0) that this package does NOT
29
- // bundle because its provenance could not be verified from the supplied
30
- // archives. hridoy-ultimate 9.0.0 now ships the user-supplied messagix.so /
31
- // messagix.dll under native/build/ (SHA-256 pinned in THIRD_PARTY_NOTICES.md),
32
- // but the engine stays OPT-IN: set FCA_E2EE_NATIVE=1 (FCA_E2EE_NATIVE_LIB can
33
- // still override the bundled path). Upstream 1.0.8 flipped this to default-on;
34
- // that is rejected here because the native and vendored engines share one E2EE
35
- // device and kick each other (see the note in connect()).
36
- function _bundledNativeLib() {
37
- const base = path.join(__dirname, "native", "build");
38
- if (process.platform === "win32") return path.join(base, "messagix.dll");
39
- if (process.platform === "darwin") return path.join(base, "messagix.dylib");
40
- return path.join(base, "messagix.so");
41
- }
42
- function _nativeLibAvailable() {
43
- try {
44
- if (process.env.FCA_E2EE_NATIVE_LIB) return fs.existsSync(process.env.FCA_E2EE_NATIVE_LIB);
45
- return fs.existsSync(_bundledNativeLib());
46
- } catch (_) { return false; }
47
- }
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.
48
30
  function _nativeMediaEnabled() {
49
- return process.env.FCA_E2EE_NATIVE === "1" && _nativeLibAvailable();
31
+ return process.env.FCA_E2EE_NATIVE !== "0";
50
32
  }
51
33
 
52
34
  // Simple FIFO-bounded Map.set — evicts the oldest entry once maxSize is
@@ -70,7 +52,7 @@ function loadFBClient() {
70
52
  // 1. fb-messenger-e2ee is NOT published on the npm registry — only
71
53
  // available as TypeScript source on GitHub.
72
54
  // 2. Depending on it as a git/file dependency nested inside
73
- // fca-eryxenx (itself installed via `github:` in GoatBot-Pro)
55
+ // hridoy-ultimate (itself installed via `github:` in GoatBot-Pro)
74
56
  // is unreliable — npm does not consistently build nested
75
57
  // non-registry dependencies during a git-dependency install.
76
58
  // Shipping plain compiled JS here means zero build step is needed
@@ -404,11 +386,11 @@ class E2EEBridge {
404
386
  // loop (a fresh Noise handshake every few seconds). Until the two
405
387
  // engines can use distinct device ids, the native engine must stay
406
388
  // OFF for the vendored socket to stay alive. Opt back in with
407
- // native engine not enabled (set FCA_E2EE_NATIVE=1; bundled messagix binary is used unless FCA_E2EE_NATIVE_LIB overrides it) (group E2EE receive + native media sends) if you
389
+ // FCA_E2EE_NATIVE=0 (group E2EE receive + native media sends) if you
408
390
  // accept the resulting socket churn.
409
391
  const _nativeEnabled = _nativeMediaEnabled();
410
392
  if (!_nativeEnabled) {
411
- logger.info("E2EE", "[native-group] native engine disabled (native engine not enabled (set FCA_E2EE_NATIVE=1; bundled messagix binary is used unless FCA_E2EE_NATIVE_LIB overrides it)) — 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");
412
394
  }
413
395
  try {
414
396
  if (!_nativeEnabled) throw { _skipNative: true };
@@ -542,7 +524,7 @@ class E2EEBridge {
542
524
  // Native engine disabled (default) — it shares the same E2EE
543
525
  // device id as the vendored socket and triggers server-side
544
526
  // device kicks. Group E2EE text send unavailable in this mode.
545
- throw new Error("E2EE group text send requires the native engine, which is disabled (native engine not enabled (set FCA_E2EE_NATIVE=1; bundled messagix binary is used unless FCA_E2EE_NATIVE_LIB overrides it)) 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.");
546
528
  }
547
529
  // Vendor's own group Sender-Key encryption is broken (see native-group
548
530
  // listener above) — group text sends go through native instead.
@@ -621,7 +603,7 @@ class E2EEBridge {
621
603
  let result;
622
604
  try {
623
605
  if (!_nativeMediaEnabled()) {
624
- throw new Error("native engine disabled (native engine not enabled (set FCA_E2EE_NATIVE=1; bundled messagix binary is used unless FCA_E2EE_NATIVE_LIB overrides it)) — see native-group note");
606
+ throw new Error("native engine disabled (FCA_E2EE_NATIVE=0) — see native-group note");
625
607
  }
626
608
  result = await nativeMediaBridge.sendMedia(appState, threadId, mediaType, data, mimeType, {
627
609
  caption: text || undefined,
@@ -739,7 +721,7 @@ class E2EEBridge {
739
721
  const isGroup = this._knownE2EEGroups.has(String(threadId));
740
722
  try {
741
723
  if (!_nativeMediaEnabled())
742
- throw new Error("native engine disabled (native engine not enabled (set FCA_E2EE_NATIVE=1; bundled messagix binary is used unless FCA_E2EE_NATIVE_LIB overrides it))");
724
+ throw new Error("native engine disabled (FCA_E2EE_NATIVE=0)");
743
725
  return await nativeMediaBridge.sendReaction(this.api.getAppState(), threadId, messageId, senderJid, reaction, isGroup);
744
726
  } catch (err) {
745
727
  if (isGroup) {
@@ -762,7 +744,7 @@ class E2EEBridge {
762
744
  const isGroup = this._knownE2EEGroups.has(String(threadId));
763
745
  try {
764
746
  if (!_nativeMediaEnabled())
765
- throw new Error("native engine disabled (native engine not enabled (set FCA_E2EE_NATIVE=1; bundled messagix binary is used unless FCA_E2EE_NATIVE_LIB overrides it))");
747
+ throw new Error("native engine disabled (FCA_E2EE_NATIVE=0)");
766
748
  return await nativeMediaBridge.unsendMessage(this.api.getAppState(), threadId, messageId, isGroup);
767
749
  } catch (err) {
768
750
  if (isGroup) {
@@ -780,7 +762,7 @@ class E2EEBridge {
780
762
  const isGroup = this._knownE2EEGroups.has(String(threadId));
781
763
  if (isGroup) {
782
764
  if (!_nativeMediaEnabled())
783
- throw new Error("E2EE group edit requires the native engine, which is disabled (native engine not enabled (set FCA_E2EE_NATIVE=1; bundled messagix binary is used unless FCA_E2EE_NATIVE_LIB overrides it)).");
765
+ throw new Error("E2EE group edit requires the native engine, which is disabled (FCA_E2EE_NATIVE=0).");
784
766
  // Vendor's group Sender-Key encryption is broken, so route
785
767
  // group edits through native (it doesn't need a chatJid — the
786
768
  // messageId alone identifies which chat/message to edit).
@@ -16,12 +16,12 @@ https://www.mozilla.org/en-US/MPL/2.0/
16
16
  into the compiled binary, adapted from the same upstream project's Node
17
17
  bindings.
18
18
 
19
- fca-eryxenx uses this native engine only for sending E2EE media attachments
19
+ hridoy-ultimate uses this native engine only for sending E2EE media attachments
20
20
  (images/video/audio/documents) in end-to-end encrypted Messenger threads,
21
21
  because the project's own hand-written Signal Protocol media encoder
22
22
  (`../vendor/fme`) produces payloads that Facebook's servers accept but that
23
23
  Messenger clients fail to render. Text messages and all non-media E2EE
24
- functionality continue to use fca-eryxenx's original implementation.
24
+ functionality continue to use hridoy-ultimate's original implementation.
25
25
 
26
26
  In accordance with MPL-2.0 §3.3, this NOTICE file must be preserved and
27
27
  distributed alongside these files, and any modifications to the covered
@@ -19,7 +19,6 @@ function resolveDirname() {
19
19
  }
20
20
  __name(resolveDirname, "resolveDirname");
21
21
  function libPath() {
22
- if (process.env.FCA_E2EE_NATIVE_LIB) return process.env.FCA_E2EE_NATIVE_LIB;
23
22
  const base = path.join(resolveDirname(), "..", "build");
24
23
  if (process.platform === "win32") return path.join(base, "messagix.dll");
25
24
  if (process.platform === "darwin") return path.join(base, "messagix.dylib");
@@ -28,7 +27,7 @@ function libPath() {
28
27
  __name(libPath, "libPath");
29
28
  var LIB_FILE = libPath();
30
29
  if (!fs.existsSync(LIB_FILE)) {
31
- throw new Error(`Native library not found at ${LIB_FILE}. No bundled build exists for this platform/arch (only x86-64 .so/.dll ship). Set FCA_E2EE_NATIVE_LIB to a trusted messagix build.`);
30
+ throw new Error(`Native library not found at ${LIB_FILE}. Run: npm run build:go`);
32
31
  }
33
32
  var lib = koffi.load(LIB_FILE);
34
33
  var mk = /* @__PURE__ */ __name((ret, name, args) => lib.func(name, ret, args), "mk");
@@ -6,7 +6,7 @@
6
6
  * Upstream: https://github.com/mautrix/go, https://github.com/mautrix/meta
7
7
  * License: https://www.mozilla.org/en-US/MPL/2.0/
8
8
  *
9
- * fca-eryxenx's own hand-written Signal/Noise media encoder (vendor/fme)
9
+ * hridoy-ultimate's own hand-written Signal/Noise media encoder (vendor/fme)
10
10
  * sends E2EE image/video/audio/document messages that Facebook's server
11
11
  * accepts (valid messageId + CDN path returned) but that the real Messenger
12
12
  * client silently fails to render — the reverse-engineered .proto schema
@@ -16,7 +16,7 @@
16
16
  * mautrix-go/mautrix-meta (a maintained, protocol-accurate Go implementation
17
17
  * of the same E2EE stack) via the `koffi` FFI, exactly like the reference
18
18
  * "stfca" package does. Only MEDIA sending is routed through here — text
19
- * messages keep using fca-eryxenx's existing, already-working E2EE path.
19
+ * messages keep using hridoy-ultimate's existing, already-working E2EE path.
20
20
  */
21
21
 
22
22
  const path = require("path");
@@ -101,7 +101,7 @@ let _connected = false;
101
101
  * on every reconnect was never releasing the old handle fast enough and
102
102
  * leaked native memory over a multi-hour uptime — reusing the instance and
103
103
  * just re-calling .connect() avoids that entirely.
104
- * @param {Array} appState fca-eryxenx's appState (from api.getAppState())
104
+ * @param {Array} appState hridoy-ultimate's appState (from api.getAppState())
105
105
  */
106
106
  async function getClient(appState) {
107
107
  if (_connected && _clientInstance) return _clientInstance;
@@ -184,7 +184,7 @@ async function sendMedia(appState, chatJid, mediaType, data, mimeType, opts) {
184
184
  const client = await getClient(appState);
185
185
  opts = opts || {};
186
186
 
187
- // Native client needs a full JID (id@server). fca-eryxenx normalizes
187
+ // Native client needs a full JID (id@server). hridoy-ultimate normalizes
188
188
  // E2EE thread ids down to bare numeric strings for GoatBot's benefit,
189
189
  // so re-attach the domain here if it's missing — groups use "@g.us",
190
190
  // DMs use "@msgr". Using the wrong domain makes native try to look up
@@ -27,9 +27,7 @@ module.exports = function (defaultFuncs, api, ctx) {
27
27
  cb([]);
28
28
  }
29
29
  if (ctx.e2ee && ctx.e2ee.connected) {
30
- ctx.e2ee.disconnect().catch((e) => {
31
- require("../../../func/logger")("E2EE disconnect failed: " + (e && e.message ? e.message : String(e)), "warn");
32
- });
30
+ ctx.e2ee.disconnect().catch(() => {});
33
31
  }
34
32
  }
35
33
  stopListeningAsync() {