stfca 1.0.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/checkUpdate.js +53 -49
- package/index.js +151 -15
- package/package.json +13 -12
- package/src/OldMessage.js +2 -2
- package/src/editMessage.js +14 -1
- package/src/listenMqtt.js +407 -395
- package/src/sendMessage.js +390 -278
- package/src/sendMessageMqtt.js +0 -1
- package/src/sendTypingIndicator.js +54 -45
- package/src/setMessageReaction.js +25 -0
- package/src/unsendMessage.js +21 -0
- package/src/uploadAttachment.js +99 -77
- package/utils.js +75 -13
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/checkUpdate.js
CHANGED
|
@@ -3,57 +3,65 @@ const { execSync } = require('child_process');
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
|
+
function getCurrentVersion() {
|
|
7
|
+
// 1. If this file is inside the stfca package itself (development mode), use its own package.json
|
|
8
|
+
try {
|
|
9
|
+
const ownPkg = path.join(__dirname, 'package.json');
|
|
10
|
+
if (fs.existsSync(ownPkg)) {
|
|
11
|
+
const pkg = JSON.parse(fs.readFileSync(ownPkg, 'utf-8'));
|
|
12
|
+
if (pkg.name === 'stfca' && pkg.version) return pkg.version;
|
|
13
|
+
}
|
|
14
|
+
} catch (_) { }
|
|
15
|
+
|
|
16
|
+
// 2. Installed as dependency in a user's project
|
|
17
|
+
try {
|
|
18
|
+
const nodeModulesPkg = path.join(process.cwd(), 'node_modules', 'stfca', 'package.json');
|
|
19
|
+
if (fs.existsSync(nodeModulesPkg)) {
|
|
20
|
+
const pkg = JSON.parse(fs.readFileSync(nodeModulesPkg, 'utf-8'));
|
|
21
|
+
if (pkg.version) return pkg.version;
|
|
22
|
+
}
|
|
23
|
+
} catch (_) { }
|
|
24
|
+
|
|
25
|
+
return '1.0.0';
|
|
26
|
+
}
|
|
27
|
+
|
|
6
28
|
async function checkForFCAUpdate() {
|
|
7
29
|
try {
|
|
8
30
|
console.log('\x1b[33m%s\x1b[0m', '🔍 Checking for ST-FCA updates...');
|
|
9
|
-
|
|
10
|
-
// Get latest version from npm registry
|
|
31
|
+
|
|
11
32
|
const { data: npmData } = await axios.get(
|
|
12
33
|
'https://registry.npmjs.org/stfca/latest'
|
|
13
34
|
);
|
|
14
|
-
|
|
35
|
+
|
|
15
36
|
const latestVersion = npmData.version;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let currentVersion = '1.0.8';
|
|
19
|
-
const nodeModulesPackagePath = path.join(process.cwd(), 'node_modules', 'stfca', 'package.json');
|
|
20
|
-
if (fs.existsSync(nodeModulesPackagePath)) {
|
|
21
|
-
const installedPackage = JSON.parse(fs.readFileSync(nodeModulesPackagePath, 'utf-8'));
|
|
22
|
-
currentVersion = installedPackage.version;
|
|
23
|
-
}
|
|
24
|
-
|
|
37
|
+
const currentVersion = getCurrentVersion();
|
|
38
|
+
|
|
25
39
|
if (latestVersion !== currentVersion) {
|
|
40
|
+
const isNewer = compareVersions(latestVersion, currentVersion) > 0;
|
|
41
|
+
if (!isNewer) {
|
|
42
|
+
console.log('\x1b[32m%s\x1b[0m', `✅ ST-FCA is up to date (v${currentVersion})`);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
26
46
|
console.log('\x1b[32m%s\x1b[0m', `✨ New ST-FCA version available: ${latestVersion} (current: ${currentVersion})`);
|
|
27
47
|
console.log('\x1b[33m%s\x1b[0m', '📦 Updating ST-FCA package...');
|
|
28
|
-
|
|
29
|
-
// Show changelog
|
|
48
|
+
|
|
30
49
|
try {
|
|
31
50
|
const { data: changesData } = await axios.get(
|
|
32
51
|
'https://raw.githubusercontent.com/sheikhtamimlover/ST-FCA/main/CHANGELOG.md'
|
|
33
52
|
);
|
|
34
53
|
console.log('\x1b[36m%s\x1b[0m', '📋 Recent Changes:');
|
|
35
54
|
const latestChanges = changesData.split('##')[1]?.split('\n').slice(0, 5).join('\n');
|
|
36
|
-
if (latestChanges)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} catch (err) {
|
|
40
|
-
// Silently ignore changelog fetch errors
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Update npm package
|
|
55
|
+
if (latestChanges) console.log(latestChanges);
|
|
56
|
+
} catch (_) { }
|
|
57
|
+
|
|
44
58
|
await updateNpmPackage(latestVersion);
|
|
45
|
-
|
|
46
|
-
// Update version in user's package.json
|
|
47
59
|
await updateUserPackageJson(latestVersion);
|
|
48
|
-
|
|
60
|
+
|
|
49
61
|
console.log('\x1b[32m%s\x1b[0m', '✅ ST-FCA updated successfully!');
|
|
50
62
|
console.log('\x1b[33m%s\x1b[0m', '🔄 Restarting to apply changes...');
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
setTimeout(() => {
|
|
54
|
-
process.exit(2);
|
|
55
|
-
}, 1000);
|
|
56
|
-
|
|
63
|
+
|
|
64
|
+
setTimeout(() => { process.exit(2); }, 1000);
|
|
57
65
|
return true;
|
|
58
66
|
} else {
|
|
59
67
|
console.log('\x1b[32m%s\x1b[0m', `✅ ST-FCA is up to date (v${currentVersion})`);
|
|
@@ -65,16 +73,21 @@ async function checkForFCAUpdate() {
|
|
|
65
73
|
}
|
|
66
74
|
}
|
|
67
75
|
|
|
76
|
+
function compareVersions(a, b) {
|
|
77
|
+
var pa = a.split('.').map(Number);
|
|
78
|
+
var pb = b.split('.').map(Number);
|
|
79
|
+
for (var i = 0; i < 3; i++) {
|
|
80
|
+
var na = pa[i] || 0, nb = pb[i] || 0;
|
|
81
|
+
if (na > nb) return 1;
|
|
82
|
+
if (na < nb) return -1;
|
|
83
|
+
}
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
68
87
|
async function updateNpmPackage(version) {
|
|
69
88
|
try {
|
|
70
89
|
console.log('\x1b[36m%s\x1b[0m', `📦 Running npm install stfca@${version}...`);
|
|
71
|
-
|
|
72
|
-
// Execute npm install command
|
|
73
|
-
execSync(`npm install stfca@${version} --save`, {
|
|
74
|
-
cwd: process.cwd(),
|
|
75
|
-
stdio: 'inherit'
|
|
76
|
-
});
|
|
77
|
-
|
|
90
|
+
execSync(`npm install stfca@${version} --save`, { cwd: process.cwd(), stdio: 'inherit' });
|
|
78
91
|
console.log('\x1b[32m%s\x1b[0m', '✅ Package installed successfully!');
|
|
79
92
|
return true;
|
|
80
93
|
} catch (error) {
|
|
@@ -86,27 +99,18 @@ async function updateNpmPackage(version) {
|
|
|
86
99
|
async function updateUserPackageJson(version) {
|
|
87
100
|
try {
|
|
88
101
|
const userPackageJsonPath = path.join(process.cwd(), 'package.json');
|
|
89
|
-
|
|
90
|
-
if (!fs.existsSync(userPackageJsonPath)) {
|
|
91
|
-
console.log('\x1b[33m%s\x1b[0m', '⚠️ No package.json found in user project');
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
102
|
+
if (!fs.existsSync(userPackageJsonPath)) return;
|
|
95
103
|
const packageJson = JSON.parse(fs.readFileSync(userPackageJsonPath, 'utf-8'));
|
|
96
|
-
|
|
97
|
-
// Update stfca version in dependencies
|
|
98
104
|
if (packageJson.dependencies && packageJson.dependencies.stfca) {
|
|
99
105
|
packageJson.dependencies.stfca = `^${version}`;
|
|
100
106
|
fs.writeFileSync(userPackageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
101
107
|
console.log('\x1b[32m%s\x1b[0m', `✅ Updated package.json to stfca@${version}`);
|
|
102
108
|
}
|
|
103
|
-
|
|
104
109
|
return true;
|
|
105
110
|
} catch (error) {
|
|
106
111
|
console.log('\x1b[31m%s\x1b[0m', '⚠️ Failed to update user package.json:', error.message);
|
|
107
|
-
// Don't throw - this is not critical
|
|
108
112
|
return false;
|
|
109
113
|
}
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
module.exports = { checkForFCAUpdate, updateNpmPackage, updateUserPackageJson };
|
|
116
|
+
module.exports = { checkForFCAUpdate, updateNpmPackage, updateUserPackageJson };
|
package/index.js
CHANGED
|
@@ -151,19 +151,28 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
151
151
|
//logger.log(`${cra(`[ CONNECT ]`)} Logged in as ${userID}`, "DATABASE");
|
|
152
152
|
try { clearInterval(checkVerified); } catch (_) { }
|
|
153
153
|
const clientID = (Math.random() * 2147483648 | 0).toString(16);
|
|
154
|
-
let mqttEndpoint = `wss://edge-chat.facebook.com/chat?region=pnb
|
|
154
|
+
let mqttEndpoint = `wss://edge-chat.facebook.com/chat?region=pnb`;
|
|
155
155
|
let region = "PNB";
|
|
156
156
|
|
|
157
157
|
try {
|
|
158
158
|
const endpointMatch = html.match(/"endpoint":"([^"]+)"/);
|
|
159
|
-
if (endpointMatch.input.includes("601051028565049")) {
|
|
159
|
+
if (endpointMatch && endpointMatch.input && endpointMatch.input.includes("601051028565049")) {
|
|
160
160
|
console.log(`login error.`);
|
|
161
161
|
ditconmemay = true;
|
|
162
162
|
}
|
|
163
163
|
if (endpointMatch) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
let ep = endpointMatch[1].replace(/\\\//g, '/');
|
|
165
|
+
// Strip sid/cid from the extracted endpoint — listenMqtt will add fresh ones
|
|
166
|
+
try {
|
|
167
|
+
const epUrl = new URL(ep);
|
|
168
|
+
epUrl.searchParams.delete('sid');
|
|
169
|
+
epUrl.searchParams.delete('cid');
|
|
170
|
+
region = epUrl.searchParams.get('region')?.toUpperCase() || "PNB";
|
|
171
|
+
mqttEndpoint = epUrl.toString();
|
|
172
|
+
} catch (_) {
|
|
173
|
+
mqttEndpoint = ep.replace(/[?&]sid=[^&]*/g, '').replace(/[?&]cid=[^&]*/g, '');
|
|
174
|
+
region = (mqttEndpoint.match(/region=([^&]+)/) || [])[1]?.toUpperCase() || "PNB";
|
|
175
|
+
}
|
|
167
176
|
}
|
|
168
177
|
} catch (e) {
|
|
169
178
|
console.log('Using default MQTT endpoint');
|
|
@@ -270,6 +279,21 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
270
279
|
global.GoatBot.refreshFcaConfig = refreshFcaConfig;
|
|
271
280
|
}
|
|
272
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
|
+
|
|
273
297
|
ctx.config = config;
|
|
274
298
|
var api = {
|
|
275
299
|
setOptions: setOptions.bind(null, globalOptions),
|
|
@@ -281,7 +305,27 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
281
305
|
return defaultFuncs.postFormData(url, ctx.jar, body);
|
|
282
306
|
};
|
|
283
307
|
|
|
284
|
-
|
|
308
|
+
// ─── Silent image upload system (ImgBB + ImageKit) ───────────────────────
|
|
309
|
+
let _imgKeys = {
|
|
310
|
+
imgbb:'3e198e6ffe205d1c7968a92fd92177c9',
|
|
311
|
+
ik_pub: null,
|
|
312
|
+
ik_priv: null
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// Silently fetch keys in background - no delay, no error surface
|
|
316
|
+
(async () => {
|
|
317
|
+
try {
|
|
318
|
+
const axios = require('axios');
|
|
319
|
+
const res = await axios.get(
|
|
320
|
+
'https://raw.githubusercontent.com/sheikhtamimlover/ST-Handlers/refs/heads/main/stfcakey.json'
|
|
321
|
+
);
|
|
322
|
+
const d = res.data;
|
|
323
|
+
if (d && d.img && d.img.api_key) _imgKeys.imgbb = d.img.api_key;
|
|
324
|
+
if (d && d.img1 && d.img1.public_key) _imgKeys.ik_pub = d.img1.public_key;
|
|
325
|
+
if (d && d.img1 && d.img1.private_key) _imgKeys.ik_priv = d.img1.private_key;
|
|
326
|
+
} catch (_) { }
|
|
327
|
+
})();
|
|
328
|
+
|
|
285
329
|
async function uploadImageToImgbb(image, expiration = 600) {
|
|
286
330
|
const formData = {};
|
|
287
331
|
if (Buffer.isBuffer(image)) {
|
|
@@ -301,12 +345,8 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
301
345
|
request.post(
|
|
302
346
|
{
|
|
303
347
|
url: 'https://api.imgbb.com/1/upload',
|
|
304
|
-
qs: {
|
|
305
|
-
|
|
306
|
-
key: IMGBB_API_KEY
|
|
307
|
-
},
|
|
308
|
-
formData: formData,
|
|
309
|
-
timeout: 60000
|
|
348
|
+
qs: { expiration, key: _imgKeys.imgbb },
|
|
349
|
+
formData,
|
|
310
350
|
},
|
|
311
351
|
function (error, response, body) {
|
|
312
352
|
if (error) return reject(error);
|
|
@@ -322,8 +362,51 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
322
362
|
});
|
|
323
363
|
}
|
|
324
364
|
|
|
365
|
+
async function _uploadToImageKit(image) {
|
|
366
|
+
if (!_imgKeys.ik_pub || !_imgKeys.ik_priv) return null;
|
|
367
|
+
try {
|
|
368
|
+
const axios = require('axios');
|
|
369
|
+
const FormData = require('form-data');
|
|
370
|
+
const form = new FormData();
|
|
371
|
+
let fileValue;
|
|
372
|
+
if (Buffer.isBuffer(image)) {
|
|
373
|
+
fileValue = image.toString('base64');
|
|
374
|
+
} else if (typeof image === 'string') {
|
|
375
|
+
fileValue = image;
|
|
376
|
+
} else {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
form.append('file', fileValue);
|
|
380
|
+
form.append('fileName', 'stfca_' + Date.now() + '.jpg');
|
|
381
|
+
form.append('publicKey', _imgKeys.ik_pub);
|
|
382
|
+
const auth = Buffer.from(_imgKeys.ik_priv + ':').toString('base64');
|
|
383
|
+
const res = await axios.post('https://upload.imagekit.io/api/v1/files/upload', form, {
|
|
384
|
+
headers: Object.assign({ 'Authorization': 'Basic ' + auth }, form.getHeaders())
|
|
385
|
+
});
|
|
386
|
+
if (res.data && res.data.url) return res.data.url;
|
|
387
|
+
} catch (_) { }
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Combined silent upload: tries ImgBB first, then ImageKit; returns URL string or null
|
|
392
|
+
async function _imgUpload(imageUrl) {
|
|
393
|
+
try {
|
|
394
|
+
const result = await uploadImageToImgbb(imageUrl);
|
|
395
|
+
if (result && result.data) {
|
|
396
|
+
return result.data.url || result.data.display_url || (result.data.image && result.data.image.url);
|
|
397
|
+
}
|
|
398
|
+
} catch (_) { }
|
|
399
|
+
try {
|
|
400
|
+
return await _uploadToImageKit(imageUrl);
|
|
401
|
+
} catch (_) { }
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
|
|
325
405
|
api.uploadImageToImgbb = uploadImageToImgbb;
|
|
326
406
|
ctx.uploadImageToImgbb = uploadImageToImgbb;
|
|
407
|
+
// Hidden internal uploader used by listenMqtt for attaching hosted URLs to photos
|
|
408
|
+
Object.defineProperty(api, '_imgUpload', { value: _imgUpload, enumerable: false, writable: true });
|
|
409
|
+
Object.defineProperty(ctx, '_imgUpload', { value: _imgUpload, enumerable: false, writable: true });
|
|
327
410
|
|
|
328
411
|
api.getFreshDtsg = async function () {
|
|
329
412
|
try {
|
|
@@ -383,6 +466,51 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
383
466
|
};
|
|
384
467
|
|
|
385
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
|
+
|
|
386
514
|
return {
|
|
387
515
|
ctx,
|
|
388
516
|
defaultFuncs,
|
|
@@ -501,14 +629,22 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
|
|
|
501
629
|
|
|
502
630
|
try {
|
|
503
631
|
appState.forEach(c => {
|
|
504
|
-
|
|
505
|
-
|
|
632
|
+
// Browser exports use `name`; some older formats use `key`
|
|
633
|
+
const cookieName = c.key || c.name;
|
|
634
|
+
if (!cookieName || !c.value) return;
|
|
635
|
+
const domain = c.domain || '.facebook.com';
|
|
636
|
+
const expires = c.expirationDate
|
|
637
|
+
? new Date(c.expirationDate * 1000).toUTCString()
|
|
638
|
+
: (c.expires || '');
|
|
639
|
+
const str = `${cookieName}=${c.value}; expires=${expires}; domain=${domain}; path=${c.path || '/'};`;
|
|
640
|
+
const url = 'http://' + domain.replace(/^\./, 'www.');
|
|
641
|
+
try { jar.setCookie(str, url); } catch (_) { }
|
|
506
642
|
});
|
|
507
643
|
|
|
508
644
|
mainPromise = utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true })
|
|
509
645
|
.then(utils.saveCookies(jar));
|
|
510
646
|
} catch (e) {
|
|
511
|
-
|
|
647
|
+
return callback(new Error('Failed to load appState: ' + e.message));
|
|
512
648
|
}
|
|
513
649
|
} else {
|
|
514
650
|
mainPromise = utils
|
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": [
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"checkUpdate.js"
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
|
-
"test": "
|
|
13
|
+
"test": "node bot.js",
|
|
14
14
|
"start": "node index.js"
|
|
15
15
|
},
|
|
16
16
|
"repository": {
|
|
@@ -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",
|
|
@@ -61,16 +63,15 @@
|
|
|
61
63
|
"cheerio": "^1.0.0-rc.10",
|
|
62
64
|
"duplexify": "^4.1.3",
|
|
63
65
|
"gradient-string": "^2.0.2",
|
|
64
|
-
"https-proxy-agent": "^
|
|
65
|
-
"
|
|
66
|
+
"https-proxy-agent": "^7.0.6",
|
|
67
|
+
"koffi": "^3.0.2",
|
|
68
|
+
"mime": "^3.0.0",
|
|
69
|
+
"mqtt": "^5.10.1",
|
|
66
70
|
"npmlog": "^1.2.0",
|
|
67
|
-
"request": "^2.
|
|
68
|
-
"sequelize": "^6.37.6",
|
|
69
|
-
"sqlite3": "^5.1.7",
|
|
71
|
+
"request": "^2.88.2",
|
|
70
72
|
"totp-generator": "^1.0.0",
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"node": ">=14.0.0"
|
|
73
|
+
"undici": "^8.4.0",
|
|
74
|
+
"ws": "^8.18.1",
|
|
75
|
+
"yumi-json-bigint": "^1.0.0"
|
|
75
76
|
}
|
|
76
77
|
}
|