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 +226 -2
- package/index.js +60 -0
- package/package.json +8 -6
- package/src/OldMessage.js +2 -2
- package/src/editMessage.js +14 -1
- package/src/listenMqtt.js +15 -1
- package/src/sendMessage.js +99 -1
- package/src/sendTypingIndicator.js +54 -45
- package/src/setMessageReaction.js +25 -0
- package/src/unsendMessage.js +21 -0
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
|
-
- 🔐
|
|
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
|
-
##
|
|
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.
|
|
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
|
-
"
|
|
71
|
-
|
|
72
|
-
|
|
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,
|
|
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,
|
|
347
|
+
sendContent(form, threadID, isGroup, messageAndOTID, callback)
|
|
348
348
|
)
|
|
349
349
|
)
|
|
350
350
|
)
|
package/src/editMessage.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
+
};
|
package/src/sendMessage.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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:
|
package/src/unsendMessage.js
CHANGED
|
@@ -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
|
};
|