shadowx-fca 2.1.0 โ†’ 2.2.0

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 (3) hide show
  1. package/README.md +457 -0
  2. package/index.js +110 -68
  3. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,457 @@
1
+ Here's a comprehensive README.md for your shadowx-fca package:
2
+
3
+ ```markdown
4
+ # shadowx-fca
5
+
6
+ <div align="center">
7
+ <img src="https://img.shields.io/npm/v/shadowx-fca.svg?style=for-the-badge" alt="NPM Version">
8
+ <img src="https://img.shields.io/npm/dt/shadowx-fca.svg?style=for-the-badge" alt="NPM Downloads">
9
+ <img src="https://img.shields.io/github/license/mueidmursalinrifat/shadowx-fca.svg?style=for-the-badge" alt="License">
10
+ <img src="https://img.shields.io/badge/node-%3E%3D14.0.0-brightgreen.svg?style=for-the-badge" alt="Node Version">
11
+ </div>
12
+
13
+ <p align="center">
14
+ <strong>Unofficial Facebook Chat API for Node.js with Auto-Update System</strong><br>
15
+ Modified by Mueid Mursalin Rifat | Original by shadowX
16
+ </p>
17
+
18
+ ## ๐Ÿ“‹ Table of Contents
19
+
20
+ - [Features](#-features)
21
+ - [Installation](#-installation)
22
+ - [Quick Start](#-quick-start)
23
+ - [Authentication Methods](#-authentication-methods)
24
+ - [API Documentation](#-api-documentation)
25
+ - [Configuration](#-configuration)
26
+ - [Anti-Detection Features](#-anti-detection-features)
27
+ - [Troubleshooting](#-troubleshooting)
28
+ - [Examples](#-examples)
29
+ - [Changelog](#-changelog)
30
+ - [License](#-license)
31
+
32
+ ## โœจ Features
33
+
34
+ - โœ… **Multiple Authentication Methods** - Support for both appState and email/password login
35
+ - โœ… **Real-time Messaging** - MQTT/WebSocket based real-time message listening
36
+ - โœ… **Auto-Update System** - Automatically checks and updates to latest version
37
+ - โœ… **Anti-Detection** - Rotating user agents, rate limiting, and stealth headers
38
+ - โœ… **2FA Support** - Two-factor authentication handling
39
+ - โœ… **Message Attachments** - Support for images, videos, files, stickers, and more
40
+ - โœ… **Typing Indicators** - Send and receive typing status
41
+ - โœ… **Read Receipts** - Message read confirmation
42
+ - โœ… **Thread Management** - Create, delete, rename threads
43
+ - โœ… **Mention Support** - Tag users in messages (both legacy and new formats)
44
+ - โœ… **Configurable Options** - Extensive customization options
45
+
46
+ ## ๐Ÿ“ฆ Installation
47
+
48
+ ```bash
49
+ npm install shadowx-fca
50
+ ```
51
+
52
+ Requirements
53
+
54
+ ยท Node.js >= 14.0.0
55
+ ยท npm >= 6.0.0
56
+
57
+ ๐Ÿš€ Quick Start
58
+
59
+ Method 1: Using AppState (Recommended)
60
+
61
+ ```javascript
62
+ const login = require('shadowx-fca');
63
+ const fs = require('fs');
64
+
65
+ // Load saved appState
66
+ const appState = JSON.parse(fs.readFileSync('appstate.json', 'utf8'));
67
+
68
+ login({ appState: appState }, (err, api) => {
69
+ if (err) return console.error('Login failed:', err);
70
+
71
+ console.log('โœ… Logged in successfully!');
72
+
73
+ // Listen for incoming messages
74
+ api.listenMqtt((err, message) => {
75
+ if (err) return console.error('Listen error:', err);
76
+
77
+ if (message.type === 'message') {
78
+ console.log(`[${message.senderID}] ${message.body}`);
79
+
80
+ // Reply to message
81
+ api.sendMessage('Hello! I\'m a bot!', message.threadID);
82
+ }
83
+ });
84
+ });
85
+ ```
86
+
87
+ Method 2: Email & Password
88
+
89
+ ```javascript
90
+ const login = require('shadowx-fca');
91
+
92
+ login({
93
+ email: 'your_email@example.com',
94
+ password: 'your_password'
95
+ }, (err, api) => {
96
+ if (err) return console.error('Login failed:', err);
97
+
98
+ console.log('โœ… Logged in successfully!');
99
+
100
+ // Save appState for future use
101
+ const appState = api.getAppState();
102
+ fs.writeFileSync('appstate.json', JSON.stringify(appState, null, 2));
103
+
104
+ // Your bot logic here
105
+ });
106
+ ```
107
+
108
+ ๐Ÿ” Authentication Methods
109
+
110
+ 1. AppState (Recommended)
111
+
112
+ Most secure method - uses saved cookies instead of credentials.
113
+
114
+ Get your appState:
115
+
116
+ ```javascript
117
+ const login = require('shadowx-fca');
118
+ const fs = require('fs');
119
+
120
+ login({ email: 'your_email', password: 'your_password' }, (err, api) => {
121
+ if (err) return console.error(err);
122
+
123
+ const appState = api.getAppState();
124
+ fs.writeFileSync('appstate.json', JSON.stringify(appState, null, 2));
125
+ console.log('โœ… AppState saved! You can now use this file to login.');
126
+ process.exit(0);
127
+ });
128
+ ```
129
+
130
+ 2. Email & Password
131
+
132
+ Traditional method with 2FA support.
133
+
134
+ With 2FA:
135
+
136
+ ```javascript
137
+ login({ email: 'email@example.com', password: 'password' }, (err, api) => {
138
+ if (err && err.error === 'login-approval') {
139
+ console.log('Enter 2FA code:');
140
+ // Get code from user input
141
+ const twoFACode = '123456';
142
+ err.continue(twoFACode);
143
+ }
144
+ });
145
+ ```
146
+
147
+ ๐Ÿ“š API Documentation
148
+
149
+ Core Methods
150
+
151
+ login(loginData, [options], callback)
152
+
153
+ Main login function.
154
+
155
+ Parameters:
156
+
157
+ ยท loginData - Object containing either appState or email/password
158
+ ยท options - Optional configuration object
159
+ ยท callback - Function called after login
160
+
161
+ api.sendMessage(message, threadID, [callback], [replyToMessage], [isSingleUser])
162
+
163
+ Send a message to a thread.
164
+
165
+ ```javascript
166
+ // Simple message
167
+ api.sendMessage('Hello world!', 'thread_id_here');
168
+
169
+ // With reply
170
+ api.sendMessage('Replying to you!', 'thread_id_here', null, 'message_id_here');
171
+
172
+ // With mention
173
+ api.sendMessage('Hello @John Doe', 'thread_id_here');
174
+ ```
175
+
176
+ api.listenMqtt(callback)
177
+
178
+ Listen for incoming events (messages, typing, presence).
179
+
180
+ ```javascript
181
+ api.listenMqtt((err, event) => {
182
+ if (err) return console.error(err);
183
+
184
+ switch(event.type) {
185
+ case 'message':
186
+ console.log('New message:', event.body);
187
+ break;
188
+ case 'event':
189
+ console.log('Thread event:', event.logMessageType);
190
+ break;
191
+ case 'typ':
192
+ console.log(event.isTyping ? 'Typing...' : 'Stopped typing');
193
+ break;
194
+ }
195
+ });
196
+ ```
197
+
198
+ api.getThreadInfo(threadID, callback)
199
+
200
+ Get information about a thread.
201
+
202
+ ```javascript
203
+ api.getThreadInfo('thread_id_here', (err, info) => {
204
+ if (err) return console.error(err);
205
+ console.log('Thread name:', info.name);
206
+ console.log('Participants:', info.participants);
207
+ console.log('Unread count:', info.unreadCount);
208
+ });
209
+ ```
210
+
211
+ api.getUserInfo(userID, callback)
212
+
213
+ Get user information.
214
+
215
+ ```javascript
216
+ api.getUserInfo('user_id_here', (err, info) => {
217
+ if (err) return console.error(err);
218
+ console.log('Name:', info.name);
219
+ console.log('Gender:', info.gender);
220
+ });
221
+ ```
222
+
223
+ api.setOptions(options)
224
+
225
+ Update API options at runtime.
226
+
227
+ ```javascript
228
+ api.setOptions({
229
+ listenEvents: true,
230
+ autoMarkRead: false,
231
+ delayBetweenRequests: 2000
232
+ });
233
+ ```
234
+
235
+ Additional Methods
236
+
237
+ Method Description
238
+ api.getAppState() Get current appState (cookies)
239
+ api.markAsRead(threadID, callback) Mark thread as read
240
+ api.markAsDelivered(threadID, messageID, callback) Mark message as delivered
241
+ api.sendTypingIndicator(threadID, callback) Send typing indicator
242
+ api.changeNickname(nickname, threadID, userID, callback) Change user nickname
243
+ api.addUserToGroup(userID, threadID, callback) Add user to group
244
+ api.removeUserFromGroup(userID, threadID, callback) Remove user from group
245
+ api.createNewGroup(participantIDs, groupName, callback) Create new group
246
+ api.deleteThread(threadID, callback) Delete/leave thread
247
+ api.getThreadList(limit, timestamp, tags, callback) Get thread list
248
+
249
+ โš™๏ธ Configuration
250
+
251
+ Global Configuration (config.json)
252
+
253
+ Create config.json in your project root:
254
+
255
+ ```json
256
+ {
257
+ "enableTypingIndicator": false,
258
+ "typingDuration": 4000,
259
+ "delayBetweenRequests": 1500,
260
+ "autoMarkDelivery": false,
261
+ "autoMarkRead": false,
262
+ "listenEvents": true,
263
+ "selfListen": false,
264
+ "autoReconnect": true,
265
+ "logLevel": "info",
266
+ "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
267
+ }
268
+ ```
269
+
270
+ Options Reference
271
+
272
+ Option Type Default Description
273
+ selfListen Boolean false Listen to own messages
274
+ listenEvents Boolean true Listen to thread events
275
+ listenTyping Boolean false Listen to typing indicators
276
+ updatePresence Boolean false Update online status
277
+ forceLogin Boolean false Force login even if suspicious
278
+ autoMarkDelivery Boolean false Auto-mark messages as delivered
279
+ autoMarkRead Boolean false Auto-mark messages as read
280
+ autoReconnect Boolean true Auto-reconnect on disconnect
281
+ delayBetweenRequests Number 1000 Delay between API requests (ms)
282
+ logLevel String "info" Log level (silent/error/warn/info/verbose)
283
+
284
+ ๐Ÿ›ก๏ธ Anti-Detection Features
285
+
286
+ shadowx-fca includes several features to avoid Facebook's anti-bot detection:
287
+
288
+ 1. Rate Limiting
289
+
290
+ ```javascript
291
+ // Set custom delay between requests
292
+ api.setOptions({ delayBetweenRequests: 2000 });
293
+ ```
294
+
295
+ 2. Rotating User Agents
296
+
297
+ Automatically rotates between different user agents to avoid fingerprinting.
298
+
299
+ 3. Stealth Headers
300
+
301
+ Includes realistic browser headers (Sec-Ch-Ua, Accept-Language, etc.)
302
+
303
+ 4. Request Queue
304
+
305
+ Automatically queues and spaces out requests to prevent rate limiting.
306
+
307
+ ๐Ÿ”ง Troubleshooting
308
+
309
+ Common Issues & Solutions
310
+
311
+ 1. "Error! Your cookiestate is not valid!"
312
+
313
+ Solution: Refresh your appState
314
+
315
+ ```javascript
316
+ // Get new appState
317
+ login({ email: 'your_email', password: 'your_password' }, (err, api) => {
318
+ const newAppState = api.getAppState();
319
+ fs.writeFileSync('appstate.json', JSON.stringify(newAppState, null, 2));
320
+ });
321
+ ```
322
+
323
+ 2. Account Suspension
324
+
325
+ Solutions:
326
+
327
+ ยท Increase delay between requests
328
+ ยท Don't spam messages (add 2-3 second delays)
329
+ ยท Use appState instead of email/password
330
+ ยท Avoid running 24/7
331
+ ยท Use a proxy if running multiple bots
332
+
333
+ 3. Login Timeout
334
+
335
+ Solution: Increase timeout in options
336
+
337
+ ```javascript
338
+ login({ appState: appState }, { timeout: 120000 }, callback);
339
+ ```
340
+
341
+ 4. MQTT Connection Issues
342
+
343
+ Solution: Enable auto-reconnect
344
+
345
+ ```javascript
346
+ api.setOptions({ autoReconnect: true });
347
+ ```
348
+
349
+ ๐Ÿ’ก Examples
350
+
351
+ Simple Echo Bot
352
+
353
+ ```javascript
354
+ const login = require('shadowx-fca');
355
+
356
+ login({ appState: require('./appstate.json') }, (err, api) => {
357
+ if (err) return console.error(err);
358
+
359
+ api.listenMqtt((err, message) => {
360
+ if (err) return console.error(err);
361
+
362
+ if (message.type === 'message' && message.body) {
363
+ // Echo the message back
364
+ api.sendMessage(message.body, message.threadID);
365
+ }
366
+ });
367
+ });
368
+ ```
369
+
370
+ Command Handler
371
+
372
+ ```javascript
373
+ const commands = {
374
+ '!help': (api, message) => {
375
+ api.sendMessage('Available commands: !help, !time, !ping', message.threadID);
376
+ },
377
+ '!time': (api, message) => {
378
+ api.sendMessage(`Current time: ${new Date().toLocaleString()}`, message.threadID);
379
+ },
380
+ '!ping': (api, message) => {
381
+ api.sendMessage('Pong!', message.threadID);
382
+ }
383
+ };
384
+
385
+ login({ appState: require('./appstate.json') }, (err, api) => {
386
+ if (err) return console.error(err);
387
+
388
+ api.listenMqtt((err, message) => {
389
+ if (err) return console.error(err);
390
+
391
+ if (message.type === 'message' && message.body) {
392
+ const cmd = message.body.split(' ')[0];
393
+ if (commands[cmd]) commands[cmd](api, message);
394
+ }
395
+ });
396
+ });
397
+ ```
398
+
399
+ Auto-Reply with Delay
400
+
401
+ ```javascript
402
+ login({ appState: require('./appstate.json') }, (err, api) => {
403
+ if (err) return console.error(err);
404
+
405
+ api.listenMqtt((err, message) => {
406
+ if (err) return console.error(err);
407
+
408
+ if (message.type === 'message' && !message.isGroup) {
409
+ // Add delay to avoid rate limiting
410
+ setTimeout(() => {
411
+ api.sendMessage('Thanks for your message! I\'ll reply soon.', message.threadID);
412
+ }, 3000);
413
+ }
414
+ });
415
+ });
416
+ ```
417
+
418
+ ๐Ÿ“ Changelog
419
+
420
+ v2.1.0 (Latest)
421
+
422
+ ยท โœจ Added rotating user agents for anti-detection
423
+ ยท ๐Ÿš€ Implemented request queue with rate limiting
424
+ ยท ๐Ÿ”ง Fixed email/password login issues
425
+ ยท ๐Ÿ›ก๏ธ Enhanced security headers
426
+ ยท ๐Ÿ“š Improved documentation
427
+
428
+ v2.0.0
429
+
430
+ ยท Added auto-update system
431
+ ยท Improved MQTT connection stability
432
+ ยท Added support for new mention format
433
+ ยท Fixed 2FA handling
434
+
435
+ ๐Ÿ“„ License
436
+
437
+ This project is licensed under the MIT License - see the LICENSE file for details.
438
+
439
+ โš ๏ธ Disclaimer
440
+
441
+ This project is for educational purposes only. Use at your own risk. The authors are not responsible for any consequences that may arise from using this software, including but not limited to account suspension or legal action from Facebook.
442
+
443
+ ๐Ÿค Contributing
444
+
445
+ Contributions are welcome! Please feel free to submit a Pull Request.
446
+
447
+ ๐Ÿ“ž Support
448
+
449
+ ยท Issues: t.me/mueidmursalinrifat
450
+ ยท Author: Mueid Mursalin Rifat
451
+
452
+ ---
453
+
454
+ <div align="center">
455
+ Made with โค๏ธ by Mueid Mursalin Rifat
456
+ </div>
457
+ ```
package/index.js CHANGED
@@ -6,24 +6,31 @@ var log = require("npmlog");
6
6
  var { checkForFCAUpdate } = require("./checkUpdate");
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
- /*var { getThemeColors } = require("../../func/utils/log.js");
10
- var logger = require("../../func/utils/log.js");
11
- var { cra, cv, cb, co } = getThemeColors();*/
9
+
12
10
  log.maxRecordSize = 100;
13
11
  var checkVerified = null;
12
+
14
13
  const Boolean_Option = ['online', 'selfListen', 'listenEvents', 'updatePresence', 'forceLogin', 'autoMarkDelivery', 'autoMarkRead', 'listenTyping', 'autoReconnect', 'emitReady'];
14
+
15
15
  global.ditconmemay = false;
16
16
  global.stfcaUpdateChecked = false;
17
17
 
18
- // Auto-check for updates on package load (non-blocking)
19
- if (!global.stfcaUpdateChecked) {
20
- global.stfcaUpdateChecked = true;
21
- const { checkForFCAUpdate } = require("./checkUpdate");
22
- setImmediate(() => {
23
- checkForFCAUpdate().catch(() => {
24
- // Silent fail - don't interrupt user's bot
25
- });
26
- });
18
+ // Rotating user agents to avoid detection
19
+ const USER_AGENTS = [
20
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
21
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
22
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
23
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
24
+ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
25
+ ];
26
+
27
+ function getRandomUserAgent() {
28
+ return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
29
+ }
30
+
31
+ // Delay function to avoid rate limiting
32
+ function delay(ms) {
33
+ return new Promise(resolve => setTimeout(resolve, ms));
27
34
  }
28
35
 
29
36
  function setOptions(globalOptions, options) {
@@ -55,7 +62,7 @@ function setOptions(globalOptions, options) {
55
62
  break;
56
63
  }
57
64
  case 'userAgent': {
58
- globalOptions.userAgent = (options.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36');
65
+ globalOptions.userAgent = options.userAgent || getRandomUserAgent();
59
66
  break;
60
67
  }
61
68
  case 'proxy': {
@@ -68,6 +75,10 @@ function setOptions(globalOptions, options) {
68
75
  }
69
76
  break;
70
77
  }
78
+ case 'delayBetweenRequests': {
79
+ globalOptions.delayBetweenRequests = options.delayBetweenRequests || 1000;
80
+ break;
81
+ }
71
82
  default: {
72
83
  log.warn("setOptions", "Unrecognized option given to setOptions: " + key);
73
84
  break;
@@ -82,6 +93,7 @@ function setOptions(globalOptions, options) {
82
93
  function buildAPI(globalOptions, html, jar) {
83
94
  let fb_dtsg = null;
84
95
  let irisSeqID = null;
96
+
85
97
  function extractFromHTML() {
86
98
  try {
87
99
  const $ = cheerio.load(html);
@@ -129,35 +141,43 @@ function buildAPI(globalOptions, html, jar) {
129
141
  }
130
142
  } catch { }
131
143
  if (fb_dtsg) {
132
- console.log("Found fb_dtsg!");
144
+ log.info("Found fb_dtsg token");
133
145
  }
134
146
  } catch (e) {
135
- console.log("Error finding fb_dtsg:", e);
147
+ log.error("Error finding fb_dtsg:", e);
136
148
  }
137
149
  }
150
+
138
151
  extractFromHTML();
152
+
139
153
  var userID;
140
154
  var cookies = jar.getCookies("https://www.facebook.com");
141
155
  var userCookie = cookies.find(cookie => cookie.cookieString().startsWith("c_user="));
142
156
  var tiktikCookie = cookies.find(cookie => cookie.cookieString().startsWith("i_user="));
157
+
143
158
  if (!userCookie && !tiktikCookie) {
144
- return log.error("Error! Your cookiestate is not valid!");
159
+ log.error("Error! Your cookiestate is not valid!");
160
+ throw new Error("Invalid appState");
145
161
  }
162
+
146
163
  if (html.includes("/checkpoint/block/?next")) {
147
- return log.error('error', "Appstate is dead rechange it!", 'error');
164
+ log.error('error', "Appstate is dead, please refresh it!", 'error');
165
+ throw new Error("AppState expired");
148
166
  }
167
+
149
168
  userID = (tiktikCookie || userCookie).cookieString().split("=")[1];
150
- //logger.log(`${cra(`[ CONNECT ]`)} Logged in as ${userID}`, "DATABASE");
169
+
151
170
  try { clearInterval(checkVerified); } catch (_) { }
171
+
152
172
  const clientID = (Math.random() * 2147483648 | 0).toString(16);
153
173
  let mqttEndpoint = `wss://edge-chat.facebook.com/chat?region=pnb&sid=${userID}`;
154
174
  let region = "PNB";
155
175
 
156
176
  try {
157
177
  const endpointMatch = html.match(/"endpoint":"([^"]+)"/);
158
- if (endpointMatch.input.includes("601051028565049")) {
159
- console.log(`login error.`);
160
- ditconmemay = true;
178
+ if (endpointMatch && endpointMatch.input && endpointMatch.input.includes("601051028565049")) {
179
+ log.error(`Login error detected`);
180
+ global.ditconmemay = true;
161
181
  }
162
182
  if (endpointMatch) {
163
183
  mqttEndpoint = endpointMatch[1].replace(/\\\//g, '/');
@@ -165,9 +185,11 @@ function buildAPI(globalOptions, html, jar) {
165
185
  region = url.searchParams.get('region')?.toUpperCase() || "PNB";
166
186
  }
167
187
  } catch (e) {
168
- console.log('Using default MQTT endpoint');
188
+ log.warn('Using default MQTT endpoint');
169
189
  }
170
- log.info('Logging in...');
190
+
191
+ log.info('Building API...');
192
+
171
193
  var ctx = {
172
194
  userID: userID,
173
195
  jar: jar,
@@ -188,11 +210,12 @@ function buildAPI(globalOptions, html, jar) {
188
210
  wsReqNumber: 0,
189
211
  wsTaskNumber: 0,
190
212
  reqCallbacks: {},
191
- threadTypes: {} // Store thread type (dm/group) for each thread
213
+ threadTypes: {}
192
214
  };
215
+
193
216
  let config = { enableTypingIndicator: false, typingDuration: 4000 };
217
+
194
218
  try {
195
- // Prefer global root config (project-level), but fallback to fca/config.json if present.
196
219
  const rootConfigPath = path.join(process.cwd(), 'config.json');
197
220
  if (fs.existsSync(rootConfigPath)) {
198
221
  const rootConfig = JSON.parse(fs.readFileSync(rootConfigPath, 'utf8'));
@@ -216,15 +239,13 @@ function buildAPI(globalOptions, html, jar) {
216
239
  if (typeof global.GoatBot.config.typingDuration !== 'undefined') config.typingDuration = global.GoatBot.config.typingDuration;
217
240
  }
218
241
  } catch (e) {
219
- console.log('Error loading config.json:', e);
242
+ log.warn('Error loading config.json:', e.message);
220
243
  }
221
244
 
222
245
  const refreshFcaConfig = () => {
223
246
  try {
224
- // Defaults first
225
247
  const updatedConfig = { enableTypingIndicator: false, typingDuration: 4000 };
226
248
 
227
- // Layered config sources
228
249
  if (fs.existsSync(path.join(process.cwd(), 'config.json'))) {
229
250
  const rootConfig = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'config.json'), 'utf8'));
230
251
  if (rootConfig && typeof rootConfig === 'object') {
@@ -248,39 +269,33 @@ function buildAPI(globalOptions, html, jar) {
248
269
 
249
270
  ctx.config = updatedConfig;
250
271
  config = updatedConfig;
251
- if (global.GoatBot) global.GoatBot.config = global.GoatBot.config || {};
252
- if (global.GoatBot && typeof global.GoatBot.config.enableTypingIndicator !== 'undefined') {
253
- global.GoatBot.config.enableTypingIndicator = updatedConfig.enableTypingIndicator;
254
- }
255
- if (global.GoatBot && typeof global.GoatBot.config.typingDuration !== 'undefined') {
256
- global.GoatBot.config.typingDuration = updatedConfig.typingDuration;
257
- }
258
272
  } catch (e) {
259
- console.log('Failed to refresh fca config:', e);
273
+ log.warn('Failed to refresh fca config:', e.message);
260
274
  }
261
275
  };
262
276
 
263
- // Initial config load
264
277
  refreshFcaConfig();
265
-
266
- // Accessible runtime API for config reload
267
278
  ctx.refreshFcaConfig = refreshFcaConfig;
268
279
  if (global.GoatBot) {
269
280
  global.GoatBot.refreshFcaConfig = refreshFcaConfig;
270
281
  }
271
282
 
272
283
  ctx.config = config;
284
+
273
285
  var api = {
274
286
  setOptions: setOptions.bind(null, globalOptions),
275
287
  getAppState: () => utils.getAppState(jar),
276
288
  postFormData: (url, body) => utils.makeDefaults(html, userID, ctx).postFormData(url, ctx.jar, body)
277
289
  };
290
+
278
291
  var defaultFuncs = utils.makeDefaults(html, userID, ctx);
279
292
  api.postFormData = function (url, body) {
280
293
  return defaultFuncs.postFormData(url, ctx.jar, body);
281
294
  };
295
+
282
296
  api.getFreshDtsg = async function () {
283
297
  try {
298
+ await delay(globalOptions.delayBetweenRequests || 1000);
284
299
  const res = await defaultFuncs.get('https://www.facebook.com/', jar, null, globalOptions);
285
300
  const $ = cheerio.load(res.body);
286
301
  let newDtsg;
@@ -310,12 +325,18 @@ function buildAPI(globalOptions, html, jar) {
310
325
 
311
326
  return newDtsg;
312
327
  } catch (e) {
313
- console.log("Error getting fresh dtsg:", e);
328
+ log.error("Error getting fresh dtsg:", e.message);
314
329
  return null;
315
330
  }
316
331
  };
317
- //if (noMqttData) api.htmlData = noMqttData;
318
- require('fs').readdirSync(__dirname + '/src/').filter(v => v.endsWith('.js')).forEach(v => { api[v.replace('.js', '')] = require(`./src/${v}`)(utils.makeDefaults(html, userID, ctx), api, ctx); });
332
+
333
+ // Load API modules
334
+ const srcPath = path.join(__dirname, 'src');
335
+ if (fs.existsSync(srcPath)) {
336
+ fs.readdirSync(srcPath).filter(v => v.endsWith('.js')).forEach(v => {
337
+ api[v.replace('.js', '')] = require(`./src/${v}`)(utils.makeDefaults(html, userID, ctx), api, ctx);
338
+ });
339
+ }
319
340
 
320
341
  // Store original sendMessage as the primary method
321
342
  const originalSendMessage = api.sendMessage;
@@ -325,8 +346,7 @@ function buildAPI(globalOptions, html, jar) {
325
346
  try {
326
347
  return await originalSendMessage(msg, threadID, callback, replyToMessage, isSingleUser);
327
348
  } catch (error) {
328
- // If modern method fails, fallback to OldMessage
329
- console.log('sendMessage failed, using OldMessage fallback:', error.message);
349
+ log.warn('sendMessage failed, using OldMessage fallback:', error.message);
330
350
  return api.OldMessage(msg, threadID, callback, replyToMessage, isSingleUser);
331
351
  }
332
352
  };
@@ -337,6 +357,7 @@ function buildAPI(globalOptions, html, jar) {
337
357
  };
338
358
 
339
359
  api.listen = api.listenMqtt;
360
+
340
361
  return {
341
362
  ctx,
342
363
  defaultFuncs,
@@ -362,23 +383,32 @@ function makeLogin(jar, email, password, loginOptions, callback, prCallback) {
362
383
  form.locale = 'en_US';
363
384
  form.timezone = '240';
364
385
  form.lgnjs = Math.floor(Date.now() / 1000);
386
+
365
387
  const willBeCookies = html.split("\"_js_");
366
388
  willBeCookies.slice(1).forEach(val => {
367
389
  const cookieData = JSON.parse("[\"" + utils.getFrom(val, "", "]") + "]");
368
390
  jar.setCookie(utils.formatCookie(cookieData, "facebook"), "https://www.facebook.com");
369
391
  });
370
- log.info("Logging in...");
392
+
393
+ log.info("Logging in with email/password...");
394
+ await delay(2000); // Delay to avoid rate limiting
395
+
371
396
  const loginRes = await utils.post(
372
397
  "https://www.facebook.com/login/device-based/regular/login/?login_attempt=1&lwv=110",
373
398
  jar,
374
399
  form,
375
400
  loginOptions
376
401
  );
402
+
377
403
  await utils.saveCookies(jar)(loginRes);
378
404
  const headers = loginRes.headers;
379
- if (!headers.location) throw new Error("Wrong username/password.");
405
+
406
+ if (!headers.location) {
407
+ throw new Error("Wrong username/password or account locked.");
408
+ }
409
+
380
410
  if (headers.location.includes('https://www.facebook.com/checkpoint/')) {
381
- log.info("login", "You have login approvals turned on.");
411
+ log.info("login", "Login approval required. Checking checkpoint...");
382
412
  const checkpointRes = await utils.get(headers.location, jar, null, loginOptions);
383
413
  await utils.saveCookies(jar)(checkpointRes);
384
414
  const checkpointHtml = checkpointRes.body;
@@ -387,6 +417,7 @@ function makeLogin(jar, email, password, loginOptions, callback, prCallback) {
387
417
  $("form input").each((i, v) => checkpointForm.push({ val: $(v).val(), name: $(v).attr("name") }));
388
418
  checkpointForm = checkpointForm.filter(v => v.val && v.val.length);
389
419
  const form = utils.arrToForm(checkpointForm);
420
+
390
421
  if (checkpointHtml.includes("checkpoint/?next")) {
391
422
  return new Promise((resolve, reject) => {
392
423
  const submit2FA = async (code) => {
@@ -422,7 +453,11 @@ function makeLogin(jar, email, password, loginOptions, callback, prCallback) {
422
453
  };
423
454
  });
424
455
  }
425
- if (!loginOptions.forceLogin) throw new Error("Couldn't login. Facebook might have blocked this account.");
456
+
457
+ if (!loginOptions.forceLogin) {
458
+ throw new Error("Couldn't login. Facebook might have blocked this account or requires verification.");
459
+ }
460
+
426
461
  form['submit[This was me]'] = checkpointHtml.includes("Suspicious Login Attempt") ? "This was me" : "This Is Okay";
427
462
  await utils.post("https://www.facebook.com/checkpoint/?next=https%3A%2F%2Fwww.facebook.com%2Fhome.php", jar, form, loginOptions);
428
463
  form.name_action_selected = 'save_device';
@@ -430,39 +465,40 @@ function makeLogin(jar, email, password, loginOptions, callback, prCallback) {
430
465
  const appState = utils.getAppState(jar);
431
466
  return await loginHelper(appState, email, password, loginOptions, callback);
432
467
  }
468
+
433
469
  await utils.get('https://www.facebook.com/', jar, null, loginOptions);
434
470
  return await utils.saveCookies(jar);
435
471
  } catch (error) {
472
+ log.error("Login error:", error.message);
436
473
  callback(error);
437
474
  }
438
475
  };
439
476
  }
440
477
 
441
-
442
478
  function loginHelper(appState, email, password, globalOptions, callback, prCallback) {
443
479
  let mainPromise = null;
444
480
  const jar = utils.getJar();
481
+
445
482
  if (appState) {
446
483
  try {
447
- appState = JSON.parse(appState);
448
- } catch (e) {
449
- try {
450
- appState = appState;
451
- } catch (e) {
452
- return callback(new Error("Failed to parse appState"));
484
+ if (typeof appState === 'string') {
485
+ appState = JSON.parse(appState);
453
486
  }
487
+ } catch (e) {
488
+ return callback(new Error("Failed to parse appState: " + e.message));
454
489
  }
455
490
 
456
491
  try {
457
492
  appState.forEach(c => {
458
- const str = `${c.key}=${c.value}; expires=${c.expires}; domain=${c.domain}; path=${c.path};`;
493
+ const str = `${c.key}=${c.value}; ${c.expires ? 'expires=' + c.expires + ';' : ''} domain=${c.domain}; path=${c.path};`;
459
494
  jar.setCookie(str, "http://" + c.domain);
460
495
  });
461
496
 
462
497
  mainPromise = utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true })
463
498
  .then(utils.saveCookies(jar));
464
499
  } catch (e) {
465
- process.exit(0);
500
+ log.error("Error setting cookies:", e.message);
501
+ return callback(new Error("Failed to set appState cookies"));
466
502
  }
467
503
  } else {
468
504
  mainPromise = utils
@@ -487,7 +523,7 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
487
523
  .then(res => {
488
524
  const mobileAgentRegex = /MPageLoadClientMetrics/gs;
489
525
  if (!mobileAgentRegex.test(res.body)) {
490
- globalOptions.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36";
526
+ globalOptions.userAgent = getRandomUserAgent();
491
527
  return utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true }).then(utils.saveCookies(jar));
492
528
  }
493
529
  return res;
@@ -517,11 +553,11 @@ function loginHelper(appState, email, password, globalOptions, callback, prCallb
517
553
  callback(null, api);
518
554
  })
519
555
  .catch(e => {
556
+ log.error("Login helper error:", e.message);
520
557
  callback(e);
521
558
  });
522
559
  }
523
560
 
524
-
525
561
  function login(loginData, options, callback) {
526
562
  // Check for updates (non-blocking, only once per session)
527
563
  if (!global.stfcaUpdateChecked) {
@@ -548,7 +584,8 @@ function login(loginData, options, callback) {
548
584
  logRecordSize: 100,
549
585
  online: false,
550
586
  emitReady: false,
551
- userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
587
+ delayBetweenRequests: 1500,
588
+ userAgent: getRandomUserAgent()
552
589
  };
553
590
 
554
591
  var prCallback = null;
@@ -566,19 +603,24 @@ function login(loginData, options, callback) {
566
603
  callback = prCallback;
567
604
  }
568
605
 
569
- if (loginData.email && loginData.password) {
606
+ // Handle different login methods
607
+ if (loginData.appState) {
608
+ setOptions(globalOptions, options);
609
+ loginHelper(loginData.appState, loginData.email, loginData.password, globalOptions, callback, prCallback);
610
+ } else if (loginData.email && loginData.password) {
570
611
  setOptions(globalOptions, {
571
- logLevel: "silent",
572
612
  forceLogin: true,
573
- userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
613
+ userAgent: getRandomUserAgent(),
614
+ delayBetweenRequests: 1500
574
615
  });
575
- loginHelper(loginData.appState, loginData.email, loginData.password, globalOptions, callback, prCallback);
576
- } else if (loginData.appState) {
577
- setOptions(globalOptions, options);
578
- return loginHelper(loginData.appState, loginData.email, loginData.password, globalOptions, callback, prCallback);
616
+ loginHelper(null, loginData.email, loginData.password, globalOptions, callback, prCallback);
617
+ } else {
618
+ const error = new Error("Invalid login data. Provide either appState or email/password");
619
+ if (callback) callback(error);
620
+ else throw error;
579
621
  }
622
+
580
623
  return returnPromise;
581
624
  }
582
625
 
583
-
584
626
  module.exports = login;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadowx-fca",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Unofficial Facebook Chat API for Node.js with Auto-Update System - modify by Mueid Mursalin Rifat",
5
5
  "main": "index.js",
6
6
  "files": [