fca-phantom 1.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/LICENSE +58 -0
- package/README.md +534 -0
- package/index.js +35 -0
- package/package.json +101 -0
- package/phantom/core/builder/bootstrap.js +334 -0
- package/phantom/core/builder/config.js +78 -0
- package/phantom/core/builder/forge.js +113 -0
- package/phantom/core/builder/ignite.js +386 -0
- package/phantom/core/builder/options.js +61 -0
- package/phantom/core/engine.js +71 -0
- package/phantom/core/reactor.js +2 -0
- package/phantom/datastore/appState.js +2 -0
- package/phantom/datastore/appStateBackup.js +34 -0
- package/phantom/datastore/models/cipher/e2ee.js +48 -0
- package/phantom/datastore/models/cipher/vault.js +153 -0
- package/phantom/datastore/models/index.js +3 -0
- package/phantom/datastore/models/matrix/auth.js +151 -0
- package/phantom/datastore/models/matrix/cache.js +3 -0
- package/phantom/datastore/models/matrix/checker.js +2 -0
- package/phantom/datastore/models/matrix/clients.js +2 -0
- package/phantom/datastore/models/matrix/constants.js +2 -0
- package/phantom/datastore/models/matrix/credentials.js +2 -0
- package/phantom/datastore/models/matrix/cycle.js +2 -0
- package/phantom/datastore/models/matrix/gate.js +282 -0
- package/phantom/datastore/models/matrix/ghost.js +332 -0
- package/phantom/datastore/models/matrix/headers.js +193 -0
- package/phantom/datastore/models/matrix/heartbeat.js +298 -0
- package/phantom/datastore/models/matrix/identity.js +235 -0
- package/phantom/datastore/models/matrix/logger.js +271 -0
- package/phantom/datastore/models/matrix/monitor.js +2 -0
- package/phantom/datastore/models/matrix/net.js +316 -0
- package/phantom/datastore/models/matrix/response.js +193 -0
- package/phantom/datastore/models/matrix/revive.js +255 -0
- package/phantom/datastore/models/matrix/signals.js +2 -0
- package/phantom/datastore/models/matrix/store.js +263 -0
- package/phantom/datastore/models/matrix/telemetry.js +272 -0
- package/phantom/datastore/models/matrix/tools.js +93 -0
- package/phantom/datastore/models/matrix/transform/cookieParser.js +2 -0
- package/phantom/datastore/models/matrix/transform/cookies.js +114 -0
- package/phantom/datastore/models/matrix/transform/index.js +203 -0
- package/phantom/datastore/models/matrix/validator.js +157 -0
- package/phantom/datastore/models/types/index.d.ts +498 -0
- package/phantom/datastore/schema.js +167 -0
- package/phantom/datastore/session.js +129 -0
- package/phantom/datastore/threads.js +22 -0
- package/phantom/datastore/users.js +26 -0
- package/phantom/dispatch/addExternalModule.js +239 -0
- package/phantom/dispatch/addUserToGroup.js +161 -0
- package/phantom/dispatch/changeAdminStatus.js +142 -0
- package/phantom/dispatch/changeArchivedStatus.js +135 -0
- package/phantom/dispatch/changeAvatar.js +123 -0
- package/phantom/dispatch/changeBio.js +86 -0
- package/phantom/dispatch/changeBlockedStatus.js +86 -0
- package/phantom/dispatch/changeGroupImage.js +145 -0
- package/phantom/dispatch/changeThreadColor.js +172 -0
- package/phantom/dispatch/changeThreadEmoji.js +130 -0
- package/phantom/dispatch/comment.js +136 -0
- package/phantom/dispatch/createAITheme.js +333 -0
- package/phantom/dispatch/createNewGroup.js +99 -0
- package/phantom/dispatch/createPoll.js +148 -0
- package/phantom/dispatch/deleteMessage.js +131 -0
- package/phantom/dispatch/deleteThread.js +155 -0
- package/phantom/dispatch/e2ee.js +101 -0
- package/phantom/dispatch/editMessage.js +158 -0
- package/phantom/dispatch/emoji.js +143 -0
- package/phantom/dispatch/fetchThemeData.js +233 -0
- package/phantom/dispatch/follow.js +111 -0
- package/phantom/dispatch/forwardMessage.js +110 -0
- package/phantom/dispatch/friend.js +189 -0
- package/phantom/dispatch/gcmember.js +138 -0
- package/phantom/dispatch/gcname.js +131 -0
- package/phantom/dispatch/gcrule.js +111 -0
- package/phantom/dispatch/getAccess.js +109 -0
- package/phantom/dispatch/getBotInfo.js +81 -0
- package/phantom/dispatch/getBotInitialData.js +110 -0
- package/phantom/dispatch/getFriendsList.js +118 -0
- package/phantom/dispatch/getMessage.js +199 -0
- package/phantom/dispatch/getTheme.js +199 -0
- package/phantom/dispatch/getThemeInfo.js +160 -0
- package/phantom/dispatch/getThreadHistory.js +139 -0
- package/phantom/dispatch/getThreadInfo.js +153 -0
- package/phantom/dispatch/getThreadList.js +132 -0
- package/phantom/dispatch/getThreadPictures.js +93 -0
- package/phantom/dispatch/getUserID.js +147 -0
- package/phantom/dispatch/getUserInfo.js +513 -0
- package/phantom/dispatch/getUserInfoV2.js +146 -0
- package/phantom/dispatch/handleMessageRequest.js +50 -0
- package/phantom/dispatch/httpGet.js +63 -0
- package/phantom/dispatch/httpPost.js +89 -0
- package/phantom/dispatch/httpPostFormData.js +69 -0
- package/phantom/dispatch/listenMqtt.js +1236 -0
- package/phantom/dispatch/listenSpeed.js +179 -0
- package/phantom/dispatch/logout.js +93 -0
- package/phantom/dispatch/markAsDelivered.js +92 -0
- package/phantom/dispatch/markAsRead.js +119 -0
- package/phantom/dispatch/markAsReadAll.js +215 -0
- package/phantom/dispatch/markAsSeen.js +70 -0
- package/phantom/dispatch/mqttDeltaValue.js +278 -0
- package/phantom/dispatch/muteThread.js +253 -0
- package/phantom/dispatch/nickname.js +132 -0
- package/phantom/dispatch/notes.js +263 -0
- package/phantom/dispatch/pinMessage.js +238 -0
- package/phantom/dispatch/produceMetaTheme.js +335 -0
- package/phantom/dispatch/realtime.js +291 -0
- package/phantom/dispatch/removeUserFromGroup.js +248 -0
- package/phantom/dispatch/resolvePhotoUrl.js +217 -0
- package/phantom/dispatch/searchForThread.js +258 -0
- package/phantom/dispatch/sendMessage.js +354 -0
- package/phantom/dispatch/sendMessageMqtt.js +249 -0
- package/phantom/dispatch/sendTypingIndicator.js +206 -0
- package/phantom/dispatch/setMessageReaction.js +188 -0
- package/phantom/dispatch/setMessageReactionMqtt.js +248 -0
- package/phantom/dispatch/setThreadTheme.js +330 -0
- package/phantom/dispatch/setThreadThemeMqtt.js +207 -0
- package/phantom/dispatch/share.js +200 -0
- package/phantom/dispatch/shareContact.js +216 -0
- package/phantom/dispatch/stickers.js +395 -0
- package/phantom/dispatch/story.js +240 -0
- package/phantom/dispatch/theme.js +296 -0
- package/phantom/dispatch/unfriend.js +199 -0
- package/phantom/dispatch/unsendMessage.js +124 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const tools = require("../../datastore/models/matrix/tools");
|
|
4
|
+
const axios = require("axios");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const qs = require("querystring");
|
|
8
|
+
const { normalizeCookieHeaderString, setJarFromPairs } = require("../../datastore/models/matrix/transform/cookieParser");
|
|
9
|
+
const { parseRegion, genTotp } = require("../../datastore/models/matrix/credentials");
|
|
10
|
+
const { generateIdentityByMode, cacheIdentityData } = require("../../datastore/models/matrix/identity");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Orchestrates the full session bootstrap sequence:
|
|
14
|
+
* cookie injection → page fetch → context assembly → API wiring.
|
|
15
|
+
*
|
|
16
|
+
* @param {object} creds Cookie state, email/pass, or TOTP bundle.
|
|
17
|
+
* @param {object} cfg Live configuration object.
|
|
18
|
+
* @param {function} cb Final node-style callback (err, api).
|
|
19
|
+
* @param {function} configFn Reference to the config applicator.
|
|
20
|
+
* @param {function} forgeFn Reference to the context forge function.
|
|
21
|
+
* @param {object} seedApi Partially-built API object (may be null).
|
|
22
|
+
* @param {function} originFn Facebook URL builder.
|
|
23
|
+
* @param {string} errNoIdentity Error message when identity can't be found.
|
|
24
|
+
* @returns {Promise<void>}
|
|
25
|
+
*/
|
|
26
|
+
async function ignite(creds, cfg, cb, configFn, forgeFn, seedApi, originFn, errNoIdentity) {
|
|
27
|
+
let ctx = null;
|
|
28
|
+
let coreFuncs = null;
|
|
29
|
+
let api = seedApi;
|
|
30
|
+
|
|
31
|
+
const { phantomBanner } = require("../../datastore/models/matrix/tools");
|
|
32
|
+
phantomBanner();
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const jar = tools.getJar();
|
|
36
|
+
tools.log("phantom-fca", "Initializing session...");
|
|
37
|
+
|
|
38
|
+
const mode = cfg.persona || "desktop";
|
|
39
|
+
const modeSwitched = cfg.cachedPersona && cfg.cachedPersona !== mode;
|
|
40
|
+
|
|
41
|
+
if (modeSwitched) {
|
|
42
|
+
tools.log("phantom-fca", `Identity mode changed ${cfg.cachedPersona} → ${mode}, resetting fingerprint cache`);
|
|
43
|
+
const desktopKeys = ["cachedUserAgent","cachedSecChUa","cachedSecChUaFullVersionList","cachedSecChUaPlatform","cachedSecChUaPlatformVersion","cachedBrowser"];
|
|
44
|
+
const androidKeys = ["cachedAndroidUA","cachedAndroidVersion","cachedAndroidDevice","cachedAndroidBuildId","cachedAndroidResolution","cachedAndroidFbav","cachedAndroidFbbv","cachedAndroidLocale","cachedAndroidCarrier"];
|
|
45
|
+
[...desktopKeys, ...androidKeys, "cachedLocale", "cachedTimezone"].forEach(k => delete cfg[k]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const needsDesktop = mode === "desktop" && !cfg.cachedUserAgent;
|
|
49
|
+
const needsAndroid = (mode === "android" || mode === "mobile") && !cfg.cachedAndroidUA;
|
|
50
|
+
|
|
51
|
+
if (needsDesktop || needsAndroid) {
|
|
52
|
+
const identity = generateIdentityByMode(mode, cfg);
|
|
53
|
+
cacheIdentityData(cfg, identity);
|
|
54
|
+
cfg.cachedPersona = mode;
|
|
55
|
+
tools.log("phantom-fca", mode === "desktop"
|
|
56
|
+
? `Desktop identity initialized (${identity.browser})`
|
|
57
|
+
: "Android/Orca mobile identity initialized");
|
|
58
|
+
|
|
59
|
+
const { getRandomLocale, getRandomTimezone } = require("../../datastore/models/matrix/signals");
|
|
60
|
+
if (!cfg.cachedLocale) cfg.cachedLocale = getRandomLocale();
|
|
61
|
+
if (!cfg.cachedTimezone) cfg.cachedTimezone = getRandomTimezone();
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const { globalShield } = require("../../datastore/models/matrix/ghost");
|
|
65
|
+
globalShield.lockSessionFingerprint(
|
|
66
|
+
identity.userAgent || cfg.cachedAndroidUA,
|
|
67
|
+
identity.secChUa || "",
|
|
68
|
+
identity.secChUaPlatform || identity.persona || "desktop",
|
|
69
|
+
cfg.cachedLocale,
|
|
70
|
+
cfg.cachedTimezone
|
|
71
|
+
);
|
|
72
|
+
} catch (_) {}
|
|
73
|
+
} else {
|
|
74
|
+
tools.log("phantom-fca", mode === "desktop" ? "Using cached desktop identity" : "Using cached Android identity");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let sessionCookies = creds.appState;
|
|
78
|
+
|
|
79
|
+
if (!sessionCookies && !creds.email && !creds.password) {
|
|
80
|
+
try {
|
|
81
|
+
const { hydrateJarFromDB } = require("../../datastore/appState");
|
|
82
|
+
const restored = await hydrateJarFromDB(jar, null);
|
|
83
|
+
if (restored) tools.log("phantom-fca", "Session restored from persistent store");
|
|
84
|
+
} catch (e) {
|
|
85
|
+
tools.warn("phantom-fca", "Could not restore session from store:", e.message);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (sessionCookies) {
|
|
90
|
+
let pairs = [];
|
|
91
|
+
if (Array.isArray(sessionCookies)) {
|
|
92
|
+
pairs = sessionCookies.map(c => [c.name || c.key, c.value].join("="));
|
|
93
|
+
} else if (typeof sessionCookies === "string") {
|
|
94
|
+
pairs = normalizeCookieHeaderString(sessionCookies);
|
|
95
|
+
if (!pairs.length) pairs = sessionCookies.split(";").map(s => s.trim()).filter(Boolean);
|
|
96
|
+
} else {
|
|
97
|
+
throw new Error("Invalid session format. Provide cookie array or cookie string.");
|
|
98
|
+
}
|
|
99
|
+
setJarFromPairs(jar, pairs);
|
|
100
|
+
tools.log("phantom-fca", "Session cookies injected");
|
|
101
|
+
|
|
102
|
+
} else if (creds.email && creds.password) {
|
|
103
|
+
if (creds.totpSecret) tools.log("phantom-fca", "TOTP secret detected");
|
|
104
|
+
|
|
105
|
+
const authEndpoint = "https://api.facebook.com/method/auth.login";
|
|
106
|
+
const authParams = {
|
|
107
|
+
access_token: "350685531728|62f8ce9f74b12f84c123cc23437a4a32",
|
|
108
|
+
format: "json",
|
|
109
|
+
sdk_version: 2,
|
|
110
|
+
email: creds.email,
|
|
111
|
+
locale: "en_US",
|
|
112
|
+
password: creds.password,
|
|
113
|
+
generate_session_cookies: 1,
|
|
114
|
+
sig: "c1c640010993db92e5afd11634ced864",
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (creds.totpSecret) {
|
|
118
|
+
try {
|
|
119
|
+
const otp = await genTotp(creds.totpSecret);
|
|
120
|
+
authParams.credentials_type = "two_factor";
|
|
121
|
+
authParams.twofactor_code = otp;
|
|
122
|
+
tools.log("phantom-fca", "2FA token generated");
|
|
123
|
+
} catch (e) {
|
|
124
|
+
tools.warn("phantom-fca", "2FA token generation failed:", e.message);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const res = await axios.get(`${authEndpoint}?${qs.stringify(authParams)}`);
|
|
130
|
+
if (res.status !== 200) throw new Error("Authentication failed");
|
|
131
|
+
setJarFromPairs(jar, res.data.session_cookies.map(c => `${c.name}=${c.value}`));
|
|
132
|
+
tools.log("phantom-fca", "Email/password authentication successful");
|
|
133
|
+
} catch (e) {
|
|
134
|
+
throw new Error("Authentication failed — check credentials");
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
throw new Error("No credentials provided. Supply appState cookies or email+password.");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!api) {
|
|
141
|
+
api = {
|
|
142
|
+
setOptions: configFn.bind(null, cfg),
|
|
143
|
+
getAppState() {
|
|
144
|
+
const state = tools.getAppState(jar);
|
|
145
|
+
if (!Array.isArray(state)) return [];
|
|
146
|
+
const unique = state.filter((item, i, self) => self.findIndex(t => t.key === item.key) === i);
|
|
147
|
+
return unique.length > 0 ? unique : state;
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const { globalShield } = require("../../datastore/models/matrix/ghost");
|
|
154
|
+
globalShield.resetCircuitBreaker();
|
|
155
|
+
globalShield.enableWarmup();
|
|
156
|
+
} catch (_) {}
|
|
157
|
+
|
|
158
|
+
const pageResp = await tools.get(originFn(), jar, null, cfg, { noRef: true }).then(tools.saveCookies(jar));
|
|
159
|
+
|
|
160
|
+
const extractBlobs = (html) => {
|
|
161
|
+
const blobs = [];
|
|
162
|
+
const re = /<script type="application\/json"[^>]*>(.*?)<\/script>/g;
|
|
163
|
+
let m;
|
|
164
|
+
while ((m = re.exec(html)) !== null) {
|
|
165
|
+
try { blobs.push(JSON.parse(m[1])); } catch (_) {}
|
|
166
|
+
}
|
|
167
|
+
return blobs;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const netBlobs = extractBlobs(pageResp.body);
|
|
171
|
+
const [newCtx, newCoreFuncs] = await forgeFn(pageResp.body, jar, netBlobs, cfg, originFn, errNoIdentity);
|
|
172
|
+
ctx = newCtx;
|
|
173
|
+
coreFuncs = newCoreFuncs;
|
|
174
|
+
|
|
175
|
+
const detectedRegion = parseRegion(pageResp.body);
|
|
176
|
+
ctx.region = detectedRegion;
|
|
177
|
+
tools.log("phantom-fca", "Region detected:", detectedRegion);
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const { backupAppStateSQL } = require("../../datastore/appState");
|
|
181
|
+
await backupAppStateSQL(jar, ctx.userID);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
tools.warn("phantom-fca", "Session snapshot failed:", e.message);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
api.message = new Map();
|
|
187
|
+
api.timestamp = {};
|
|
188
|
+
|
|
189
|
+
const mountDispatch = () => {
|
|
190
|
+
const dispatchPath = path.join(__dirname, "..", "..", "dispatch");
|
|
191
|
+
if (!fs.existsSync(dispatchPath)) {
|
|
192
|
+
tools.error("phantom-fca", "Dispatch directory not found:", dispatchPath);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const skipModules = ["deltaProcessor"];
|
|
196
|
+
fs.readdirSync(dispatchPath)
|
|
197
|
+
.filter(f => f.endsWith(".js"))
|
|
198
|
+
.forEach(f => {
|
|
199
|
+
const name = path.basename(f, ".js");
|
|
200
|
+
if (skipModules.includes(name)) return;
|
|
201
|
+
try {
|
|
202
|
+
const mod = require(path.join(dispatchPath, f));
|
|
203
|
+
if (typeof mod === "function") api[name] = mod(coreFuncs, api, ctx);
|
|
204
|
+
} catch (e) {
|
|
205
|
+
tools.error("phantom-fca", `Failed to mount dispatch module [${name}]:`, e);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
api.getCurrentUserID = () => ctx.userID;
|
|
211
|
+
api.getOptions = (k) => (k ? cfg[k] : cfg);
|
|
212
|
+
mountDispatch();
|
|
213
|
+
|
|
214
|
+
if (api.nickname && typeof api.nickname === "function") {
|
|
215
|
+
api.changeNickname = api.nickname;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
const schema = require("../../datastore/models");
|
|
220
|
+
const threadsDb = require("../../datastore/threads");
|
|
221
|
+
const usersDb = require("../../datastore/users");
|
|
222
|
+
schema.syncAll().then(() => tools.log("phantom-fca", "Data store synced")).catch(e => tools.warn("phantom-fca", "Data store sync error:", e.message));
|
|
223
|
+
api.threadData = threadsDb(api);
|
|
224
|
+
api.userData = usersDb(api);
|
|
225
|
+
} catch (e) {
|
|
226
|
+
tools.warn("phantom-fca", "Data store optional — skipped:", e.message);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
api.ctx = ctx;
|
|
230
|
+
api.defaultFuncs = coreFuncs;
|
|
231
|
+
api.globalOptions = cfg;
|
|
232
|
+
|
|
233
|
+
const { CycleManager } = require("../../datastore/models/matrix/cycle");
|
|
234
|
+
if (api._cycleManager) {
|
|
235
|
+
api._cycleManager.halt();
|
|
236
|
+
} else {
|
|
237
|
+
api._cycleManager = new CycleManager();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const { globalReviveManager } = require("../../datastore/models/matrix/revive");
|
|
241
|
+
|
|
242
|
+
if (cfg.autoReLogin !== false) {
|
|
243
|
+
globalReviveManager.setCredentials(creds, cfg, cb);
|
|
244
|
+
tools.log("phantom-fca", "Auto-revive enabled");
|
|
245
|
+
try { globalReviveManager.updateAppState(api.getAppState()); } catch (_) {}
|
|
246
|
+
|
|
247
|
+
api._cycleManager.setExpiryHandler(() => {
|
|
248
|
+
tools.warn("phantom-fca", "Token cycle expired — triggering revive...");
|
|
249
|
+
globalReviveManager.handleSessionExpiry(api, originFn(), errNoIdentity);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
ctx.performAutoLogin = async () => {
|
|
253
|
+
try {
|
|
254
|
+
const r = await globalReviveManager.handleSessionExpiry(api, originFn(), errNoIdentity);
|
|
255
|
+
return r !== false;
|
|
256
|
+
} catch (_) { return false; }
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
api.disconnect = () => {
|
|
261
|
+
const fn = require("../../dispatch/logout")(coreFuncs, api, ctx);
|
|
262
|
+
return fn();
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const shutdownCleanup = () => {
|
|
266
|
+
tools.log("phantom-fca", "Graceful shutdown in progress...");
|
|
267
|
+
if (api._cycleManager) api._cycleManager.halt();
|
|
268
|
+
if (globalReviveManager) { globalReviveManager.stopSessionMonitoring(); globalReviveManager.disable(); }
|
|
269
|
+
if (ctx.mqttClient?.end) { try { ctx.mqttClient.end(true); } catch (_) {} }
|
|
270
|
+
if (ctx._emitter) ctx._emitter.removeAllListeners();
|
|
271
|
+
["_mqttWatchdog","_autoCycleTimer","_periodicBackupInterval"].forEach(k => { if (ctx[k]) clearInterval(ctx[k]); });
|
|
272
|
+
["_tmsTimeout","_reconnectTimer"].forEach(k => { if (ctx[k]) clearTimeout(ctx[k]); });
|
|
273
|
+
tools.log("phantom-fca", "Shutdown complete");
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
if (!process._phantomCleanupRegistered) {
|
|
277
|
+
process._phantomCleanupRegistered = true;
|
|
278
|
+
process.on("exit", () => shutdownCleanup());
|
|
279
|
+
process.on("SIGINT", () => { shutdownCleanup(); process.exit(0); });
|
|
280
|
+
process.on("SIGTERM", () => { shutdownCleanup(); process.exit(0); });
|
|
281
|
+
process.on("uncaughtException", (err) => { tools.error("phantom-fca", err.message); shutdownCleanup(); process.exit(1); });
|
|
282
|
+
process.on("unhandledRejection",(r) => tools.error("phantom-fca", String(r?.message ?? r)));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (ctx._periodicBackupInterval) clearInterval(ctx._periodicBackupInterval);
|
|
286
|
+
ctx._periodicBackupInterval = setInterval(async () => {
|
|
287
|
+
try {
|
|
288
|
+
const { backupAppStateSQL } = require("../../datastore/appState");
|
|
289
|
+
await backupAppStateSQL(jar, ctx.userID);
|
|
290
|
+
} catch (_) {}
|
|
291
|
+
}, 15 * 60 * 1000);
|
|
292
|
+
|
|
293
|
+
api._cycleManager.startAutoRefresh(ctx, coreFuncs, originFn());
|
|
294
|
+
|
|
295
|
+
api.refreshTokens = () => api._cycleManager.refreshTokens(ctx, coreFuncs, originFn());
|
|
296
|
+
api.getCycleStatus = () => api._cycleManager.getStatus();
|
|
297
|
+
|
|
298
|
+
api.getHealthStatus = () => {
|
|
299
|
+
const mqttOk = !!(ctx.mqttClient?.connected);
|
|
300
|
+
let rateStats = null;
|
|
301
|
+
try { const { getRateLimiterStats } = require("../../datastore/models/matrix/gate"); rateStats = getRateLimiterStats(); } catch (_) {}
|
|
302
|
+
return {
|
|
303
|
+
mqttConnected: mqttOk,
|
|
304
|
+
autoReconnect: !!cfg.autoReconnect,
|
|
305
|
+
tokenCycle: {
|
|
306
|
+
lastRefresh: api._cycleManager.lastRefresh,
|
|
307
|
+
nextRefresh: api._cycleManager.getTimeUntilNextRefresh(),
|
|
308
|
+
failureCount: api._cycleManager.getFailureCount(),
|
|
309
|
+
},
|
|
310
|
+
autoRevive: {
|
|
311
|
+
enabled: globalReviveManager.isEnabled(),
|
|
312
|
+
sessionMonitoring: !!globalReviveManager.sessionMonitorInterval,
|
|
313
|
+
},
|
|
314
|
+
rateLimiter: rateStats,
|
|
315
|
+
};
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
api.enableAutoRevive = (on = true) => on
|
|
319
|
+
? globalReviveManager.setCredentials(creds, cfg, cb)
|
|
320
|
+
: globalReviveManager.disable();
|
|
321
|
+
api.isAutoReviveActive = () => globalReviveManager.isEnabled();
|
|
322
|
+
|
|
323
|
+
api.isSessionAlive = () => new Promise(async (resolve) => {
|
|
324
|
+
try {
|
|
325
|
+
const probe = { noRef: true, _skipSessionInspect: true };
|
|
326
|
+
const r = await tools.get(
|
|
327
|
+
"https://www.facebook.com/ajax/presence/reconnect.php?reason=14&fb_dtsg_ag=&__a=1",
|
|
328
|
+
ctx.jar, null, ctx.globalOptions, probe
|
|
329
|
+
);
|
|
330
|
+
const html = r.body || "";
|
|
331
|
+
const isLoginWall =
|
|
332
|
+
html.includes('<form id="login_form"') ||
|
|
333
|
+
html.includes('id="loginbutton"') ||
|
|
334
|
+
html.includes('"login_page"') ||
|
|
335
|
+
html.includes('action="/login.php');
|
|
336
|
+
if (isLoginWall) return resolve(false);
|
|
337
|
+
|
|
338
|
+
if (html.includes('"checkpoint"') && html.includes('"flow_type"')) {
|
|
339
|
+
try {
|
|
340
|
+
const { globalShield } = require("../../datastore/models/matrix/ghost");
|
|
341
|
+
globalShield.tripCircuitBreaker("checkpoint_detected", 60 * 60 * 1000);
|
|
342
|
+
} catch (_) {}
|
|
343
|
+
return resolve(false);
|
|
344
|
+
}
|
|
345
|
+
resolve(!!(ctx.fb_dtsg && ctx.fb_dtsg.length > 10));
|
|
346
|
+
} catch (err) {
|
|
347
|
+
const msg = err.message || String(err);
|
|
348
|
+
const code = err.code || "";
|
|
349
|
+
const NET = ["ECONNRESET","ETIMEDOUT","ECONNREFUSED","ENETUNREACH","EHOSTUNREACH","EAI_AGAIN","ENOTFOUND","ESOCKETTIMEDOUT"];
|
|
350
|
+
if (NET.some(c => code === c || msg.includes(c)) || msg.includes("socket hang up") || msg.includes("connect ETIMEDOUT")) {
|
|
351
|
+
tools.warn("phantom-fca", "Session probe — transient network issue, treating as valid");
|
|
352
|
+
return resolve("network_error");
|
|
353
|
+
}
|
|
354
|
+
tools.error("phantom-fca", "Session probe failed:", msg);
|
|
355
|
+
resolve(false);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
if (cfg.autoReLogin !== false) {
|
|
360
|
+
try {
|
|
361
|
+
const { globalReviveManager: rm } = require("../../datastore/models/matrix/revive");
|
|
362
|
+
rm.startSessionMonitoring(api);
|
|
363
|
+
tools.log("phantom-fca", "Session watchdog started");
|
|
364
|
+
} catch (_) {}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
const { globalShield } = require("../../datastore/models/matrix/ghost");
|
|
369
|
+
api.shield = {
|
|
370
|
+
getStatus: () => globalShield.getStatus(),
|
|
371
|
+
reset: () => globalShield.resetCircuitBreaker(),
|
|
372
|
+
addDelay: () => globalShield.addSmartDelay(),
|
|
373
|
+
setDailyLimit:(n) => globalShield.setDailyLimit(n),
|
|
374
|
+
getBehavior: () => globalShield.getBehaviorProfile(),
|
|
375
|
+
};
|
|
376
|
+
} catch (_) {}
|
|
377
|
+
|
|
378
|
+
cb(null, api);
|
|
379
|
+
|
|
380
|
+
} catch (err) {
|
|
381
|
+
tools.error("phantom-fca", "Session launch error:", err.message || err);
|
|
382
|
+
cb(err);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
module.exports = ignite;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const tools = require("../../datastore/models/matrix/tools");
|
|
4
|
+
|
|
5
|
+
async function applyConfig(cfg, patch = {}) {
|
|
6
|
+
const handlers = {
|
|
7
|
+
online: (v) => (cfg.online = Boolean(v)),
|
|
8
|
+
selfListen: (v) => (cfg.selfListen = Boolean(v)),
|
|
9
|
+
selfListenEvent: (v) => (cfg.selfListenEvent = v),
|
|
10
|
+
listenEvents: (v) => (cfg.listenEvents = Boolean(v)),
|
|
11
|
+
updatePresence: (v) => (cfg.updatePresence = Boolean(v)),
|
|
12
|
+
forceLogin: (v) => (cfg.forceLogin = Boolean(v)),
|
|
13
|
+
userAgent: (v) => (cfg.userAgent = v),
|
|
14
|
+
autoMarkDelivery: (v) => (cfg.autoMarkDelivery = Boolean(v)),
|
|
15
|
+
autoMarkRead: (v) => (cfg.autoMarkRead = Boolean(v)),
|
|
16
|
+
listenTyping: (v) => (cfg.listenTyping = Boolean(v)),
|
|
17
|
+
autoReconnect: (v) => (cfg.autoReconnect = Boolean(v)),
|
|
18
|
+
emitReady: (v) => (cfg.emitReady = Boolean(v)),
|
|
19
|
+
simulateTyping: (v) => (cfg.simulateTyping = Boolean(v)),
|
|
20
|
+
autoReLogin: (v) => (cfg.autoReLogin = Boolean(v)),
|
|
21
|
+
persona: (v) => (cfg.persona = ["desktop","android","mobile"].includes(v) ? v : "desktop"),
|
|
22
|
+
logLevel: (v) => (cfg.logLevel = v),
|
|
23
|
+
sessionPersist: (v) => (cfg.sessionPersist = Boolean(v)),
|
|
24
|
+
proxy(v) {
|
|
25
|
+
if (typeof v !== "string") { delete cfg.proxy; tools.setProxy(); }
|
|
26
|
+
else { cfg.proxy = v; tools.setProxy(v); }
|
|
27
|
+
},
|
|
28
|
+
randomUserAgent(v) {
|
|
29
|
+
cfg.randomUserAgent = Boolean(v);
|
|
30
|
+
if (v) cfg.userAgent = tools.randomUserAgent?.() || cfg.userAgent;
|
|
31
|
+
},
|
|
32
|
+
bypassRegion(v) { cfg.bypassRegion = v ? v.toUpperCase() : v; },
|
|
33
|
+
maxConcurrentRequests(v) {
|
|
34
|
+
if (typeof v === "number") { cfg.maxConcurrentRequests = Math.floor(v); tools.configureRateLimiter({ maxConcurrentRequests: cfg.maxConcurrentRequests }); }
|
|
35
|
+
},
|
|
36
|
+
maxRequestsPerMinute(v) {
|
|
37
|
+
if (typeof v === "number") { cfg.maxRequestsPerMinute = Math.floor(v); tools.configureRateLimiter({ maxRequestsPerMinute: cfg.maxRequestsPerMinute }); }
|
|
38
|
+
},
|
|
39
|
+
requestCooldownMs(v) {
|
|
40
|
+
if (typeof v === "number") { cfg.requestCooldownMs = Math.floor(v); tools.configureRateLimiter({ requestCooldownMs: cfg.requestCooldownMs }); }
|
|
41
|
+
},
|
|
42
|
+
errorCacheTtlMs(v) {
|
|
43
|
+
if (typeof v === "number") { cfg.errorCacheTtlMs = Math.floor(v); tools.configureRateLimiter({ errorCacheTtlMs: cfg.errorCacheTtlMs }); }
|
|
44
|
+
},
|
|
45
|
+
stealthMode(v) {
|
|
46
|
+
cfg.stealthMode = Boolean(v);
|
|
47
|
+
if (v) {
|
|
48
|
+
cfg.updatePresence = false;
|
|
49
|
+
cfg.online = false;
|
|
50
|
+
cfg.simulateTyping = false;
|
|
51
|
+
tools.configureRateLimiter({ maxConcurrentRequests: 2, maxRequestsPerMinute: 60 });
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
for (const [key, val] of Object.entries(patch)) {
|
|
57
|
+
if (handlers[key]) handlers[key](val);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = applyConfig;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const tools = require("../datastore/models/matrix/tools");
|
|
4
|
+
const applyConfig = require("./builder/options");
|
|
5
|
+
const assembleAPI = require("./builder/forge");
|
|
6
|
+
const igniteSession = require("./builder/bootstrap");
|
|
7
|
+
|
|
8
|
+
let _globalCfg = {};
|
|
9
|
+
let _ctx = null;
|
|
10
|
+
let _coreFuncs = null;
|
|
11
|
+
let _client = null;
|
|
12
|
+
|
|
13
|
+
const origin = (ext) => "https://www.facebook.com" + (ext ? "/" + ext : "");
|
|
14
|
+
|
|
15
|
+
const ERR_NO_IDENTITY =
|
|
16
|
+
"Unable to retrieve account identity. This may be caused by IP blocks, checkpoint walls, or invalid session cookies. Try re-authenticating via a browser first.";
|
|
17
|
+
|
|
18
|
+
const DEFAULTS = {
|
|
19
|
+
selfListen: false,
|
|
20
|
+
listenEvents: true,
|
|
21
|
+
listenTyping: false,
|
|
22
|
+
simulateTyping: true,
|
|
23
|
+
updatePresence: false,
|
|
24
|
+
forceLogin: false,
|
|
25
|
+
autoMarkDelivery: false,
|
|
26
|
+
autoMarkRead: true,
|
|
27
|
+
autoReconnect: true,
|
|
28
|
+
online: true,
|
|
29
|
+
emitReady: false,
|
|
30
|
+
persona: "desktop",
|
|
31
|
+
autoReLogin: true,
|
|
32
|
+
sessionPersist: true,
|
|
33
|
+
userAgent:
|
|
34
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
async function connect(creds, opts, cb) {
|
|
38
|
+
if (typeof opts === "function") { cb = opts; opts = {}; }
|
|
39
|
+
|
|
40
|
+
let resolve, reject, promise;
|
|
41
|
+
if (typeof cb !== "function") {
|
|
42
|
+
promise = new Promise((res, rej) => { resolve = res; reject = rej; });
|
|
43
|
+
cb = (err, api) => (err ? reject(err) : resolve(api));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (opts && "logging" in opts) tools.logOptions?.(opts.logging);
|
|
47
|
+
|
|
48
|
+
Object.assign(_globalCfg, DEFAULTS, opts);
|
|
49
|
+
await applyConfig(_globalCfg, opts || {});
|
|
50
|
+
|
|
51
|
+
igniteSession(
|
|
52
|
+
creds,
|
|
53
|
+
_globalCfg,
|
|
54
|
+
(err, sessionApi) => {
|
|
55
|
+
if (err) return cb(err);
|
|
56
|
+
_client = sessionApi;
|
|
57
|
+
_ctx = sessionApi.ctx;
|
|
58
|
+
_coreFuncs = sessionApi.defaultFuncs;
|
|
59
|
+
return cb(null, sessionApi);
|
|
60
|
+
},
|
|
61
|
+
applyConfig,
|
|
62
|
+
assembleAPI,
|
|
63
|
+
_client,
|
|
64
|
+
origin,
|
|
65
|
+
ERR_NO_IDENTITY
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return promise;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { connect };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { backupAppStateSQL } = require("./session");
|
|
4
|
+
|
|
5
|
+
let _backupTimer = null;
|
|
6
|
+
const DEFAULT_INTERVAL_MS = 5 * 60 * 1000;
|
|
7
|
+
|
|
8
|
+
function startAutoBackup(jar, userID, intervalMs = DEFAULT_INTERVAL_MS) {
|
|
9
|
+
if (_backupTimer) clearInterval(_backupTimer);
|
|
10
|
+
_backupTimer = setInterval(async () => {
|
|
11
|
+
try {
|
|
12
|
+
await backupAppStateSQL(jar, userID);
|
|
13
|
+
} catch (_) {}
|
|
14
|
+
}, intervalMs);
|
|
15
|
+
if (_backupTimer.unref) _backupTimer.unref();
|
|
16
|
+
return _backupTimer;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function stopAutoBackup() {
|
|
20
|
+
if (_backupTimer) {
|
|
21
|
+
clearInterval(_backupTimer);
|
|
22
|
+
_backupTimer = null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isRunning() {
|
|
27
|
+
return _backupTimer !== null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = {
|
|
31
|
+
startAutoBackup,
|
|
32
|
+
stopAutoBackup,
|
|
33
|
+
isRunning,
|
|
34
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const crypto = require("crypto");
|
|
4
|
+
|
|
5
|
+
class PhantomCipher {
|
|
6
|
+
constructor(algo = "aes-256-gcm") {
|
|
7
|
+
this._algo = algo;
|
|
8
|
+
this._keyLen = 32;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
generateKey() { return crypto.randomBytes(this._keyLen); }
|
|
12
|
+
|
|
13
|
+
encrypt(text, key) {
|
|
14
|
+
const iv = crypto.randomBytes(12);
|
|
15
|
+
const cipher= crypto.createCipheriv(this._algo, key, iv);
|
|
16
|
+
const enc = Buffer.concat([cipher.update(text, "utf8"), cipher.final()]);
|
|
17
|
+
const tag = cipher.getAuthTag();
|
|
18
|
+
return Buffer.concat([iv, tag, enc]).toString("base64url");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
decrypt(data, key) {
|
|
22
|
+
const buf = Buffer.from(data, "base64url");
|
|
23
|
+
const iv = buf.slice(0, 12);
|
|
24
|
+
const tag = buf.slice(12, 28);
|
|
25
|
+
const enc = buf.slice(28);
|
|
26
|
+
const decipher = crypto.createDecipheriv(this._algo, key, iv);
|
|
27
|
+
decipher.setAuthTag(tag);
|
|
28
|
+
return Buffer.concat([decipher.update(enc), decipher.final()]).toString("utf8");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
deriveKey(passphrase, salt = null) {
|
|
32
|
+
const s = salt || crypto.randomBytes(16);
|
|
33
|
+
const key = crypto.scryptSync(passphrase, s, this._keyLen);
|
|
34
|
+
return { key, salt: s.toString("hex") };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
hash(data, algo = "sha256") {
|
|
38
|
+
return crypto.createHash(algo).update(data).digest("hex");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
hmac(data, key, algo = "sha256") {
|
|
42
|
+
return crypto.createHmac(algo, key).update(data).digest("hex");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const globalCipher = new PhantomCipher();
|
|
47
|
+
|
|
48
|
+
module.exports = { PhantomCipher, globalCipher };
|