shadowx-fca 8.0.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.
- package/README.md +1066 -0
- package/build/messagix.dll +0 -0
- package/build/messagix.so +0 -0
- package/checkUpdate.js +393 -0
- package/config.json +17 -0
- package/e2ee.js +563 -0
- package/e2eetest.js +356 -0
- package/index.js +611 -0
- package/lib/index.mjs +1412 -0
- package/logger.js +500 -0
- package/package.json +65 -0
- package/src/GetBotInfo.js +66 -0
- package/src/OldMessage.js +182 -0
- package/src/Screenshot.js +83 -0
- package/src/addExternalModule.js +13 -0
- package/src/addUserToGroup.js +33 -0
- package/src/approveGroupJoinRequests.js +18 -0
- package/src/changeAdminStatus.js +16 -0
- package/src/changeArchivedStatus.js +17 -0
- package/src/changeAvatar.js +136 -0
- package/src/changeAvatarV2.js +86 -0
- package/src/changeAvt.js +85 -0
- package/src/changeBio.js +76 -0
- package/src/changeBlockedStatus.js +20 -0
- package/src/changeBlockedStatusMqtt.js +80 -0
- package/src/changeCover.js +72 -0
- package/src/changeGroupImage.js +16 -0
- package/src/changeName.js +79 -0
- package/src/changeNickname.js +16 -0
- package/src/changeThreadColor.js +15 -0
- package/src/changeThreadEmoji.js +15 -0
- package/src/changeThreadMemberNickname.js +6 -0
- package/src/changeUsername.js +59 -0
- package/src/createCommentPost.js +230 -0
- package/src/createNewGroup.js +38 -0
- package/src/createNote.js +35 -0
- package/src/createPoll.js +27 -0
- package/src/createPost.js +276 -0
- package/src/createThemeAI.js +129 -0
- package/src/data/cache/system/data.json +4 -0
- package/src/data/cache/system/datahandle.js +21 -0
- package/src/data/getThreadInfo.json +1 -0
- package/src/deleteComment.js +23 -0
- package/src/deleteMessage.js +15 -0
- package/src/deleteThread.js +15 -0
- package/src/denyGroupJoinRequests.js +18 -0
- package/src/e2ee/crypto.js +173 -0
- package/src/e2ee/index.js +144 -0
- package/src/e2ee/proto/ArmadilloApplication.proto +281 -0
- package/src/e2ee/proto/ArmadilloICDC.proto +14 -0
- package/src/e2ee/proto/ConsumerApplication.proto +232 -0
- package/src/e2ee/proto/MessageApplication.proto +82 -0
- package/src/e2ee/proto/MessageTransport.proto +77 -0
- package/src/e2ee/proto/WACommon.proto +66 -0
- package/src/e2ee/proto/WAMediaTransport.proto +176 -0
- package/src/e2ee/proto/proto-writer.ts +76 -0
- package/src/e2ee/protocol.js +196 -0
- package/src/e2ee/ratchet.js +219 -0
- package/src/e2ee/store.js +182 -0
- package/src/e2ee.js +8 -0
- package/src/editMessage.js +56 -0
- package/src/editMessageOld.js +67 -0
- package/src/enableReactions.js +24 -0
- package/src/follow.js +74 -0
- package/src/followUser.js +23 -0
- package/src/forwardAttachment.js +16 -0
- package/src/friendList.js +98 -0
- package/src/getAccess.js +112 -0
- package/src/getAppState.js +13 -0
- package/src/getAvatarUser.js +11 -0
- package/src/getBio.js +24 -0
- package/src/getBotInitialData.js +42 -0
- package/src/getCtx.js +5 -0
- package/src/getCurrentUserID.js +6 -0
- package/src/getEmojiUrl.js +29 -0
- package/src/getFriendsList.js +36 -0
- package/src/getMessage.js +37 -0
- package/src/getNotes.js +17 -0
- package/src/getOptions.js +5 -0
- package/src/getPinnedMessages.js +33 -0
- package/src/getPostInfo.js +17 -0
- package/src/getProfileInfo.js +17 -0
- package/src/getPublicData.js +25 -0
- package/src/getRegion.js +7 -0
- package/src/getRepInfo.js +17 -0
- package/src/getStickerPacks.js +25 -0
- package/src/getStickers.js +39 -0
- package/src/getStoryReactions.js +18 -0
- package/src/getThreadHistory.js +45 -0
- package/src/getThreadHistoryDeprecated.js +71 -0
- package/src/getThreadInfo.js +73 -0
- package/src/getThreadInfoDeprecated.js +56 -0
- package/src/getThreadList.js +76 -0
- package/src/getThreadListDeprecated.js +46 -0
- package/src/getThreadPictures.js +59 -0
- package/src/getThreadTheme.js +77 -0
- package/src/getUID.js +17 -0
- package/src/getUserID.js +17 -0
- package/src/getUserInfo.js +28 -0
- package/src/handleFriendRequest.js +21 -0
- package/src/handleMessageRequest.js +15 -0
- package/src/httpGet.js +13 -0
- package/src/httpPost.js +12 -0
- package/src/httpPostFormData.js +12 -0
- package/src/listenE2EE.js +75 -0
- package/src/listenMqtt.js +802 -0
- package/src/listenNotification.js +85 -0
- package/src/logout.js +22 -0
- package/src/markAsDelivered.js +17 -0
- package/src/markAsRead.js +14 -0
- package/src/markAsReadAll.js +15 -0
- package/src/markAsSeen.js +15 -0
- package/src/metaTheme.js +185 -0
- package/src/muteThread.js +52 -0
- package/src/note.js +228 -0
- package/src/pin.js +53 -0
- package/src/pinMessage.js +6 -0
- package/src/postComment.js +29 -0
- package/src/postFormData.js +46 -0
- package/src/reactToComment.js +31 -0
- package/src/reactToPost.js +32 -0
- package/src/refreshFb_dtsg.js +31 -0
- package/src/removeSuspiciousAccount.js +74 -0
- package/src/removeUserFromGroup.js +15 -0
- package/src/reply.js +442 -0
- package/src/resolvePhotoUrl.js +15 -0
- package/src/searchForThread.js +20 -0
- package/src/searchFriends.js +28 -0
- package/src/searchStickers.js +53 -0
- package/src/send.js +46 -0
- package/src/sendAudio.js +8 -0
- package/src/sendBroadcast.js +93 -0
- package/src/sendButtons.js +161 -0
- package/src/sendComment.js +159 -0
- package/src/sendEmoji.js +10 -0
- package/src/sendFile.js +9 -0
- package/src/sendFriendRequest.js +33 -0
- package/src/sendGif.js +24 -0
- package/src/sendImage.js +9 -0
- package/src/sendLocation.js +9 -0
- package/src/sendMessage.js +487 -0
- package/src/sendMessage1.js +309 -0
- package/src/sendMessageMqtt.js +68 -0
- package/src/sendSticker.js +8 -0
- package/src/sendTypingIndicator.js +36 -0
- package/src/sendTypingIndicatorV2.js +28 -0
- package/src/sendVideo.js +9 -0
- package/src/sessionGuard.js +130 -0
- package/src/setActiveStatus.js +16 -0
- package/src/setMessageReaction.js +61 -0
- package/src/setMessageReactionMqtt.js +62 -0
- package/src/setOptions.js +22 -0
- package/src/setPollVote.js +17 -0
- package/src/setPostReaction.js +112 -0
- package/src/setProfileGuard.js +44 -0
- package/src/setProfileLock.js +93 -0
- package/src/setStoryReaction.js +129 -0
- package/src/setStorySeen.js +99 -0
- package/src/setThreadTheme.js +17 -0
- package/src/setTitle.js +15 -0
- package/src/shareContact.js +33 -0
- package/src/shareLink.js +8 -0
- package/src/sharePost.js +31 -0
- package/src/stopListenMqtt.js +23 -0
- package/src/storyManager.js +353 -0
- package/src/suggestFriend.js +128 -0
- package/src/threadColors.js +131 -0
- package/src/unfollowUser.js +23 -0
- package/src/unfriend.js +15 -0
- package/src/unpinMessage.js +6 -0
- package/src/unsendMessage.js +14 -0
- package/src/uploadAttachment.js +58 -0
- package/src/uploadImageToImgbb.js +29 -0
- package/utils.js +2945 -0
package/index.js
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ╔══════════════════════════════════════════════════════════╗
|
|
5
|
+
* ║ SHADOWX-FCA - Ultimate Facebook API ║
|
|
6
|
+
* ║ Facebook Chat API - All Features Merged ║
|
|
7
|
+
* ║ Dev by Mueid Mursalin Rifat ║
|
|
8
|
+
* ╚══════════════════════════════════════════════════════════╝
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
if (typeof WebSocket === "undefined") {
|
|
12
|
+
global.WebSocket = require("ws");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const cheerio = require("cheerio");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const fs = require("fs");
|
|
18
|
+
const utils = require("./utils");
|
|
19
|
+
const logger = require("./logger");
|
|
20
|
+
const { initAutoUpdate, checkForFCAUpdate } = require("./checkUpdate");
|
|
21
|
+
|
|
22
|
+
const PKG = require("./package.json");
|
|
23
|
+
const VERSION = PKG.version;
|
|
24
|
+
const NAME = "SHADOWX-FCA";
|
|
25
|
+
const DEV = "Mueid Mursalin Rifat";
|
|
26
|
+
|
|
27
|
+
// Initialize auto-update on startup
|
|
28
|
+
initAutoUpdate();
|
|
29
|
+
|
|
30
|
+
// Check for updates immediately on require
|
|
31
|
+
checkForFCAUpdate().catch(() => {
|
|
32
|
+
// Silently fail - updates are non-critical
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const BOOLEAN_OPTIONS = [
|
|
36
|
+
'online', 'selfListen', 'listenEvents', 'updatePresence', 'forceLogin',
|
|
37
|
+
'autoMarkDelivery', 'autoMarkRead', 'listenTyping', 'autoReconnect', 'emitReady'
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
function setOptions(globalOptions, options) {
|
|
41
|
+
Object.keys(options || {}).forEach(key => {
|
|
42
|
+
if (BOOLEAN_OPTIONS.includes(key)) {
|
|
43
|
+
globalOptions[key] = Boolean(options[key]);
|
|
44
|
+
} else {
|
|
45
|
+
switch (key) {
|
|
46
|
+
case 'pauseLog':
|
|
47
|
+
if (options.pauseLog) logger.pause && logger.pause();
|
|
48
|
+
else logger.resume && logger.resume();
|
|
49
|
+
break;
|
|
50
|
+
case 'logLevel':
|
|
51
|
+
globalOptions.logLevel = options.logLevel;
|
|
52
|
+
break;
|
|
53
|
+
case 'logRecordSize':
|
|
54
|
+
globalOptions.logRecordSize = options.logRecordSize;
|
|
55
|
+
break;
|
|
56
|
+
case 'pageID':
|
|
57
|
+
globalOptions.pageID = String(options.pageID);
|
|
58
|
+
break;
|
|
59
|
+
case 'userAgent':
|
|
60
|
+
globalOptions.userAgent = options.userAgent || globalOptions.userAgent;
|
|
61
|
+
break;
|
|
62
|
+
case 'proxy':
|
|
63
|
+
if (typeof options.proxy !== "string") {
|
|
64
|
+
delete globalOptions.proxy;
|
|
65
|
+
utils.setProxy();
|
|
66
|
+
} else {
|
|
67
|
+
globalOptions.proxy = options.proxy;
|
|
68
|
+
utils.setProxy(globalOptions.proxy);
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
case 'autoUpdate':
|
|
72
|
+
globalOptions.autoUpdate = Boolean(options.autoUpdate);
|
|
73
|
+
if (globalOptions.autoUpdate) {
|
|
74
|
+
process.env.AUTO_UPDATE = 'true';
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
default:
|
|
78
|
+
logger.warn && logger.warn("setOptions", "Unrecognized option given to setOptions: " + key);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function extractFbDtsg(html) {
|
|
86
|
+
let htmlString = html;
|
|
87
|
+
if (Buffer.isBuffer(html)) htmlString = html.toString('utf8');
|
|
88
|
+
else if (typeof html !== 'string') htmlString = String(html);
|
|
89
|
+
|
|
90
|
+
const patterns = [
|
|
91
|
+
/\["DTSGInitialData",\[\],{"token":"([^"]+)"}]/,
|
|
92
|
+
/\["DTSGInitData",\[\],{"token":"([^"]+)"/,
|
|
93
|
+
/{"dtsg":{"token":"([^"]+)"/,
|
|
94
|
+
/"token":"([^"]+)"/,
|
|
95
|
+
/name="fb_dtsg" value="([^"]+)"/
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
for (const pat of patterns) {
|
|
99
|
+
const m = htmlString.match(pat);
|
|
100
|
+
if (m && m[1]) return m[1];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const $ = cheerio.load(htmlString);
|
|
105
|
+
const dtsgInput = $('input[name="fb_dtsg"]').val();
|
|
106
|
+
if (dtsgInput) return dtsgInput;
|
|
107
|
+
} catch (_) {}
|
|
108
|
+
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function buildAPI(globalOptions, html, jar) {
|
|
113
|
+
let htmlString = html;
|
|
114
|
+
if (Buffer.isBuffer(html)) htmlString = html.toString('utf8');
|
|
115
|
+
else if (typeof html !== 'string') htmlString = String(html);
|
|
116
|
+
|
|
117
|
+
const fb_dtsg = extractFbDtsg(htmlString);
|
|
118
|
+
const irisSeqMatch = htmlString.match(/irisSeqID":"([^"]+)"/);
|
|
119
|
+
const irisSeqID = irisSeqMatch ? irisSeqMatch[1] : null;
|
|
120
|
+
|
|
121
|
+
const FB_DOMAINS = [
|
|
122
|
+
"https://www.facebook.com",
|
|
123
|
+
"https://facebook.com",
|
|
124
|
+
"https://mbasic.facebook.com",
|
|
125
|
+
"https://web.facebook.com",
|
|
126
|
+
];
|
|
127
|
+
const allCookies = [];
|
|
128
|
+
const seenKeys = new Set();
|
|
129
|
+
FB_DOMAINS.forEach(domain => {
|
|
130
|
+
try {
|
|
131
|
+
jar.getCookies(domain).forEach(c => {
|
|
132
|
+
if (!seenKeys.has(c.key)) {
|
|
133
|
+
seenKeys.add(c.key);
|
|
134
|
+
allCookies.push(c);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
} catch (_) {}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const userCookie = allCookies.find(c => c.key === "c_user");
|
|
141
|
+
const i_userCookie = allCookies.find(c => c.key === "i_user");
|
|
142
|
+
|
|
143
|
+
if (!userCookie && !i_userCookie) {
|
|
144
|
+
const presentKeys = allCookies.map(c => c.key).join(", ") || "(none)";
|
|
145
|
+
throw new Error(
|
|
146
|
+
"AppState is invalid or expired — no c_user/i_user cookie found.\n" +
|
|
147
|
+
" Cookies present: " + presentKeys + "\n" +
|
|
148
|
+
" Make sure your appstate.json was exported from an active Facebook session."
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if (htmlString.includes("/checkpoint/block/?next")) {
|
|
152
|
+
throw new Error("This account has been checkpointed by Facebook.");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const userID = (i_userCookie || userCookie).value;
|
|
156
|
+
const clientID = (Math.random() * 2147483648 | 0).toString(16);
|
|
157
|
+
|
|
158
|
+
let mqttEndpoint = "wss://edge-chat.facebook.com/chat?region=pnb";
|
|
159
|
+
let region = "PNB";
|
|
160
|
+
try {
|
|
161
|
+
const epMatch = htmlString.match(/"endpoint":"([^"]+)"/);
|
|
162
|
+
if (epMatch) {
|
|
163
|
+
let ep = epMatch[1].replace(/\\\//g, '/');
|
|
164
|
+
try {
|
|
165
|
+
const epUrl = new URL(ep);
|
|
166
|
+
epUrl.searchParams.delete('sid');
|
|
167
|
+
epUrl.searchParams.delete('cid');
|
|
168
|
+
region = (epUrl.searchParams.get('region') || "PNB").toUpperCase();
|
|
169
|
+
mqttEndpoint = epUrl.toString();
|
|
170
|
+
} catch (_) {
|
|
171
|
+
mqttEndpoint = ep.replace(/[?&]sid=[^&]*/g, '').replace(/[?&]cid=[^&]*/g, '');
|
|
172
|
+
region = (mqttEndpoint.match(/region=([^&]+)/) || [])[1]?.toUpperCase() || "PNB";
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} catch (_) {}
|
|
176
|
+
|
|
177
|
+
// Load config
|
|
178
|
+
let config = { enableTypingIndicator: false, typingDuration: 4000 };
|
|
179
|
+
try {
|
|
180
|
+
const configPaths = [
|
|
181
|
+
path.join(process.cwd(), 'config.json'),
|
|
182
|
+
path.join(__dirname, 'config.json')
|
|
183
|
+
];
|
|
184
|
+
for (const configPath of configPaths) {
|
|
185
|
+
if (fs.existsSync(configPath)) {
|
|
186
|
+
const fileConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
187
|
+
if (fileConfig && typeof fileConfig === 'object') {
|
|
188
|
+
if (typeof fileConfig.enableTypingIndicator !== 'undefined')
|
|
189
|
+
config.enableTypingIndicator = fileConfig.enableTypingIndicator;
|
|
190
|
+
if (typeof fileConfig.typingDuration !== 'undefined')
|
|
191
|
+
config.typingDuration = fileConfig.typingDuration;
|
|
192
|
+
// Map e2ee.enable from config.json -> enableE2EE
|
|
193
|
+
if (fileConfig.e2ee && typeof fileConfig.e2ee.enable !== 'undefined')
|
|
194
|
+
config.enableE2EE = Boolean(fileConfig.e2ee.enable);
|
|
195
|
+
if (fileConfig.e2ee && typeof fileConfig.e2ee.memoryOnly !== 'undefined')
|
|
196
|
+
config.e2eeMemoryOnly = Boolean(fileConfig.e2ee.memoryOnly);
|
|
197
|
+
if (fileConfig.e2ee && fileConfig.e2ee.devicePath)
|
|
198
|
+
config.e2eeDevicePath = fileConfig.e2ee.devicePath;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (global.GoatBot && global.GoatBot.config) {
|
|
203
|
+
if (typeof global.GoatBot.config.enableTypingIndicator !== 'undefined')
|
|
204
|
+
config.enableTypingIndicator = global.GoatBot.config.enableTypingIndicator;
|
|
205
|
+
if (typeof global.GoatBot.config.typingDuration !== 'undefined')
|
|
206
|
+
config.typingDuration = global.GoatBot.config.typingDuration;
|
|
207
|
+
}
|
|
208
|
+
} catch (_) {}
|
|
209
|
+
|
|
210
|
+
const refreshFcaConfig = () => {
|
|
211
|
+
try {
|
|
212
|
+
const updatedConfig = { enableTypingIndicator: false, typingDuration: 4000 };
|
|
213
|
+
const configPaths = [
|
|
214
|
+
path.join(process.cwd(), 'config.json'),
|
|
215
|
+
path.join(__dirname, 'config.json')
|
|
216
|
+
];
|
|
217
|
+
for (const configPath of configPaths) {
|
|
218
|
+
if (fs.existsSync(configPath)) {
|
|
219
|
+
const fileConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
220
|
+
if (fileConfig && typeof fileConfig === 'object') {
|
|
221
|
+
if (typeof fileConfig.enableTypingIndicator !== 'undefined')
|
|
222
|
+
updatedConfig.enableTypingIndicator = fileConfig.enableTypingIndicator;
|
|
223
|
+
if (typeof fileConfig.typingDuration !== 'undefined')
|
|
224
|
+
updatedConfig.typingDuration = fileConfig.typingDuration;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (global.GoatBot && global.GoatBot.config) {
|
|
229
|
+
if (typeof global.GoatBot.config.enableTypingIndicator !== 'undefined')
|
|
230
|
+
updatedConfig.enableTypingIndicator = global.GoatBot.config.enableTypingIndicator;
|
|
231
|
+
if (typeof global.GoatBot.config.typingDuration !== 'undefined')
|
|
232
|
+
updatedConfig.typingDuration = global.GoatBot.config.typingDuration;
|
|
233
|
+
}
|
|
234
|
+
ctx.config = updatedConfig;
|
|
235
|
+
} catch (_) {}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const ctx = {
|
|
239
|
+
userID,
|
|
240
|
+
i_userID: i_userCookie ? i_userCookie.value : userID,
|
|
241
|
+
jar,
|
|
242
|
+
clientID,
|
|
243
|
+
globalOptions,
|
|
244
|
+
loggedIn: true,
|
|
245
|
+
access_token: 'NONE',
|
|
246
|
+
clientMutationId: 0,
|
|
247
|
+
mqttClient: undefined,
|
|
248
|
+
lastSeqId: irisSeqID,
|
|
249
|
+
syncToken: undefined,
|
|
250
|
+
mqttEndpoint,
|
|
251
|
+
region,
|
|
252
|
+
firstListen: true,
|
|
253
|
+
fb_dtsg,
|
|
254
|
+
req_ID: 0,
|
|
255
|
+
callback_Task: {},
|
|
256
|
+
wsReqNumber: 0,
|
|
257
|
+
wsTaskNumber: 0,
|
|
258
|
+
reqCallbacks: {},
|
|
259
|
+
threadTypes: {},
|
|
260
|
+
config,
|
|
261
|
+
enableE2EE: config.enableE2EE !== undefined ? config.enableE2EE : false,
|
|
262
|
+
e2eeMemoryOnly: config.e2eeMemoryOnly !== undefined ? config.e2eeMemoryOnly : true,
|
|
263
|
+
e2eeDevicePath: config.e2eeDevicePath || null,
|
|
264
|
+
refreshFcaConfig
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
if (global.GoatBot) {
|
|
268
|
+
global.GoatBot.refreshFcaConfig = refreshFcaConfig;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const defaultFuncs = utils.makeDefaults(htmlString, userID, ctx);
|
|
272
|
+
|
|
273
|
+
const api = {
|
|
274
|
+
setOptions: setOptions.bind(null, globalOptions),
|
|
275
|
+
getAppState: () => utils.getAppState(jar),
|
|
276
|
+
getCurrentUserID: () => userID,
|
|
277
|
+
getCtx: () => ctx,
|
|
278
|
+
postFormData: (url, body) => defaultFuncs.postFormData(url, ctx.jar, body),
|
|
279
|
+
checkForUpdate: () => checkForFCAUpdate(),
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Load all src modules dynamically (merged from all 3 FCAs)
|
|
283
|
+
const SRC = path.join(__dirname, "src");
|
|
284
|
+
const EXCLUDED = new Set([
|
|
285
|
+
"listenMqtt.js", "sendMessage.js", "OldMessage.js", "e2ee.js",
|
|
286
|
+
"httpPost.js", "httpGet.js", "httpPostFormData.js",
|
|
287
|
+
"uploadImageToImgbb.js", "refreshFb_dtsg.js", "addExternalModule.js",
|
|
288
|
+
"listenE2EE.js", "sendBroadcast.js", "sessionGuard.js", "sendMessageMqtt.js"
|
|
289
|
+
]);
|
|
290
|
+
|
|
291
|
+
// Load critical modules first
|
|
292
|
+
api.httpPost = require("./src/httpPost")(defaultFuncs, api, ctx);
|
|
293
|
+
api.httpGet = require("./src/httpGet")(defaultFuncs, api, ctx);
|
|
294
|
+
api.httpPostFormData = require("./src/httpPostFormData")(defaultFuncs, api, ctx);
|
|
295
|
+
api.getFreshDtsg = require("./src/refreshFb_dtsg")(defaultFuncs, api, ctx);
|
|
296
|
+
api.addExternalModule = require("./src/addExternalModule")(defaultFuncs, api, ctx);
|
|
297
|
+
|
|
298
|
+
// Optional: imgbb uploader (may not exist in all variants)
|
|
299
|
+
if (fs.existsSync(path.join(SRC, "uploadImageToImgbb.js"))) {
|
|
300
|
+
try {
|
|
301
|
+
api.uploadImageToImgbb = require("./src/uploadImageToImgbb")(defaultFuncs, api, ctx);
|
|
302
|
+
} catch (_) {}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Load all remaining modules from merged src/
|
|
306
|
+
fs.readdirSync(SRC)
|
|
307
|
+
.filter(f => f.endsWith('.js') && !EXCLUDED.has(f))
|
|
308
|
+
.forEach(f => {
|
|
309
|
+
const name = f.replace('.js', '');
|
|
310
|
+
try {
|
|
311
|
+
const mod = require(path.join(SRC, f))(defaultFuncs, api, ctx);
|
|
312
|
+
if (mod !== undefined) api[name] = mod;
|
|
313
|
+
} catch (e) {
|
|
314
|
+
logger.warn && logger.warn("SHADOWX-FCA", `Failed to load module '${name}': ${e.message}`);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// sendMessage with OldMessage fallback
|
|
319
|
+
const originalSendMessage = require("./src/sendMessage")(defaultFuncs, api, ctx);
|
|
320
|
+
const originalOldMessage = require("./src/OldMessage")(defaultFuncs, api, ctx);
|
|
321
|
+
|
|
322
|
+
api.sendMessage = async function(msg, threadID, callback, replyToMessage, isSingleUser) {
|
|
323
|
+
try {
|
|
324
|
+
return await originalSendMessage(msg, threadID, callback, replyToMessage, isSingleUser);
|
|
325
|
+
} catch (error) {
|
|
326
|
+
logger.warn && logger.warn('SHADOWX-FCA', 'sendMessage failed, using OldMessage fallback');
|
|
327
|
+
return originalOldMessage(msg, threadID, callback, replyToMessage, isSingleUser);
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
api.OldMessage = originalOldMessage;
|
|
331
|
+
api.sendMessageDM = (msg, threadID, cb, replyTo) => originalOldMessage(msg, threadID, cb, replyTo, true);
|
|
332
|
+
api.sendMessageMqtt = require("./src/sendMessageMqtt")(defaultFuncs, api, ctx);
|
|
333
|
+
|
|
334
|
+
// MQTT listener with binary message filter
|
|
335
|
+
const originalListenMqtt = require("./src/listenMqtt")(defaultFuncs, api, ctx);
|
|
336
|
+
api.listenMqtt = function(callback) {
|
|
337
|
+
return originalListenMqtt((err, event) => {
|
|
338
|
+
if (err && err.error && err.error.includes('JSON.parse')) return;
|
|
339
|
+
if (err && err.isBinaryResponse === true) return;
|
|
340
|
+
if (err && err.res && Buffer.isBuffer(err.res)) return;
|
|
341
|
+
callback(err, event);
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
api.listen = api.listenMqtt;
|
|
345
|
+
|
|
346
|
+
// E2EE support
|
|
347
|
+
try {
|
|
348
|
+
const e2eeModule = require("./src/e2ee");
|
|
349
|
+
api.e2ee = new e2eeModule.E2EEBridge(ctx, api, defaultFuncs);
|
|
350
|
+
ctx.e2ee = api.e2ee;
|
|
351
|
+
api.connectE2EE = async (deviceStorePath) => {
|
|
352
|
+
await api.e2ee.connect(deviceStorePath, userID);
|
|
353
|
+
logger.info && logger.info("E2EE", "Connected e2ee ok");
|
|
354
|
+
return api.e2ee;
|
|
355
|
+
};
|
|
356
|
+
api.listenE2EE = require("./src/listenE2EE")(defaultFuncs, api, ctx);
|
|
357
|
+
|
|
358
|
+
// Auto-connect when config.json has "e2ee": { "enable": true }
|
|
359
|
+
if (ctx.globalOptions.enableE2EE) {
|
|
360
|
+
api.connectE2EE().catch(function(err) {
|
|
361
|
+
logger.warn && logger.warn("SHADOWX-FCA", "E2EE auto-connect failed: " + err.message);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
} catch (e) {
|
|
365
|
+
logger.warn && logger.warn('SHADOWX-FCA', 'E2EE module failed to load: ' + e.message);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Extra modules from all 3 FCAs
|
|
369
|
+
try { api.sendBroadcast = require("./src/sendBroadcast")(defaultFuncs, api, ctx); } catch (_) {}
|
|
370
|
+
try { api.sessionGuard = require("./src/sessionGuard")(defaultFuncs, api, ctx); } catch (_) {}
|
|
371
|
+
|
|
372
|
+
// Image upload helper
|
|
373
|
+
Object.defineProperty(api, '_imgUpload', {
|
|
374
|
+
value: async (imageUrl) => {
|
|
375
|
+
try {
|
|
376
|
+
if (api.uploadImageToImgbb) {
|
|
377
|
+
const r = await api.uploadImageToImgbb(imageUrl);
|
|
378
|
+
if (r && r.data) return r.data.url || r.data.display_url;
|
|
379
|
+
}
|
|
380
|
+
} catch (_) {}
|
|
381
|
+
return null;
|
|
382
|
+
},
|
|
383
|
+
enumerable: false,
|
|
384
|
+
writable: true
|
|
385
|
+
});
|
|
386
|
+
Object.defineProperty(ctx, '_imgUpload', {
|
|
387
|
+
value: api._imgUpload,
|
|
388
|
+
enumerable: false,
|
|
389
|
+
writable: true
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
return { ctx, defaultFuncs, api };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function makeLoginForm(html, jar, email, password) {
|
|
396
|
+
let htmlString = html;
|
|
397
|
+
if (Buffer.isBuffer(html)) htmlString = html.toString('utf8');
|
|
398
|
+
else if (typeof html !== 'string') htmlString = String(html);
|
|
399
|
+
|
|
400
|
+
const $ = cheerio.load(htmlString);
|
|
401
|
+
let arr = [];
|
|
402
|
+
$("#login_form input").each((i, v) => arr.push({ val: $(v).val(), name: $(v).attr("name") }));
|
|
403
|
+
arr = arr.filter(v => v.val && v.val.length);
|
|
404
|
+
const form = utils.arrToForm(arr);
|
|
405
|
+
form.lsd = utils.getFrom(htmlString, "[\"LSD\",[],{\"token\":\"", "\"}");
|
|
406
|
+
form.lgndim = Buffer.from(JSON.stringify({ w: 1440, h: 900, aw: 1440, ah: 834, c: 24 })).toString('base64');
|
|
407
|
+
form.email = email;
|
|
408
|
+
form.pass = password;
|
|
409
|
+
form.default_persistent = '0';
|
|
410
|
+
form.lgnrnd = utils.getFrom(htmlString, "name=\"lgnrnd\" value=\"", "\"");
|
|
411
|
+
form.locale = 'en_US';
|
|
412
|
+
form.timezone = '240';
|
|
413
|
+
form.lgnjs = Math.floor(Date.now() / 1000);
|
|
414
|
+
const willBeCookies = htmlString.split("\"_js_");
|
|
415
|
+
willBeCookies.slice(1).forEach(val => {
|
|
416
|
+
try {
|
|
417
|
+
const cookieData = JSON.parse("[\"" + utils.getFrom(val, "", "]") + "\"]");
|
|
418
|
+
jar.setCookie(utils.formatCookie(cookieData, "facebook"), "https://www.facebook.com");
|
|
419
|
+
} catch (_) {}
|
|
420
|
+
});
|
|
421
|
+
return form;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function saveShadowXConfig(userID, botName, region) {
|
|
425
|
+
try {
|
|
426
|
+
const cfgPath = path.join(process.cwd(), "shadowx-fca-config.json");
|
|
427
|
+
let existing = {};
|
|
428
|
+
try { existing = JSON.parse(fs.readFileSync(cfgPath, "utf8")); } catch (_) {}
|
|
429
|
+
const config = Object.assign({}, existing, {
|
|
430
|
+
botUID: userID,
|
|
431
|
+
botName: botName || existing.botName || "",
|
|
432
|
+
region: region || existing.region || "",
|
|
433
|
+
version: VERSION,
|
|
434
|
+
dev: DEV,
|
|
435
|
+
lastLogin: new Date().toISOString(),
|
|
436
|
+
logs: existing.logs !== undefined ? existing.logs : false,
|
|
437
|
+
});
|
|
438
|
+
fs.writeFileSync(cfgPath, JSON.stringify(config, null, 2));
|
|
439
|
+
} catch (_) {}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
async function loginHelper(appState, email, password, globalOptions, callback) {
|
|
443
|
+
const jar = utils.getJar();
|
|
444
|
+
|
|
445
|
+
let mainPromise;
|
|
446
|
+
if (appState) {
|
|
447
|
+
let parsed = appState;
|
|
448
|
+
if (typeof parsed === "string") {
|
|
449
|
+
try { parsed = JSON.parse(parsed); } catch (_) {}
|
|
450
|
+
}
|
|
451
|
+
parsed.forEach(c => {
|
|
452
|
+
const cookieName = c.key || c.name;
|
|
453
|
+
if (!cookieName || !c.value) return;
|
|
454
|
+
const domain = c.domain || '.facebook.com';
|
|
455
|
+
const expires = c.expirationDate
|
|
456
|
+
? new Date(c.expirationDate * 1000).toUTCString()
|
|
457
|
+
: (c.expires || '');
|
|
458
|
+
const str = `${cookieName}=${c.value}; expires=${expires}; domain=${domain}; path=${c.path || '/'};`;
|
|
459
|
+
const url = 'http://' + domain.replace(/^\./, 'www.');
|
|
460
|
+
try { jar.setCookie(str, url); } catch (_) {}
|
|
461
|
+
});
|
|
462
|
+
mainPromise = utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true })
|
|
463
|
+
.then(utils.saveCookies(jar));
|
|
464
|
+
} else {
|
|
465
|
+
mainPromise = utils.get("https://www.facebook.com/", null, null, globalOptions, { noRef: true })
|
|
466
|
+
.then(utils.saveCookies(jar))
|
|
467
|
+
.then(async res => {
|
|
468
|
+
let html = res.body;
|
|
469
|
+
if (Buffer.isBuffer(html)) html = html.toString('utf8');
|
|
470
|
+
const form = makeLoginForm(html, jar, email, password);
|
|
471
|
+
logger.info && logger.info("SHADOWX-FCA", "Logging in with email/password...");
|
|
472
|
+
const loginRes = await utils.post(
|
|
473
|
+
"https://www.facebook.com/login/device-based/regular/login/?login_attempt=1&lwv=110",
|
|
474
|
+
jar, form, globalOptions
|
|
475
|
+
);
|
|
476
|
+
await utils.saveCookies(jar)(loginRes);
|
|
477
|
+
const headers = loginRes.headers;
|
|
478
|
+
if (!headers.location) throw new Error("Wrong username/password.");
|
|
479
|
+
if (headers.location.includes('/checkpoint/')) {
|
|
480
|
+
if (!globalOptions.forceLogin) throw new Error("Account checkpoint detected. Enable forceLogin to bypass.");
|
|
481
|
+
}
|
|
482
|
+
return utils.get('https://www.facebook.com/', jar, null, globalOptions).then(utils.saveCookies(jar));
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
let ctx, api;
|
|
487
|
+
mainPromise = mainPromise
|
|
488
|
+
.then(async res => {
|
|
489
|
+
let body = res.body;
|
|
490
|
+
if (Buffer.isBuffer(body)) body = body.toString('utf8');
|
|
491
|
+
const reg = /<meta http-equiv="refresh" content="0;url=([^"]+)[^>]+>/;
|
|
492
|
+
const redirect = reg.exec(body);
|
|
493
|
+
if (redirect && redirect[1]) {
|
|
494
|
+
res = await utils.get(redirect[1], jar, null, globalOptions).then(utils.saveCookies(jar));
|
|
495
|
+
body = res.body;
|
|
496
|
+
if (Buffer.isBuffer(body)) body = body.toString('utf8');
|
|
497
|
+
}
|
|
498
|
+
const mobileAgentRegex = /MPageLoadClientMetrics/gs;
|
|
499
|
+
if (!mobileAgentRegex.test(body)) {
|
|
500
|
+
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";
|
|
501
|
+
res = await utils.get('https://www.facebook.com/', jar, null, globalOptions, { noRef: true }).then(utils.saveCookies(jar));
|
|
502
|
+
body = res.body;
|
|
503
|
+
if (Buffer.isBuffer(body)) body = body.toString('utf8');
|
|
504
|
+
}
|
|
505
|
+
return { res, body };
|
|
506
|
+
})
|
|
507
|
+
.then(({ res, body }) => {
|
|
508
|
+
const built = buildAPI(globalOptions, body, jar);
|
|
509
|
+
ctx = built.ctx;
|
|
510
|
+
api = built.api;
|
|
511
|
+
return res;
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
if (globalOptions.pageID) {
|
|
515
|
+
mainPromise = mainPromise
|
|
516
|
+
.then(() => utils.get(
|
|
517
|
+
`https://www.facebook.com/${globalOptions.pageID}/messages/?section=messages&subsection=inbox`,
|
|
518
|
+
jar, null, globalOptions
|
|
519
|
+
))
|
|
520
|
+
.then(resData => {
|
|
521
|
+
let body = resData.body;
|
|
522
|
+
if (Buffer.isBuffer(body)) body = body.toString('utf8');
|
|
523
|
+
let url = utils.getFrom(body, 'window.location.replace("https:\\/\\/www.facebook.com\\', '");').split('\\').join('');
|
|
524
|
+
url = url.substring(0, url.length - 1);
|
|
525
|
+
return utils.get('https://www.facebook.com' + url, jar, null, globalOptions);
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
mainPromise
|
|
530
|
+
.then(async () => {
|
|
531
|
+
let botName = "";
|
|
532
|
+
try {
|
|
533
|
+
const users = await api.getUserInfo([ctx.userID]);
|
|
534
|
+
if (users && users[ctx.userID]) botName = users[ctx.userID].name || "";
|
|
535
|
+
} catch (_) {}
|
|
536
|
+
|
|
537
|
+
if (logger.banner) {
|
|
538
|
+
logger.banner(NAME, VERSION, ctx.userID, botName, ctx.region, false);
|
|
539
|
+
} else {
|
|
540
|
+
logger.info && logger.info(NAME, `v${VERSION} | Dev: ${DEV} | UID: ${ctx.userID} | Region: ${ctx.region}`);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
saveShadowXConfig(ctx.userID, botName, ctx.region);
|
|
544
|
+
|
|
545
|
+
checkForFCAUpdate().catch(() => {});
|
|
546
|
+
|
|
547
|
+
callback(null, api);
|
|
548
|
+
})
|
|
549
|
+
.catch(e => {
|
|
550
|
+
logger.error && logger.error('SHADOWX-FCA', 'Login failed: ' + (e.message || e));
|
|
551
|
+
callback(e);
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function login(loginData, options, callback) {
|
|
556
|
+
if (typeof options === "function") {
|
|
557
|
+
callback = options;
|
|
558
|
+
options = {};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const globalOptions = {
|
|
562
|
+
selfListen: false,
|
|
563
|
+
listenEvents: true,
|
|
564
|
+
listenTyping: false,
|
|
565
|
+
updatePresence: false,
|
|
566
|
+
forceLogin: false,
|
|
567
|
+
autoMarkDelivery: false,
|
|
568
|
+
autoMarkRead: false,
|
|
569
|
+
autoReconnect: true,
|
|
570
|
+
logRecordSize: 100,
|
|
571
|
+
online: false,
|
|
572
|
+
emitReady: false,
|
|
573
|
+
autoUpdate: true,
|
|
574
|
+
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15"
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
setOptions(globalOptions, options || {});
|
|
578
|
+
|
|
579
|
+
let promise;
|
|
580
|
+
if (typeof callback !== "function" && typeof callback !== "undefined") {
|
|
581
|
+
callback = undefined;
|
|
582
|
+
}
|
|
583
|
+
if (!callback) {
|
|
584
|
+
promise = new Promise((resolve, reject) => {
|
|
585
|
+
callback = (err, api) => err ? reject(err) : resolve(api);
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (loginData.email && loginData.password) {
|
|
590
|
+
setOptions(globalOptions, {
|
|
591
|
+
logLevel: "silent",
|
|
592
|
+
forceLogin: true,
|
|
593
|
+
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
|
|
594
|
+
});
|
|
595
|
+
loginHelper(loginData.appState, loginData.email, loginData.password, globalOptions, callback);
|
|
596
|
+
} else if (loginData.appState) {
|
|
597
|
+
setOptions(globalOptions, options);
|
|
598
|
+
loginHelper(loginData.appState, loginData.email, loginData.password, globalOptions, callback);
|
|
599
|
+
} else {
|
|
600
|
+
callback(new Error("loginData must contain either appState or email+password."));
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return promise;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
module.exports = login;
|
|
607
|
+
module.exports.login = login;
|
|
608
|
+
module.exports.VERSION = VERSION;
|
|
609
|
+
module.exports.NAME = NAME;
|
|
610
|
+
module.exports.DEV = DEV;
|
|
611
|
+
module.exports.checkUpdate = checkForFCAUpdate;
|