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.
Files changed (121) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +534 -0
  3. package/index.js +35 -0
  4. package/package.json +101 -0
  5. package/phantom/core/builder/bootstrap.js +334 -0
  6. package/phantom/core/builder/config.js +78 -0
  7. package/phantom/core/builder/forge.js +113 -0
  8. package/phantom/core/builder/ignite.js +386 -0
  9. package/phantom/core/builder/options.js +61 -0
  10. package/phantom/core/engine.js +71 -0
  11. package/phantom/core/reactor.js +2 -0
  12. package/phantom/datastore/appState.js +2 -0
  13. package/phantom/datastore/appStateBackup.js +34 -0
  14. package/phantom/datastore/models/cipher/e2ee.js +48 -0
  15. package/phantom/datastore/models/cipher/vault.js +153 -0
  16. package/phantom/datastore/models/index.js +3 -0
  17. package/phantom/datastore/models/matrix/auth.js +151 -0
  18. package/phantom/datastore/models/matrix/cache.js +3 -0
  19. package/phantom/datastore/models/matrix/checker.js +2 -0
  20. package/phantom/datastore/models/matrix/clients.js +2 -0
  21. package/phantom/datastore/models/matrix/constants.js +2 -0
  22. package/phantom/datastore/models/matrix/credentials.js +2 -0
  23. package/phantom/datastore/models/matrix/cycle.js +2 -0
  24. package/phantom/datastore/models/matrix/gate.js +282 -0
  25. package/phantom/datastore/models/matrix/ghost.js +332 -0
  26. package/phantom/datastore/models/matrix/headers.js +193 -0
  27. package/phantom/datastore/models/matrix/heartbeat.js +298 -0
  28. package/phantom/datastore/models/matrix/identity.js +235 -0
  29. package/phantom/datastore/models/matrix/logger.js +271 -0
  30. package/phantom/datastore/models/matrix/monitor.js +2 -0
  31. package/phantom/datastore/models/matrix/net.js +316 -0
  32. package/phantom/datastore/models/matrix/response.js +193 -0
  33. package/phantom/datastore/models/matrix/revive.js +255 -0
  34. package/phantom/datastore/models/matrix/signals.js +2 -0
  35. package/phantom/datastore/models/matrix/store.js +263 -0
  36. package/phantom/datastore/models/matrix/telemetry.js +272 -0
  37. package/phantom/datastore/models/matrix/tools.js +93 -0
  38. package/phantom/datastore/models/matrix/transform/cookieParser.js +2 -0
  39. package/phantom/datastore/models/matrix/transform/cookies.js +114 -0
  40. package/phantom/datastore/models/matrix/transform/index.js +203 -0
  41. package/phantom/datastore/models/matrix/validator.js +157 -0
  42. package/phantom/datastore/models/types/index.d.ts +498 -0
  43. package/phantom/datastore/schema.js +167 -0
  44. package/phantom/datastore/session.js +129 -0
  45. package/phantom/datastore/threads.js +22 -0
  46. package/phantom/datastore/users.js +26 -0
  47. package/phantom/dispatch/addExternalModule.js +239 -0
  48. package/phantom/dispatch/addUserToGroup.js +161 -0
  49. package/phantom/dispatch/changeAdminStatus.js +142 -0
  50. package/phantom/dispatch/changeArchivedStatus.js +135 -0
  51. package/phantom/dispatch/changeAvatar.js +123 -0
  52. package/phantom/dispatch/changeBio.js +86 -0
  53. package/phantom/dispatch/changeBlockedStatus.js +86 -0
  54. package/phantom/dispatch/changeGroupImage.js +145 -0
  55. package/phantom/dispatch/changeThreadColor.js +172 -0
  56. package/phantom/dispatch/changeThreadEmoji.js +130 -0
  57. package/phantom/dispatch/comment.js +136 -0
  58. package/phantom/dispatch/createAITheme.js +333 -0
  59. package/phantom/dispatch/createNewGroup.js +99 -0
  60. package/phantom/dispatch/createPoll.js +148 -0
  61. package/phantom/dispatch/deleteMessage.js +131 -0
  62. package/phantom/dispatch/deleteThread.js +155 -0
  63. package/phantom/dispatch/e2ee.js +101 -0
  64. package/phantom/dispatch/editMessage.js +158 -0
  65. package/phantom/dispatch/emoji.js +143 -0
  66. package/phantom/dispatch/fetchThemeData.js +233 -0
  67. package/phantom/dispatch/follow.js +111 -0
  68. package/phantom/dispatch/forwardMessage.js +110 -0
  69. package/phantom/dispatch/friend.js +189 -0
  70. package/phantom/dispatch/gcmember.js +138 -0
  71. package/phantom/dispatch/gcname.js +131 -0
  72. package/phantom/dispatch/gcrule.js +111 -0
  73. package/phantom/dispatch/getAccess.js +109 -0
  74. package/phantom/dispatch/getBotInfo.js +81 -0
  75. package/phantom/dispatch/getBotInitialData.js +110 -0
  76. package/phantom/dispatch/getFriendsList.js +118 -0
  77. package/phantom/dispatch/getMessage.js +199 -0
  78. package/phantom/dispatch/getTheme.js +199 -0
  79. package/phantom/dispatch/getThemeInfo.js +160 -0
  80. package/phantom/dispatch/getThreadHistory.js +139 -0
  81. package/phantom/dispatch/getThreadInfo.js +153 -0
  82. package/phantom/dispatch/getThreadList.js +132 -0
  83. package/phantom/dispatch/getThreadPictures.js +93 -0
  84. package/phantom/dispatch/getUserID.js +147 -0
  85. package/phantom/dispatch/getUserInfo.js +513 -0
  86. package/phantom/dispatch/getUserInfoV2.js +146 -0
  87. package/phantom/dispatch/handleMessageRequest.js +50 -0
  88. package/phantom/dispatch/httpGet.js +63 -0
  89. package/phantom/dispatch/httpPost.js +89 -0
  90. package/phantom/dispatch/httpPostFormData.js +69 -0
  91. package/phantom/dispatch/listenMqtt.js +1236 -0
  92. package/phantom/dispatch/listenSpeed.js +179 -0
  93. package/phantom/dispatch/logout.js +93 -0
  94. package/phantom/dispatch/markAsDelivered.js +92 -0
  95. package/phantom/dispatch/markAsRead.js +119 -0
  96. package/phantom/dispatch/markAsReadAll.js +215 -0
  97. package/phantom/dispatch/markAsSeen.js +70 -0
  98. package/phantom/dispatch/mqttDeltaValue.js +278 -0
  99. package/phantom/dispatch/muteThread.js +253 -0
  100. package/phantom/dispatch/nickname.js +132 -0
  101. package/phantom/dispatch/notes.js +263 -0
  102. package/phantom/dispatch/pinMessage.js +238 -0
  103. package/phantom/dispatch/produceMetaTheme.js +335 -0
  104. package/phantom/dispatch/realtime.js +291 -0
  105. package/phantom/dispatch/removeUserFromGroup.js +248 -0
  106. package/phantom/dispatch/resolvePhotoUrl.js +217 -0
  107. package/phantom/dispatch/searchForThread.js +258 -0
  108. package/phantom/dispatch/sendMessage.js +354 -0
  109. package/phantom/dispatch/sendMessageMqtt.js +249 -0
  110. package/phantom/dispatch/sendTypingIndicator.js +206 -0
  111. package/phantom/dispatch/setMessageReaction.js +188 -0
  112. package/phantom/dispatch/setMessageReactionMqtt.js +248 -0
  113. package/phantom/dispatch/setThreadTheme.js +330 -0
  114. package/phantom/dispatch/setThreadThemeMqtt.js +207 -0
  115. package/phantom/dispatch/share.js +200 -0
  116. package/phantom/dispatch/shareContact.js +216 -0
  117. package/phantom/dispatch/stickers.js +395 -0
  118. package/phantom/dispatch/story.js +240 -0
  119. package/phantom/dispatch/theme.js +296 -0
  120. package/phantom/dispatch/unfriend.js +199 -0
  121. package/phantom/dispatch/unsendMessage.js +124 -0
@@ -0,0 +1,271 @@
1
+ "use strict";
2
+
3
+ const path = require("path");
4
+ const fs = require("fs");
5
+
6
+ const COLORS = {
7
+ reset: "\x1b[0m",
8
+ bold: "\x1b[1m",
9
+ dim: "\x1b[2m",
10
+ red: "\x1b[31m",
11
+ green: "\x1b[32m",
12
+ yellow: "\x1b[33m",
13
+ blue: "\x1b[34m",
14
+ magenta: "\x1b[35m",
15
+ cyan: "\x1b[36m",
16
+ white: "\x1b[37m",
17
+ gray: "\x1b[90m",
18
+ bgRed: "\x1b[41m",
19
+ };
20
+
21
+ const LEVEL_CONFIG = {
22
+ trace: { rank: 0, color: COLORS.gray, label: "TRACE", out: "log" },
23
+ debug: { rank: 1, color: COLORS.cyan, label: "DEBUG", out: "log" },
24
+ info: { rank: 2, color: COLORS.blue, label: "INFO", out: "log" },
25
+ success: { rank: 2, color: COLORS.green, label: "OK", out: "log" },
26
+ log: { rank: 2, color: COLORS.white, label: "LOG", out: "log" },
27
+ warn: { rank: 3, color: COLORS.yellow, label: "WARN", out: "warn" },
28
+ error: { rank: 4, color: COLORS.red, label: "ERROR", out: "error" },
29
+ critical: { rank: 5, color: COLORS.bgRed, label: "CRITICAL", out: "error" },
30
+ };
31
+
32
+ let _enabled = true;
33
+ let _minRank = 2;
34
+ let _useColor = process.stdout.isTTY ?? true;
35
+ let _fileStream = null;
36
+ let _perf = new Map();
37
+ let _history = [];
38
+ const _MAX_HISTORY = 500;
39
+
40
+ function _ts() {
41
+ const d = new Date();
42
+ const h = String(d.getHours()).padStart(2, "0");
43
+ const m = String(d.getMinutes()).padStart(2, "0");
44
+ const s = String(d.getSeconds()).padStart(2, "0");
45
+ const ms = String(d.getMilliseconds()).padStart(3, "0");
46
+ return `${h}:${m}:${s}.${ms}`;
47
+ }
48
+
49
+ function _fmt(level, tag, args) {
50
+ const cfg = LEVEL_CONFIG[level];
51
+ const ts = _ts();
52
+ const msg = args.map(a => (typeof a === "object" ? JSON.stringify(a) : String(a))).join(" ");
53
+ const plain = `[${ts}] [${cfg.label}] [${tag}] ${msg}`;
54
+ const colored = _useColor
55
+ ? `${COLORS.gray}[${ts}]${COLORS.reset} ${cfg.color}${COLORS.bold}[${cfg.label}]${COLORS.reset} ${COLORS.magenta}[${tag}]${COLORS.reset} ${msg}`
56
+ : plain;
57
+ return { plain, colored, level, tag, msg, ts, at: Date.now() };
58
+ }
59
+
60
+ function _emit(level, tag, args) {
61
+ if (!_enabled) return;
62
+ const cfg = LEVEL_CONFIG[level];
63
+ if (!cfg || cfg.rank < _minRank) return;
64
+ const { plain, colored } = _fmt(level, tag, args);
65
+ console[cfg.out](colored);
66
+ if (_fileStream) { try { _fileStream.write(plain + "\n"); } catch (_) {} }
67
+ if (_history.length >= _MAX_HISTORY) _history.shift();
68
+ _history.push({ level, tag, msg: args.join(" "), at: Date.now() });
69
+ }
70
+
71
+ function logOptions(v) { _enabled = Boolean(v); }
72
+ function setLogLevel(l) {
73
+ const lvl = typeof l === "string" ? l : "info";
74
+ const cfg = LEVEL_CONFIG[lvl];
75
+ if (cfg) _minRank = cfg.rank;
76
+ }
77
+ function setColor(v) { _useColor = Boolean(v); }
78
+
79
+ function enableFileLog(filePath) {
80
+ try {
81
+ _fileStream = fs.createWriteStream(filePath, { flags: "a" });
82
+ } catch (_) {}
83
+ }
84
+
85
+ function trace( tag, ...a) { _emit("trace", tag, a); }
86
+ function debug( tag, ...a) { _emit("debug", tag, a); }
87
+ function info( tag, ...a) { _emit("info", tag, a); }
88
+ function log( tag, ...a) { _emit("log", tag, a); }
89
+ function success(tag, ...a) { _emit("success", tag, a); }
90
+ function warn( tag, ...a) { _emit("warn", tag, a); }
91
+ function error( tag, ...a) { _emit("error", tag, a); }
92
+ function critical(tag,...a) { _emit("critical", tag, a); }
93
+ function banner(msg) { if (_enabled && _minRank <= 2) console.log(msg); }
94
+
95
+ function time(label) { _perf.set(label, process.hrtime.bigint()); }
96
+ function timeEnd(label, tag = "Perf") {
97
+ const start = _perf.get(label);
98
+ if (!start) return;
99
+ const ms = Number(process.hrtime.bigint() - start) / 1e6;
100
+ _perf.delete(label);
101
+ log(tag, `${label}: ${ms.toFixed(2)}ms`);
102
+ return ms;
103
+ }
104
+
105
+ function getHistory(last = 50) { return _history.slice(-last); }
106
+ function clearHistory() { _history = []; }
107
+
108
+ const NUM_TO_MONTH = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
109
+ const NUM_TO_DAY = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
110
+
111
+ function getRandom(arr) { return arr[Math.floor(Math.random() * arr.length)]; }
112
+ function padZeros(v, l = 2) { return String(v).padStart(l, "0"); }
113
+
114
+ function generateThreadingID(clientID) {
115
+ const ts = Date.now();
116
+ const rand = Math.floor(Math.random() * 0xFFFFFFFF).toString(16).padStart(8, "0");
117
+ const seq = Math.floor(Math.random() * 9999).toString(16).padStart(4, "0");
118
+ return `<${ts}:${rand}-${seq}-${clientID}@mail.projektitan.com>`;
119
+ }
120
+
121
+ function binaryToDecimal(data) {
122
+ let ret = "";
123
+ while (data !== "0") {
124
+ let end = 0, full = "", i = 0;
125
+ for (; i < data.length; i++) {
126
+ end = 2 * end + parseInt(data[i], 10);
127
+ full += end >= 10 ? (end -= 10, "1") : "0";
128
+ }
129
+ ret = end + ret;
130
+ data = full.slice(full.indexOf("1"));
131
+ }
132
+ return ret;
133
+ }
134
+
135
+ function generateOfflineThreadingID() {
136
+ const ret = Date.now();
137
+ const value = Math.floor(Math.random() * 0xFFFFFFFF);
138
+ const str = ("0000000000000000000000" + value.toString(2)).slice(-22);
139
+ return binaryToDecimal(ret.toString(2) + str);
140
+ }
141
+
142
+ const _ENC = {
143
+ _: "%", A: "%2", B: "000", C: "%7d", D: "%7b%22", E: "%2c%22", F: "%22%3a",
144
+ G: "%2c%22ut%22%3a1", H: "%2c%22bls%22%3a", I: "%2c%22n%22%3a%22%",
145
+ J: "%22%3a%7b%22i%22%3a0%7d", K: "%2c%22pt%22%3a0%2c%22vis%22%3a",
146
+ L: "%2c%22ch%22%3a%7b%22h%22%3a%22", M: "%7b%22v%22%3a2%2c%22time%22%3a1",
147
+ N: ".channel%22%2c%22sub%22%3a%5b", O: "%2c%22sb%22%3a1%2c%22t%22%3a%5b",
148
+ P: "%2c%22ud%22%3a100%2c%22lc%22%3a0", Q: "%5d%2c%22f%22%3anull%2c%22uct%22%3a",
149
+ R: ".channel%22%2c%22sub%22%3a%5b1%5d", S: "%22%2c%22m%22%3a0%7d%2c%7b%22i%22%3a",
150
+ T: "%2c%22blc%22%3a1%2c%22snd%22%3a1%2c%22ct%22%3a",
151
+ U: "%2c%22blc%22%3a0%2c%22snd%22%3a1%2c%22ct%22%3a",
152
+ V: "%2c%22blc%22%3a0%2c%22snd%22%3a0%2c%22ct%22%3a",
153
+ W: "%2c%22s%22%3a0%2c%22blo%22%3a0%7d%2c%22bl%22%3a%7b%22ac%22%3a",
154
+ X: "%2c%22ri%22%3a0%7d%2c%22state%22%3a%7b%22p%22%3a0%2c%22ut%22%3a1",
155
+ Y: "%2c%22pt%22%3a0%2c%22vis%22%3a1%2c%22bls%22%3a0%2c%22blc%22%3a0%2c%22snd%22%3a1%2c%22ct%22%3a",
156
+ Z: "%2c%22sb%22%3a1%2c%22t%22%3a%5b%5d%2c%22f%22%3anull%2c%22uct%22%3a0%2c%22s%22%3a0%2c%22blo%22%3a0%7d%2c%22bl%22%3a%7b%22ac%22%3a",
157
+ };
158
+ const _DEC = {};
159
+ const _encKeys = [];
160
+ for (const k in _ENC) { _DEC[_ENC[k]] = k; _encKeys.push(_ENC[k]); }
161
+ _encKeys.reverse();
162
+ const _RE = new RegExp(_encKeys.join("|"), "g");
163
+
164
+ function presenceEncode(str) {
165
+ return encodeURIComponent(str)
166
+ .replace(/([_A-Z])|%../g, (m, n) => n ? "%" + n.charCodeAt(0).toString(16) : m)
167
+ .toLowerCase()
168
+ .replace(_RE, (m) => _DEC[m]);
169
+ }
170
+
171
+ function presenceDecode(str) {
172
+ return decodeURIComponent(str.replace(/[_A-Z]/g, (m) => _ENC[m]));
173
+ }
174
+
175
+ function generatePresence(userID) {
176
+ const t = Date.now();
177
+ const noise = Math.floor(Math.random() * 999) + 1;
178
+ return "E" + presenceEncode(JSON.stringify({
179
+ v: 3,
180
+ time: Math.floor(t / 1000),
181
+ user: userID,
182
+ state: {
183
+ ut: 0, t2: [], lm2: null, uct2: t, tr: null,
184
+ tw: Math.floor(Math.random() * 0xFFFFFFFF) + 1, at: t,
185
+ },
186
+ ch: { [`p_${userID}`]: 0 },
187
+ noise,
188
+ }));
189
+ }
190
+
191
+ function generateAccessiblityCookie() {
192
+ const t = Date.now();
193
+ return encodeURIComponent(JSON.stringify({
194
+ sr: 0, "sr-ts": t, jk: 0, "jk-ts": t,
195
+ kb: 0, "kb-ts": t, hcm: 0, "hcm-ts": t,
196
+ }));
197
+ }
198
+
199
+ function getGUID() {
200
+ let sl = Date.now();
201
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
202
+ const r = Math.floor((sl + Math.random() * 16) % 16);
203
+ sl = Math.floor(sl / 16);
204
+ return (c === "x" ? r : (r & 7) | 8).toString(16);
205
+ });
206
+ }
207
+
208
+ function getFrom(str, start, end) {
209
+ if (!str || typeof str !== "string") return "";
210
+ const si = str.indexOf(start);
211
+ if (si === -1) return "";
212
+ const rest = str.substring(si + start.length);
213
+ const ei = rest.indexOf(end);
214
+ if (ei === -1) return "";
215
+ return rest.substring(0, ei);
216
+ }
217
+
218
+ function makeParsable(html) {
219
+ const clean = html.replace(/for\s*\(\s*;\s*;\s*\)\s*;\s*/, "");
220
+ const parts = clean.split(/\}\r\n *\{/);
221
+ return parts.length === 1 ? parts[0] : "[" + parts.join("},{") + "]";
222
+ }
223
+
224
+ function arrToForm(form) {
225
+ return form.reduce((acc, v) => { acc[v.name] = v.val; return acc; }, {});
226
+ }
227
+
228
+ function arrayToObject(arr, getKey, getValue) {
229
+ return arr.reduce((acc, v) => { acc[getKey(v)] = getValue(v); return acc; }, {});
230
+ }
231
+
232
+ function getSignatureID() { return Math.floor(Math.random() * 0x7FFFFFFF).toString(16); }
233
+ function generateTimestampRelative() { const d = new Date(); return d.getHours() + ":" + padZeros(d.getMinutes()); }
234
+ function getType(obj) { return Object.prototype.toString.call(obj).slice(8, -1); }
235
+ function isReadableStream(obj) { return obj !== null && typeof obj === "object" && typeof obj.pipe === "function"; }
236
+
237
+ function _readPkg() {
238
+ try { return JSON.parse(fs.readFileSync(path.resolve(__dirname, "..", "..", "package.json"), "utf8")); }
239
+ catch (_) { return {}; }
240
+ }
241
+
242
+ function startupBanner() {
243
+ const { name = "phantom-fca", version = "1.0.0" } = _readPkg();
244
+ const pad = (s, n) => String(s).padEnd(n);
245
+ const lines = [
246
+ ``,
247
+ ` ${COLORS.cyan}╔═══════════════════════════════════════════╗${COLORS.reset}`,
248
+ ` ${COLORS.cyan}║${COLORS.reset} ${COLORS.bold}${COLORS.magenta}${pad(name, 41)}${COLORS.reset}${COLORS.cyan}║${COLORS.reset}`,
249
+ ` ${COLORS.cyan}║${COLORS.reset} ${COLORS.green}Version : ${COLORS.bold}${pad(version, 32)}${COLORS.reset}${COLORS.cyan}║${COLORS.reset}`,
250
+ ` ${COLORS.cyan}║${COLORS.reset} ${COLORS.yellow}Stealth : ${COLORS.bold}${pad("Active", 32)}${COLORS.reset}${COLORS.cyan}║${COLORS.reset}`,
251
+ ` ${COLORS.cyan}║${COLORS.reset} ${COLORS.blue}Shield : ${COLORS.bold}${pad("Enabled", 32)}${COLORS.reset}${COLORS.cyan}║${COLORS.reset}`,
252
+ ` ${COLORS.cyan}║${COLORS.reset} ${COLORS.blue}Revive : ${COLORS.bold}${pad("Enabled", 32)}${COLORS.reset}${COLORS.cyan}║${COLORS.reset}`,
253
+ ` ${COLORS.cyan}╚═══════════════════════════════════════════╝${COLORS.reset}`,
254
+ ``,
255
+ ];
256
+ if (_useColor) lines.forEach(l => console.log(l));
257
+ else console.log(`${name} v${version} | Stealth Active | Shield Enabled`);
258
+ }
259
+
260
+ module.exports = {
261
+ logOptions, setLogLevel, setColor, enableFileLog,
262
+ trace, debug, info, log, success, warn, error, critical, banner,
263
+ time, timeEnd, getHistory, clearHistory,
264
+ getRandom, padZeros,
265
+ generateThreadingID, binaryToDecimal, generateOfflineThreadingID,
266
+ presenceEncode, presenceDecode, generatePresence, generateAccessiblityCookie,
267
+ getGUID, getFrom, makeParsable, arrToForm, arrayToObject,
268
+ getSignatureID, generateTimestampRelative, getType, isReadableStream,
269
+ startupBanner, phantomBanner: startupBanner,
270
+ NUM_TO_MONTH, NUM_TO_DAY,
271
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ module.exports = require("./telemetry");
@@ -0,0 +1,316 @@
1
+ "use strict";
2
+
3
+ const axios = require("axios");
4
+ const { CookieJar } = require("tough-cookie");
5
+ const { wrapper } = require("axios-cookiejar-support");
6
+ const FormData = require("form-data");
7
+ const { getHeaders } = require("./headers");
8
+ const { getType } = require("./logger");
9
+ const { globalGate } = require("./gate");
10
+
11
+ const _jar = new CookieJar();
12
+ const _client = wrapper(axios.create({ jar: _jar }));
13
+
14
+ let _proxy = {};
15
+ let _netStats = { req: 0, err: 0, deduped: 0, cachedHits: 0 };
16
+
17
+ const _inflightMap = new Map();
18
+ const _responseCache = new Map();
19
+ const CACHE_TTL_MS = 5_000;
20
+ const MAX_CACHE_SIZE = 100;
21
+
22
+ const pause = (ms) => new Promise(r => setTimeout(r, ms));
23
+ const quickPause = (ms) => {
24
+ if (ms <= 0) return Promise.resolve();
25
+ if (ms <= 5) return new Promise(r => setImmediate(r));
26
+ return new Promise(r => setTimeout(r, ms));
27
+ };
28
+
29
+ function _getCacheKey(url, params) {
30
+ if (!params) return url;
31
+ try { return url + "?" + new URLSearchParams(params).toString(); } catch (_) { return url; }
32
+ }
33
+
34
+ function _pruneCache() {
35
+ const now = Date.now();
36
+ for (const [k, v] of _responseCache) {
37
+ if (now > v.exp) _responseCache.delete(k);
38
+ }
39
+ }
40
+
41
+ function _adapt(res) {
42
+ const r = res.response || res;
43
+ return {
44
+ ...r,
45
+ body: r.data,
46
+ statusCode: r.status,
47
+ request: {
48
+ uri: new URL(r.config?.url || "https://www.facebook.com"),
49
+ headers: r.config?.headers || {},
50
+ method: (r.config?.method || "GET").toUpperCase(),
51
+ form: r.config?.data,
52
+ formData: r.config?.data,
53
+ },
54
+ };
55
+ }
56
+
57
+ const CHECKPOINT_PATTERNS = [
58
+ { sig: "1501092823525282", code: "checkpoint_282", msg: "Bot checkpoint 282" },
59
+ { sig: "828281030927956", code: "checkpoint_956", msg: "Bot checkpoint 956" },
60
+ { sig: "XCheckpointFBScrapingWarningController",code: "checkpoint_scraping",msg: "Scraping warning checkpoint" },
61
+ { sig: "601051028565049", code: "checkpoint_601", msg: "Automation checkpoint 601" },
62
+ ];
63
+
64
+ async function _inspectBody(adapted, ctx) {
65
+ if (!ctx || ctx._skipSessionInspect) return;
66
+ const body = adapted.body;
67
+ if (!body) return;
68
+ const bs = typeof body === "string" ? body : JSON.stringify(body);
69
+
70
+ for (const { sig, code, msg } of CHECKPOINT_PATTERNS) {
71
+ if (bs.includes(sig)) {
72
+ const e = Object.assign(new Error(msg), { error: code, res: body });
73
+ ctx._emitter?.emit?.(code, { res: body });
74
+ throw e;
75
+ }
76
+ }
77
+
78
+ const isLoginPage =
79
+ bs.includes('<form id="login_form"') ||
80
+ bs.includes('id="loginbutton"') ||
81
+ bs.includes('"login_page"') ||
82
+ (bs.includes("login.php") && bs.includes('"next":"'));
83
+
84
+ const isLoginBlocked = typeof body === "object" && body?.error === 1357001;
85
+
86
+ if (isLoginBlocked) {
87
+ throw Object.assign(new Error("Login blocked"), { error: "login_blocked", res: body });
88
+ }
89
+
90
+ if (isLoginPage) {
91
+ ctx._emitter?.emit?.("sessionExpired", { res: body });
92
+ if (!ctx.auto_login && typeof ctx.performAutoLogin === "function") {
93
+ ctx.auto_login = true;
94
+ const t = setTimeout(() => { ctx.auto_login = false; }, 120_000);
95
+ try {
96
+ const ok = await ctx.performAutoLogin();
97
+ clearTimeout(t);
98
+ ctx.auto_login = false;
99
+ if (!ok) throw Object.assign(new Error("Not logged in. Auto-revive failed."), { error: "Not logged in.", res: body });
100
+ } catch (e) {
101
+ clearTimeout(t);
102
+ ctx.auto_login = false;
103
+ throw e;
104
+ }
105
+ } else {
106
+ throw Object.assign(new Error("Not logged in. Session expired."), { error: "Not logged in.", res: body });
107
+ }
108
+ }
109
+ }
110
+
111
+ const ERROR_COOLDOWNS = {
112
+ 1545012: 90_000,
113
+ 1675004: 45_000,
114
+ 368: 180_000,
115
+ 10: 120_000,
116
+ 404: 8_000,
117
+ 500: 15_000,
118
+ 503: 45_000,
119
+ 429: 120_000,
120
+ };
121
+
122
+ function _applyCooldowns(body, ep, tid) {
123
+ if (!body || typeof body !== "object") return;
124
+ function apply(code) {
125
+ if (!code || !ERROR_COOLDOWNS[code]) return;
126
+ const ms = ERROR_COOLDOWNS[code];
127
+ if (tid) globalGate.setThreadCooldown(tid, ms);
128
+ if (ep) globalGate.setEndpointCooldown(ep, ms);
129
+ }
130
+ apply(body.error);
131
+ if (Array.isArray(body)) body.forEach(item => {
132
+ apply(item?.error);
133
+ (item?.errors || []).forEach(e => apply(e.code || e.extensions?.code));
134
+ });
135
+ (body.errors || []).forEach(e => apply(e.code || e.extensions?.code));
136
+ }
137
+
138
+ const FATAL_ERRORS = new Set([
139
+ "Not logged in.", "checkpoint_282", "checkpoint_956",
140
+ "checkpoint_scraping", "checkpoint_601", "login_blocked",
141
+ ]);
142
+
143
+ const NET_CODES = new Set([
144
+ "ECONNRESET","ETIMEDOUT","ECONNREFUSED","ENETUNREACH",
145
+ "EHOSTUNREACH","EAI_AGAIN","ENOTFOUND","ESOCKETTIMEDOUT","EPIPE",
146
+ ]);
147
+
148
+ async function _withRetry(fn, retries = 3, ep = "", tid = "", ctx = null) {
149
+ let release = null;
150
+ const hasLimiting = !!(ep || tid);
151
+ if (hasLimiting) release = await globalGate.checkRateLimit(false, ep);
152
+
153
+ const globalWait = globalGate.getRemainingCooldown?.("__GLOBAL__") || 0;
154
+ if (globalWait > 0) await pause(globalWait);
155
+
156
+ try {
157
+ for (let i = 0; i < retries; i++) {
158
+ try {
159
+ _netStats.req++;
160
+ const res = _adapt(await fn());
161
+ _applyCooldowns(res.body, ep, tid);
162
+ await _inspectBody(res, ctx);
163
+ globalGate.recordRequest(true);
164
+ return res;
165
+ } catch (e) {
166
+ if (FATAL_ERRORS.has(e.error)) throw e;
167
+
168
+ if (e.code === "ERR_INVALID_CHAR" || e.message?.includes("Invalid character in header")) {
169
+ throw Object.assign(new Error("Invalid header character"), { error: "invalid_header" });
170
+ }
171
+
172
+ if (e.response) _applyCooldowns(_adapt(e.response).body, ep, tid);
173
+
174
+ _netStats.err++;
175
+ globalGate.recordRequest(false);
176
+
177
+ if (i === retries - 1) {
178
+ if (e.response) return _adapt(e.response);
179
+ throw e;
180
+ }
181
+
182
+ const isNet = NET_CODES.has(e.code) || NET_CODES.has((e.message || "").split(" ")[0]);
183
+ const base = isNet ? 500 : 1000;
184
+ const wait = Math.min(30_000, Math.pow(2, i) * base + Math.random() * 1000);
185
+ await quickPause(wait);
186
+ }
187
+ }
188
+ } finally {
189
+ release?.();
190
+ }
191
+ }
192
+
193
+ function setProxy(url) {
194
+ if (!url) { _proxy = {}; return; }
195
+ try {
196
+ const p = new URL(url);
197
+ _proxy = {
198
+ proxy: {
199
+ host: p.hostname,
200
+ port: Number(p.port) || (p.protocol === "https:" ? 443 : 80),
201
+ protocol: p.protocol.replace(":", ""),
202
+ auth: p.username ? { username: p.username, password: p.password } : undefined,
203
+ },
204
+ };
205
+ } catch (_) { _proxy = {}; }
206
+ }
207
+
208
+ function cleanGet(url) {
209
+ return _withRetry(() => _client.get(url, { timeout: 60_000, ..._proxy }));
210
+ }
211
+
212
+ async function get(url, jar, params, opts, ctx, customHeader) {
213
+ const ep = new URL(url).pathname;
214
+ const tid = ctx?.requestThreadID ? String(ctx.requestThreadID) : "";
215
+
216
+ const cacheKey = _getCacheKey(url, params);
217
+ const cached = _responseCache.get(cacheKey);
218
+ if (cached && Date.now() < cached.exp && !customHeader) {
219
+ _netStats.cachedHits++;
220
+ return cached.res;
221
+ }
222
+
223
+ if (_inflightMap.has(cacheKey)) {
224
+ _netStats.deduped++;
225
+ return _inflightMap.get(cacheKey);
226
+ }
227
+
228
+ const prom = _withRetry(() => _client.get(url, {
229
+ headers: getHeaders(url, opts, ctx, customHeader),
230
+ timeout: 60_000,
231
+ params,
232
+ ..._proxy,
233
+ validateStatus: s => s >= 200 && s < 600,
234
+ decompress: true,
235
+ maxContentLength: 100 * 1024 * 1024,
236
+ maxBodyLength: 100 * 1024 * 1024,
237
+ }), 3, ep, tid, ctx);
238
+
239
+ _inflightMap.set(cacheKey, prom);
240
+ prom.then(res => {
241
+ if (_responseCache.size >= MAX_CACHE_SIZE) _pruneCache();
242
+ _responseCache.set(cacheKey, { res, exp: Date.now() + CACHE_TTL_MS });
243
+ }).catch(() => {}).finally(() => { _inflightMap.delete(cacheKey); });
244
+
245
+ return prom;
246
+ }
247
+
248
+ async function post(url, jar, form, opts, ctx, customHeader) {
249
+ const headers = getHeaders(url, opts, ctx, customHeader, "xhr");
250
+ const ct = headers["Content-Type"] || "application/x-www-form-urlencoded";
251
+
252
+ let data;
253
+ if (ct.includes("json")) {
254
+ data = JSON.stringify(form);
255
+ } else {
256
+ const sp = new URLSearchParams();
257
+ for (const k in form) {
258
+ if (Object.prototype.hasOwnProperty.call(form, k)) {
259
+ const v = form[k];
260
+ sp.append(k, getType(v) === "Object" ? JSON.stringify(v) : String(v ?? ""));
261
+ }
262
+ }
263
+ data = sp.toString();
264
+ }
265
+
266
+ headers["Content-Type"] = ct;
267
+ headers["Content-Length"] = String(Buffer.byteLength(data, "utf8"));
268
+
269
+ const ep = new URL(url).pathname;
270
+ const tid = ctx?.requestThreadID ? String(ctx.requestThreadID) : "";
271
+
272
+ return _withRetry(() => _client.post(url, data, {
273
+ headers, timeout: 60_000, ..._proxy,
274
+ validateStatus: s => s >= 200 && s < 600,
275
+ decompress: true,
276
+ maxContentLength: 100 * 1024 * 1024,
277
+ maxBodyLength: 100 * 1024 * 1024,
278
+ }), 3, ep, tid, ctx);
279
+ }
280
+
281
+ async function postFormData(url, jar, form, params, opts, ctx) {
282
+ const fd = new FormData();
283
+ for (const k in form) {
284
+ if (Object.prototype.hasOwnProperty.call(form, k)) fd.append(k, form[k]);
285
+ }
286
+ const boundary = fd.getBoundary();
287
+ const customHdr = { "Content-Type": `multipart/form-data; boundary=${boundary}` };
288
+ const ep = new URL(url).pathname;
289
+ const tid = ctx?.requestThreadID ? String(ctx.requestThreadID) : "";
290
+
291
+ return _withRetry(() => _client.post(url, fd, {
292
+ headers: getHeaders(url, opts, ctx, customHdr, "xhr"),
293
+ timeout: 90_000,
294
+ params,
295
+ ..._proxy,
296
+ validateStatus: s => s >= 200 && s < 600,
297
+ decompress: true,
298
+ maxContentLength: 200 * 1024 * 1024,
299
+ maxBodyLength: 200 * 1024 * 1024,
300
+ }), 3, ep, tid, ctx);
301
+ }
302
+
303
+ function getNetStats() { return { ..._netStats }; }
304
+ function clearNetCache() { _responseCache.clear(); }
305
+
306
+ module.exports = {
307
+ get,
308
+ post,
309
+ postFormData,
310
+ cleanGet,
311
+ getNetStats,
312
+ clearNetCache,
313
+ getJar: () => _jar,
314
+ setProxy,
315
+ requestWithRetry: _withRetry,
316
+ };