stfca 1.1.27 → 1.2.27

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
@@ -7,6 +7,8 @@
7
7
  > **Unofficial Facebook Chat API for Node.js** - Interact with Facebook Messenger programmatically for ST-BOT
8
8
  >
9
9
  > **Enhanced & Maintained by ST | Sheikh Tamim**
10
+ >
11
+ > 🔐 **Now with End-to-End Encryption (E2EE) Support!**
10
12
 
11
13
  ## 🌟 What's New in ST-FCA
12
14
 
@@ -14,9 +16,10 @@
14
16
  - 🔄 Auto-reconnect with configurable intervals
15
17
  - 📊 Better connection status indicators
16
18
  - 🎨 Improved console output with colors
17
- - 🔐 Enhanced security and stability
19
+ - 🔐 **NEW: End-to-End Encryption (E2EE) Support** - Full E2EE messaging system
18
20
  - 🚀 Automatic update checking and installation
19
21
  - 💡 Better error handling and debugging
22
+ - 🔐 Enhanced security and stability
20
23
 
21
24
  ## 📦 Installation
22
25
 
@@ -179,7 +182,228 @@ login({ appState: [] }, (err, api) => {
179
182
  });
180
183
  ```
181
184
 
182
- ## 📝 Message Types
185
+ ## E2EE (End-to-End Encryption) - NEW FEATURE
186
+
187
+ ST-FCA now includes **full End-to-End Encryption support** for secure encrypted messaging!
188
+
189
+ ### What is E2EE?
190
+
191
+ E2EE (End-to-End Encryption) ensures that messages are encrypted on the sender's device and only decrypted on the recipient's device. No one in between (including servers) can read your messages.
192
+
193
+ ### E2EE Features
194
+
195
+ ✅ **Encrypted Messages** - All messages are encrypted
196
+ ✅ **Encrypted Attachments** - Photos, videos, files encrypted
197
+ ✅ **Automatic Detection** - Auto-routes between E2EE and standard messages
198
+ ✅ **Message Reactions** - React to encrypted messages
199
+ ✅ **Message Editing** - Edit encrypted messages
200
+ ✅ **Typing Indicators** - Send typing indicators over E2EE
201
+ ✅ **Device Persistence** - Reuse device keys across sessions
202
+ ✅ **Media Server** - Local cache for decrypted files
203
+
204
+ ### E2EE Quick Start
205
+
206
+ ```javascript
207
+ const login = require("stfca");
208
+ const fs = require("fs");
209
+
210
+ login(
211
+ { appState: JSON.parse(fs.readFileSync("appstate.json", "utf8")) },
212
+ {
213
+ enableE2EE: true, // 🔐 Enable E2EE
214
+ listenEvents: true,
215
+ autoMarkRead: true
216
+ },
217
+ (err, api) => {
218
+ if (err) return console.error(err);
219
+
220
+ // Connect E2EE Bridge
221
+ api.connectE2EE((err) => {
222
+ if (err) console.error("E2EE connection failed:", err);
223
+ console.log("✓ E2EE connected!");
224
+ });
225
+
226
+ // Get device data
227
+ api.getE2EEDeviceData((err, data) => {
228
+ if (!err) console.log("✓ Device data loaded");
229
+ });
230
+
231
+ // Listen for E2EE Messages
232
+ api.listenMqtt((err, event) => {
233
+ if (err) return console.error(err);
234
+
235
+ // Handle E2EE messages
236
+ if (event.type === "e2ee_message") {
237
+ console.log("🔐 E2EE Message:", event.body);
238
+ console.log(" Thread:", event.threadID);
239
+ console.log(" Encrypted: ✓ YES");
240
+
241
+ // Auto-reply with E2EE
242
+ api.sendMessage("Received: " + event.body, event.threadID);
243
+ }
244
+
245
+ // Handle E2EE reactions
246
+ if (event.type === "e2ee_message_reaction") {
247
+ console.log("🔐 E2EE Reaction:", event.reaction);
248
+ }
249
+
250
+ // Handle E2EE edits
251
+ if (event.type === "e2ee_message_edit") {
252
+ console.log("🔐 E2EE Message Edited:", event.body);
253
+ }
254
+ });
255
+ }
256
+ );
257
+ ```
258
+
259
+ ### E2EE Event Types
260
+
261
+ #### E2EE Message Event
262
+ ```javascript
263
+ event.type === "e2ee_message"
264
+ {
265
+ type: "e2ee_message",
266
+ senderID: "61568577897207",
267
+ threadID: "61568577897207:69@msgr", // E2EE JID format
268
+ body: "Hello encrypted world!",
269
+ messageID: "m_1234567890",
270
+ isE2EE: true, // 🔐 Marked as encrypted
271
+ isGroup: false,
272
+ timestamp: 1780805668000,
273
+ attachments: [],
274
+ mentions: {}
275
+ }
276
+ ```
277
+
278
+ #### E2EE Reaction Event
279
+ ```javascript
280
+ event.type === "e2ee_message_reaction"
281
+ {
282
+ type: "e2ee_message_reaction",
283
+ messageID: "m_1234567890",
284
+ reaction: "❤️",
285
+ userID: "61568577897207",
286
+ threadID: "61568577897207:69@msgr",
287
+ isE2EE: true
288
+ }
289
+ ```
290
+
291
+ #### E2EE Message Edit Event
292
+ ```javascript
293
+ event.type === "e2ee_message_edit"
294
+ {
295
+ type: "e2ee_message_edit",
296
+ messageID: "m_1234567890",
297
+ body: "Updated encrypted message",
298
+ senderID: "61568577897207",
299
+ isE2EE: true
300
+ }
301
+ ```
302
+
303
+ ### E2EE API Methods
304
+
305
+ ```javascript
306
+ // Enable E2EE in options
307
+ api.setOptions({ enableE2EE: true });
308
+
309
+ // Connect E2EE bridge
310
+ api.connectE2EE(callback);
311
+
312
+ // Get device encryption keys
313
+ api.getE2EEDeviceData(callback);
314
+
315
+ // Send encrypted message (auto-detected)
316
+ api.sendMessage(message, e2eeThreadID, callback);
317
+
318
+ // React to encrypted message
319
+ api.setMessageReaction(emoji, messageID, callback);
320
+
321
+ // Edit encrypted message
322
+ api.editMessage(message, messageID, callback);
323
+
324
+ // Unsend encrypted message
325
+ api.unsendMessage(messageID, callback);
326
+
327
+ // Send typing indicator (E2EE)
328
+ api.sendTypingE2EE(threadID, callback);
329
+
330
+ // Download encrypted media
331
+ api.downloadE2EEMedia(messageID, callback);
332
+
333
+ // Resolve encrypted attachment URL
334
+ api.resolveE2EEAttachment(attachment);
335
+ ```
336
+
337
+ ### E2EE Connection Flow
338
+
339
+ <img src="assest/e2eeconnect.png" alt="E2EE Connection Process" width="800"/>
340
+
341
+ *Successful E2EE bridge connection showing:*
342
+ - ✓ Login with cookies
343
+ - ✓ MQTT connection established
344
+ - ✓ E2EE bridge connected
345
+ - ✓ Device keys established
346
+
347
+ ### E2EE Message Listening
348
+
349
+ <img src="assest/e2eelisten.png" alt="E2EE Message Event" width="800"/>
350
+
351
+ *E2EE message event showing:*
352
+ - 🔐 Encrypted message received
353
+ - ✓ Message JID format (E2EE identifier)
354
+ - ✓ Sender and thread information
355
+ - ✓ `isE2EE: true` flag
356
+
357
+ ### Configuration
358
+
359
+ ```javascript
360
+ // In config.json
361
+ {
362
+ "enableE2EE": true,
363
+ "enableTypingIndicator": true,
364
+ "typingDuration": 4000
365
+ }
366
+ ```
367
+
368
+ Or in login options:
369
+ ```javascript
370
+ {
371
+ enableE2EE: true,
372
+ e2eeMemoryOnly: false,
373
+ autoReconnect: true,
374
+ listenEvents: true
375
+ }
376
+ ```
377
+
378
+ ### Test E2EE Bot
379
+
380
+ A complete E2EE test bot is included: [e2eebot.js](./e2eebot.js)
381
+
382
+ ```bash
383
+ # Run the E2EE test bot
384
+ node e2eebot.js
385
+ ```
386
+
387
+ Commands:
388
+ - `!ping` - Test bot response
389
+ - `!info` - Show message info
390
+ - `!echo <text>` - Echo message
391
+ - `!react` - React with ❤️
392
+ - `!help` - Show help
393
+
394
+ ### Full E2EE Documentation
395
+
396
+ See [E2EE_GUIDE.md](./E2EE_GUIDE.md) for comprehensive documentation including:
397
+ - ✅ System architecture
398
+ - ✅ All API methods
399
+ - ✅ Event types reference
400
+ - ✅ Attachment handling
401
+ - ✅ Device data management
402
+ - ✅ Troubleshooting guide
403
+
404
+ ---
405
+
406
+ ## �📝 Message Types
183
407
 
184
408
  | Type | Usage |
185
409
  | ---------------------- | ----------------------------------------------------------------- |
package/index.js CHANGED
@@ -279,6 +279,21 @@ function buildAPI(globalOptions, html, jar) {
279
279
  global.GoatBot.refreshFcaConfig = refreshFcaConfig;
280
280
  }
281
281
 
282
+ // ─── E2EE options from root config.json ────────────────────────────────────
283
+ try {
284
+ const _e2eeRootPath = path.join(process.cwd(), 'config.json');
285
+ if (fs.existsSync(_e2eeRootPath)) {
286
+ const _rootCfg = JSON.parse(fs.readFileSync(_e2eeRootPath, 'utf8'));
287
+ const _e2eeCfg = (_rootCfg && _rootCfg.e2ee) ? _rootCfg.e2ee : {};
288
+ if (_e2eeCfg.enable === true) globalOptions.enableE2EE = true;
289
+ // saveType: 'memory' (default) or 'path' (persist keys to devicePath)
290
+ var _saveType = _e2eeCfg.saveType || (typeof _e2eeCfg.memoryOnly !== 'undefined' ? (_e2eeCfg.memoryOnly ? 'memory' : 'path') : 'memory');
291
+ globalOptions.e2eeMemoryOnly = (_saveType !== 'path');
292
+ if (_saveType === 'path' && _e2eeCfg.devicePath) globalOptions.e2eeDevicePath = _e2eeCfg.devicePath;
293
+ if (_e2eeCfg.deviceData) globalOptions.e2eeDeviceData = _e2eeCfg.deviceData;
294
+ }
295
+ } catch (_) {}
296
+
282
297
  ctx.config = config;
283
298
  var api = {
284
299
  setOptions: setOptions.bind(null, globalOptions),
@@ -451,6 +466,51 @@ function buildAPI(globalOptions, html, jar) {
451
466
  };
452
467
 
453
468
  api.listen = api.listenMqtt;
469
+
470
+ // ─── E2EE: patch API + expose connectE2EE / getE2EEDeviceData ───────────────
471
+ if (globalOptions.enableE2EE) {
472
+ try {
473
+ var _e2ee = require('./e2ee');
474
+ _e2ee.patchApiForE2EE(api, ctx);
475
+
476
+ // api.connectE2EE(callback?) – start the E2EE client
477
+ api.connectE2EE = function (callback) {
478
+ var bridge = _e2ee.createBridge(ctx);
479
+ api._e2eeBridge = bridge;
480
+ return bridge.connect(callback);
481
+ };
482
+
483
+ // api.getE2EEBridge() – return the live bridge instance
484
+ api.getE2EEBridge = function () {
485
+ return ctx._e2eeBridge || null;
486
+ };
487
+
488
+ // api.getE2EEDeviceData(callback?) – fetch persistent device keys
489
+ api.getE2EEDeviceData = function (callback) {
490
+ var resolve, reject;
491
+ var promise = new Promise(function (res, rej) { resolve = res; reject = rej; });
492
+ if (ctx._e2eeDeviceData) {
493
+ if (typeof callback === 'function') callback(null, ctx._e2eeDeviceData);
494
+ resolve(ctx._e2eeDeviceData);
495
+ return promise;
496
+ }
497
+ _e2ee.createBridge(ctx).getDeviceData()
498
+ .then(function (d) {
499
+ ctx._e2eeDeviceData = d;
500
+ if (typeof callback === 'function') callback(null, d);
501
+ resolve(d);
502
+ })
503
+ .catch(function (e) {
504
+ if (typeof callback === 'function') callback(e);
505
+ reject(e);
506
+ });
507
+ return promise;
508
+ };
509
+ } catch (_patchErr) {
510
+ log.warn('E2EE', 'Failed to initialise E2EE:', _patchErr && _patchErr.message ? _patchErr.message : _patchErr);
511
+ }
512
+ }
513
+
454
514
  return {
455
515
  ctx,
456
516
  defaultFuncs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stfca",
3
- "version": "1.1.27",
3
+ "version": "1.2.27",
4
4
  "description": "Unofficial Facebook Chat API for Node.js with Auto-Update System - Enhanced by ST | Sheikh Tamim",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -46,7 +46,9 @@
46
46
  "update-function",
47
47
  "unofficial-facebook-chat-api",
48
48
  "messengerwapper",
49
- "e2ee"
49
+ "e2ee",
50
+ "e2eefca",
51
+ "e2eesendmessage"
50
52
  ],
51
53
  "author": "ST | Sheikh Tamim",
52
54
  "license": "MIT",
@@ -62,14 +64,14 @@
62
64
  "duplexify": "^4.1.3",
63
65
  "gradient-string": "^2.0.2",
64
66
  "https-proxy-agent": "^7.0.6",
67
+ "koffi": "^3.0.2",
65
68
  "mime": "^3.0.0",
66
69
  "mqtt": "^5.10.1",
67
70
  "npmlog": "^1.2.0",
68
71
  "request": "^2.88.2",
69
72
  "totp-generator": "^1.0.0",
70
- "ws": "^8.18.1"
71
- },
72
- "engines": {
73
- "node": ">=16.0.0"
73
+ "undici": "^8.4.0",
74
+ "ws": "^8.18.1",
75
+ "yumi-json-bigint": "^1.0.0"
74
76
  }
75
77
  }
package/src/OldMessage.js CHANGED
@@ -329,7 +329,7 @@ module.exports = function (defaultFuncs, api, ctx) {
329
329
  handleUrl(msg, form, callback, () =>
330
330
  handleEmoji(msg, form, callback, () =>
331
331
  handleMention(msg, form, callback, () =>
332
- sendContent(form, threadID, isSingleUser, messageAndOTID, callback)
332
+ sendContent(form, threadID, isGroup, messageAndOTID, callback)
333
333
  )
334
334
  )
335
335
  )
@@ -344,7 +344,7 @@ module.exports = function (defaultFuncs, api, ctx) {
344
344
  handleUrl(msg, form, callback, () =>
345
345
  handleEmoji(msg, form, callback, () =>
346
346
  handleMention(msg, form, callback, () =>
347
- sendContent(form, threadID, isSingleUser, messageAndOTID, callback)
347
+ sendContent(form, threadID, isGroup, messageAndOTID, callback)
348
348
  )
349
349
  )
350
350
  )
@@ -23,6 +23,19 @@ function canBeCalled(func) {
23
23
 
24
24
  module.exports = function (defaultFuncs, api, ctx) {
25
25
  return function editMessage(text, messageID, callback) {
26
+ // ── editMessageE2EE: route E2EE messages through the bridge ──────────────
27
+ if (ctx.globalOptions && ctx.globalOptions.enableE2EE) {
28
+ var _e2eeMod = require('../e2ee');
29
+ var _jid = global._e2eeMessageMap && global._e2eeMessageMap.get(String(messageID));
30
+ if (_jid && _e2eeMod.isE2EEChatJid(_jid)) {
31
+ var _p = _e2eeMod.createBridge(ctx).editMessage(_jid, messageID, text)
32
+ .then(function (r) { if (typeof callback === "function") callback(null, r); return r; })
33
+ .catch(function (e) { if (typeof callback === "function") callback(e); throw e; });
34
+ return _p;
35
+ }
36
+ }
37
+ // ── end editMessageE2EE ───────────────────────────────────────────────────
38
+
26
39
  if (!ctx.mqttClient) {
27
40
  throw new Error('Not connected to MQTT');
28
41
  }
@@ -58,7 +71,7 @@ module.exports = function (defaultFuncs, api, ctx) {
58
71
  context.payload = JSON.stringify(context.payload);
59
72
 
60
73
  // if (canBeCalled(callback)) {
61
- // ctx.reqCallbacks[ctx.wsReqNumber] = callback;
74
+ // ctx.reqCallbacks[ctx.wsReqNumber] = callback;
62
75
  // }
63
76
 
64
77
  ctx.mqttClient.publish('/ls_req', JSON.stringify(context), {
package/src/listenMqtt.js CHANGED
@@ -6,6 +6,7 @@ var mqtt = require('mqtt');
6
6
  var WebSocket = require('ws');
7
7
  var Transform = require('stream').Transform;
8
8
  const EventEmitter = require('events');
9
+ var e2eeBridge = require("../e2ee");
9
10
 
10
11
  // ─── ANSI colour helpers ───────────────────────────────────────────────────────
11
12
  var C = {
@@ -399,6 +400,19 @@ function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
399
400
  clearTimeout(rTimeout);
400
401
  if (ctx.globalOptions.emitReady) globalCallback({ type: "ready", error: null });
401
402
  delete ctx.tmsWait;
403
+
404
+ // ── E2EE bridge init ──────────────────────────────────────────────────
405
+ ctx._globalCallback = globalCallback;
406
+ if (ctx.globalOptions.enableE2EE === true) {
407
+ var bridge = e2eeBridge.createBridge(ctx);
408
+ if (global.GoatBot) global.GoatBot._e2eeBridge = bridge;
409
+ bridge.connect(globalCallback).catch(function (err) {
410
+ log.error("listenMqtt", "E2EE bridge connect error:", err && err.message ? err.message : err);
411
+ });
412
+ } else {
413
+ if (global.GoatBot) global.GoatBot._e2eeBridge = null;
414
+ }
415
+ // ── end E2EE bridge init ──────────────────────────────────────────────
402
416
  };
403
417
  });
404
418
 
@@ -885,4 +899,4 @@ module.exports = function (defaultFuncs, api, ctx) {
885
899
  api.stopListeningAsync = msgEmitter.stopListeningAsync.bind(msgEmitter);
886
900
  return msgEmitter;
887
901
  };
888
- };
902
+ };
@@ -260,6 +260,98 @@ module.exports = function (defaultFuncs, api, ctx) {
260
260
  callback = function () { };
261
261
  }
262
262
 
263
+ // ── E2EE routing – sendMessageE2EE + sendMediaE2EE + downloadE2EEMedia ──
264
+ // When the destination thread is an E2EE JID (contains "@"), route through
265
+ // the Labyrinth native bridge instead of the MQTT/HTTP path.
266
+ // (Also exposes api.downloadE2EEMedia for decrypting received attachments.)
267
+ var _e2eeMod = require('../e2ee');
268
+ // Route to E2EE bridge whenever threadID is a JID — the "@" is definitive.
269
+ // Also respect explicit enableE2EE flag as a fallback check.
270
+ if (_e2eeMod.isE2EEChatJid(String(threadID))) {
271
+ var _bridge = _e2eeMod.createBridge(ctx);
272
+ var _form = typeof msg === "string" ? { body: msg } : (msg || {});
273
+ var _text = String(_form.body || _form.text || "");
274
+ var _atts = !_form.attachment ? []
275
+ : (Array.isArray(_form.attachment) ? _form.attachment : [_form.attachment]);
276
+ var _sendOpts = {};
277
+ if (replyToMessage) {
278
+ _sendOpts.replyToId = String(replyToMessage);
279
+ var _rjid = global._e2eeSenderJidMap && global._e2eeSenderJidMap.get(String(replyToMessage));
280
+ if (_rjid) _sendOpts.replyToSenderJid = _rjid;
281
+ }
282
+
283
+ var _e2eePromise = (async function () {
284
+ var _last;
285
+ // sendMediaE2EE: send each attachment
286
+ for (var _i = 0; _i < _atts.length; _i++) {
287
+ var _att = _atts[_i]; if (!_att) continue;
288
+ try {
289
+ var _buf;
290
+ if (Buffer.isBuffer(_att)) {
291
+ _buf = _att;
292
+ } else if (_att && typeof _att.read === "function") {
293
+ _buf = await new Promise(function (res, rej) {
294
+ var chunks = [];
295
+ _att.on("data", function (c) { chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c)); });
296
+ _att.on("end", function () { res(Buffer.concat(chunks)); });
297
+ _att.on("error", rej);
298
+ });
299
+ } else if (_att && _att.type === "Buffer" && Array.isArray(_att.data)) {
300
+ _buf = Buffer.from(_att.data);
301
+ } else { continue; }
302
+
303
+ var _mt = (_att.mediaType ? String(_att.mediaType).toLowerCase() : null)
304
+ || (function () {
305
+ var p = String(_att.path || _att.filename || "").split(".").pop().toLowerCase();
306
+ if (["jpg","jpeg","png","gif","webp","bmp"].includes(p)) return "image";
307
+ if (["mp4","mov","avi","mkv","webm"].includes(p)) return "video";
308
+ if (["mp3","ogg","oga","opus","wav","m4a","aac","flac"].includes(p)) return "audio";
309
+ return "document";
310
+ })();
311
+ var _mOpts = Object.assign({}, _sendOpts);
312
+ if (_i === 0 && _text) _mOpts.caption = _text;
313
+ if (!_mOpts.mimeType && _att.mimeType) _mOpts.mimeType = _att.mimeType;
314
+ if ((_mt === "file" || _mt === "document") && !_mOpts.filename)
315
+ _mOpts.filename = (_att.filename || _att.path || "file.bin").split(/[\\/]/).pop();
316
+ if (_att.duration != null) _mOpts.duration = Number(_att.duration);
317
+ if (_att.width != null) _mOpts.width = Number(_att.width);
318
+ if (_att.height != null) _mOpts.height = Number(_att.height);
319
+ if (_att.ptt || _att.voice) _mOpts.ptt = true;
320
+
321
+ var _mRes = await _bridge.sendMedia(threadID, _mt, _buf, _mOpts);
322
+ _last = { threadID: threadID, messageID: _mRes && _mRes.messageId ? String(_mRes.messageId) : undefined, isE2EE: true };
323
+ if (_last.messageID) {
324
+ global._e2eeMessageMap = global._e2eeMessageMap || new Map();
325
+ global._e2eeMessageMap.set(_last.messageID, String(threadID));
326
+ global._e2eeBotSentMsgIds = global._e2eeBotSentMsgIds || new Set();
327
+ global._e2eeBotSentMsgIds.add(_last.messageID);
328
+ }
329
+ } catch (_me) { log.error("E2EE", "sendMedia att#" + _i + " failed:", _me && _me.message ? _me.message : _me); }
330
+ }
331
+ // sendMessageE2EE: send text (if no attachments, or text wasn't used as caption)
332
+ if (!_last || _atts.length === 0) {
333
+ var _tRes = await _bridge.sendMessage(threadID, _text || "\u200b", _sendOpts);
334
+ _last = { threadID: threadID, messageID: _tRes && _tRes.messageId ? String(_tRes.messageId) : undefined, isE2EE: true };
335
+ if (_last.messageID) {
336
+ global._e2eeMessageMap = global._e2eeMessageMap || new Map();
337
+ global._e2eeMessageMap.set(_last.messageID, String(threadID));
338
+ global._e2eeBotSentMsgIds = global._e2eeBotSentMsgIds || new Set();
339
+ global._e2eeBotSentMsgIds.add(_last.messageID);
340
+ }
341
+ }
342
+ return _last;
343
+ })();
344
+
345
+ // downloadE2EEMedia: exposed on api for received attachment decryption
346
+ if (typeof api.downloadE2EEMedia !== "function") {
347
+ api.downloadE2EEMedia = function (options) { return _bridge.downloadMedia(options); };
348
+ }
349
+
350
+ _e2eePromise.then(function (r) { callback(null, r); }).catch(function (e) { callback(e); });
351
+ return _e2eePromise;
352
+ }
353
+ // ── end E2EE routing ───────────────────────────────────────────────────────
354
+
263
355
  var msgType = utils.getType(msg);
264
356
  var threadIDType = utils.getType(threadID);
265
357
 
@@ -275,9 +367,15 @@ module.exports = function (defaultFuncs, api, ctx) {
275
367
  var configSource = (global.GoatBot && global.GoatBot.config) ? global.GoatBot.config : (ctx.config || {});
276
368
  var enableTypingIndicator = typeof configSource.enableTypingIndicator !== 'undefined' ? configSource.enableTypingIndicator : (ctx.config && ctx.config.enableTypingIndicator);
277
369
  var typingDuration = Number(configSource.typingDuration || (ctx.config && ctx.config.typingDuration) || 4000);
370
+ // E2EE threads need a longer visible duration — 1 second disappears before
371
+ // Messenger renders it. Use e2eeTypingDuration from config (default 3000 ms).
372
+ var _isE2EEThread = require('../e2ee').isE2EEChatJid(String(threadID));
373
+ if (_isE2EEThread) {
374
+ typingDuration = Number(configSource.e2eeTypingDuration || (ctx.config && ctx.config.e2eeTypingDuration) || 3000);
375
+ }
278
376
 
279
377
  if (enableTypingIndicator) {
280
- await api.sendTypingIndicator(true, threadID, function () { });
378
+ await api.sendTypingIndicator(true, threadID, function () { }).catch(function () { });
281
379
  await utils.delay(typingDuration);
282
380
  }
283
381
 
@@ -1,45 +1,54 @@
1
- "use strict";
2
-
3
-
4
-
5
- var utils = require("../utils");
6
- // @NethWs3Dev
7
-
8
- module.exports = function (defaultFuncs, api, ctx) {
9
- return async function sendTypingIndicatorV2(sendTyping, threadID, callback) {
10
- const mqttClient = ctx.mqttClient || global.mqttClient;
11
- if (!mqttClient) {
12
- if (typeof callback === 'function') callback(new Error('No MQTT client available for typing indicator'));
13
- return;
14
- }
15
-
16
- let count_req = 0;
17
- var wsContent = {
18
- app_id: 2220391788200892,
19
- payload: JSON.stringify({
20
- label: 3,
21
- payload: JSON.stringify({
22
- thread_key: threadID.toString(),
23
- is_group_thread: +(threadID.toString().length >= 16),
24
- is_typing: +sendTyping,
25
- attribution: 0
26
- }),
27
- version: 5849951561777440
28
- }),
29
- request_id: ++count_req,
30
- type: 4
31
- };
32
-
33
- return new Promise((resolve, reject) => {
34
- mqttClient.publish('/ls_req', JSON.stringify(wsContent), {}, (err, _packet) => {
35
- if (err) {
36
- if (typeof callback === 'function') callback(err);
37
- reject(err);
38
- } else {
39
- if (typeof callback === 'function') callback(null, _packet);
40
- resolve(_packet);
41
- }
42
- });
43
- });
44
- };
45
- };
1
+ "use strict";
2
+
3
+ var utils = require("../utils");
4
+ // @NethWs3Dev
5
+
6
+ module.exports = function (defaultFuncs, api, ctx) {
7
+ return async function sendTypingIndicatorV2(sendTyping, threadID, callback) {
8
+ // ── sendTypingE2EE: route E2EE typing indicator through the bridge ──────
9
+ if (ctx.globalOptions && ctx.globalOptions.enableE2EE) {
10
+ var _e2eeMod = require('../e2ee');
11
+ if (_e2eeMod.isE2EEChatJid(String(threadID))) {
12
+ return _e2eeMod.createBridge(ctx).sendTyping(threadID, sendTyping !== false)
13
+ .then(function (r) { if (typeof callback === 'function') callback(null, r); return r; })
14
+ .catch(function (e) { if (typeof callback === 'function') callback(e); });
15
+ }
16
+ }
17
+ // ── end sendTypingE2EE ────────────────────────────────────────────────────
18
+
19
+ const mqttClient = ctx.mqttClient || global.mqttClient;
20
+ if (!mqttClient) {
21
+ if (typeof callback === 'function') callback(new Error('No MQTT client available for typing indicator'));
22
+ return;
23
+ }
24
+
25
+ let count_req = 0;
26
+ var wsContent = {
27
+ app_id: 2220391788200892,
28
+ payload: JSON.stringify({
29
+ label: 3,
30
+ payload: JSON.stringify({
31
+ thread_key: threadID.toString(),
32
+ is_group_thread: +(threadID.toString().length >= 16),
33
+ is_typing: +sendTyping,
34
+ attribution: 0
35
+ }),
36
+ version: 5849951561777440
37
+ }),
38
+ request_id: ++count_req,
39
+ type: 4
40
+ };
41
+
42
+ return new Promise((resolve, reject) => {
43
+ mqttClient.publish('/ls_req', JSON.stringify(wsContent), {}, (err, _packet) => {
44
+ if (err) {
45
+ if (typeof callback === 'function') callback(err);
46
+ reject(err);
47
+ } else {
48
+ if (typeof callback === 'function') callback(null, _packet);
49
+ resolve(_packet);
50
+ }
51
+ });
52
+ });
53
+ };
54
+ };
@@ -26,6 +26,31 @@ module.exports = function (defaultFuncs, api, ctx) {
26
26
  };
27
27
  }
28
28
 
29
+ // ── sendReactionE2EE: route E2EE reactions through the bridge ─────────────
30
+ if (ctx.globalOptions && ctx.globalOptions.enableE2EE) {
31
+ var _e2eeMod = require('../e2ee');
32
+ var _jid = global._e2eeMessageMap && global._e2eeMessageMap.get(String(messageID));
33
+ if (_jid && _e2eeMod.isE2EEChatJid(_jid)) {
34
+ var _senderJid = (global._e2eeSenderJidMap && global._e2eeSenderJidMap.get(String(messageID))) || null;
35
+ // Always route through the bridge when the JID is confirmed E2EE —
36
+ // never fall through to the HTTP path which cannot reach E2EE threads.
37
+ // senderJid is required by the E2EE protocol; fall back to chatJid for DMs
38
+ // (in a DM the chatJid IS the other person's JID).
39
+ var _effectiveSender = _senderJid || _jid;
40
+ _e2eeMod.createBridge(ctx).sendReaction(_jid, messageID, _effectiveSender, reaction)
41
+ .then(function (r) { callback(null, r); resolveFunc(r); })
42
+ .catch(function (e) {
43
+ var log = require('../utils').log;
44
+ log.error("setMessageReaction", "E2EE sendReaction failed — jid:" + _jid +
45
+ " msgID:" + messageID + " sender:" + _effectiveSender + " emoji:" + reaction +
46
+ " err:" + (e && e.message ? e.message : String(e)));
47
+ callback(e); rejectFunc(e);
48
+ });
49
+ return returnPromise;
50
+ }
51
+ }
52
+ // ── end sendReactionE2EE ──────────────────────────────────────────────────
53
+
29
54
  switch (reaction) {
30
55
  case "\uD83D\uDE0D": //:heart_eyes:
31
56
  case "\uD83D\uDE06": //:laughing:
@@ -21,6 +21,27 @@ module.exports = function (defaultFuncs, api, ctx) {
21
21
  };
22
22
  }
23
23
 
24
+ // ── unsendMessageE2EE: route E2EE unsend through the bridge ─────────────
25
+ if (ctx.globalOptions && ctx.globalOptions.enableE2EE) {
26
+ var _e2eeMod = require('../e2ee');
27
+ var _jid = global._e2eeMessageMap && global._e2eeMessageMap.get(String(messageID));
28
+ // Fallback: if messageID not in map but a pending JID was set externally, use it
29
+ if (!_jid && global._e2eePendingUnsendJid) {
30
+ var _pjid = global._e2eePendingUnsendJid[String(messageID)];
31
+ if (_pjid) {
32
+ _jid = _pjid;
33
+ delete global._e2eePendingUnsendJid[String(messageID)];
34
+ }
35
+ }
36
+ if (_jid && _e2eeMod.isE2EEChatJid(_jid)) {
37
+ _e2eeMod.createBridge(ctx).unsendMessage(_jid, messageID)
38
+ .then(function (r) { callback(null, r); resolveFunc(r); })
39
+ .catch(function (e) { callback(e); rejectFunc(e); });
40
+ return returnPromise;
41
+ }
42
+ }
43
+ // ── end unsendMessageE2EE ─────────────────────────────────────────────────
44
+
24
45
  const form = {
25
46
  message_id: messageID,
26
47
  };