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,298 @@
1
+ "use strict";
2
+
3
+ const { log, warn, error } = require("./logger");
4
+ const { isNetworkError } = require("./revive");
5
+
6
+ function _jitter(ms) { return Math.floor(Math.random() * ms); }
7
+
8
+ class CycleManager {
9
+ constructor() {
10
+ this._timer = null;
11
+ this._healthTimer = null;
12
+ this._keepalive = null;
13
+ this._cookieTimer = null;
14
+ this._adaptTimer = null;
15
+
16
+ this.CYCLE_MS = 2 * 60 * 60_000;
17
+ this.HEALTH_MS = 20 * 60_000;
18
+ this.KEEPALIVE_MS = 4 * 60_000;
19
+ this.COOKIE_MS = 10 * 60 * 60_000;
20
+ this.MIN_CYCLE_MS = 30 * 60_000;
21
+ this.MAX_CYCLE_MS = 6 * 60 * 60_000;
22
+
23
+ this.lastRefresh = Date.now();
24
+ this.failureCount = 0;
25
+ this.MAX_FAILURES = 20;
26
+ this.onExpiry = null;
27
+
28
+ this._refreshing = false;
29
+ this._refreshLock = null;
30
+ this._unlock = null;
31
+ this._successStreak = 0;
32
+ this._ctx = null;
33
+ this._funcs = null;
34
+ this._origin = null;
35
+ this._tokenCache = new Map();
36
+ this._healthHistory = [];
37
+ this._adaptedCycleMs = this.CYCLE_MS;
38
+ this._keepaliveErrors = 0;
39
+ this._lastHealthCheck = null;
40
+ }
41
+
42
+ async _acquireLock() {
43
+ if (this._refreshing) {
44
+ try { await this._refreshLock; } catch (_) {}
45
+ }
46
+ if (this._refreshing) return false;
47
+ this._refreshing = true;
48
+ this._refreshLock = new Promise(r => { this._unlock = r; });
49
+ return true;
50
+ }
51
+
52
+ _releaseLock() {
53
+ this._refreshing = false;
54
+ if (this._unlock) { this._unlock(); this._unlock = null; }
55
+ }
56
+
57
+ setExpiryHandler(fn) { this.onExpiry = fn; }
58
+ setSessionExpiryCallback(fn) { this.onExpiry = fn; }
59
+
60
+ _adaptCycleInterval() {
61
+ if (this._successStreak >= 10) {
62
+ this._adaptedCycleMs = Math.min(this.MAX_CYCLE_MS, this._adaptedCycleMs + 15 * 60_000);
63
+ } else if (this.failureCount > 2) {
64
+ this._adaptedCycleMs = Math.max(this.MIN_CYCLE_MS, this._adaptedCycleMs - 20 * 60_000);
65
+ }
66
+ }
67
+
68
+ startAutoRefresh(ctx, funcs, origin) {
69
+ this._ctx = ctx;
70
+ this._funcs = funcs;
71
+ this._origin = origin;
72
+
73
+ this._clearAllTimers();
74
+
75
+ const schedule = () => {
76
+ this._adaptCycleInterval();
77
+ const next = this._adaptedCycleMs + _jitter(5 * 60_000);
78
+ this._timer = setTimeout(async () => {
79
+ try {
80
+ const ok = await this.refreshTokens(ctx, funcs, origin);
81
+ if (ok) {
82
+ this._successStreak++;
83
+ log("Heartbeat", `Tokens refreshed (streak:${this._successStreak}, cycle:${Math.round(this._adaptedCycleMs / 60_000)}min)`);
84
+ } else {
85
+ this._successStreak = 0;
86
+ warn("Heartbeat", "Token refresh returned false");
87
+ }
88
+ } catch (e) {
89
+ this._successStreak = 0;
90
+ error("Heartbeat", "Token refresh error:", e.message);
91
+ } finally {
92
+ schedule();
93
+ }
94
+ }, next);
95
+ try { this._timer.unref(); } catch (_) {}
96
+ log("Heartbeat", `Next token refresh in ${Math.round(next / 60_000)}min`);
97
+ };
98
+
99
+ this._healthTimer = setInterval(async () => {
100
+ try {
101
+ const ok = await this._checkHealth(ctx, funcs, origin);
102
+ this._lastHealthCheck = { ok, at: Date.now() };
103
+ this._healthHistory.push({ ok: ok === true, at: Date.now() });
104
+ if (this._healthHistory.length > 30) this._healthHistory.shift();
105
+
106
+ if (ok === "network_error") {
107
+ warn("Heartbeat", "Health probe — network issue, skipping"); return;
108
+ }
109
+ if (!ok) {
110
+ warn("Heartbeat", "Health check failed — trying token refresh");
111
+ this._successStreak = 0;
112
+ const refreshed = await this.refreshTokens(ctx, funcs, origin);
113
+ if (!refreshed) {
114
+ warn("Heartbeat", "Refresh also failed — trying alt refresh");
115
+ await this._refreshAlt(ctx, funcs, origin);
116
+ }
117
+ }
118
+ } catch (e) {
119
+ if (isNetworkError(e)) { warn("Heartbeat", "Health check — network:", e.message); return; }
120
+ error("Heartbeat", "Health check error:", e.message);
121
+ }
122
+ }, this.HEALTH_MS + _jitter(2 * 60_000));
123
+
124
+ this._keepalive = setInterval(() => {
125
+ this._sendKeepalive(ctx).catch(e => {
126
+ this._keepaliveErrors++;
127
+ if (this._keepaliveErrors > 10) {
128
+ warn("Heartbeat", "Keepalive repeatedly failing — network may be unstable");
129
+ this._keepaliveErrors = 0;
130
+ }
131
+ });
132
+ }, this.KEEPALIVE_MS + _jitter(60_000));
133
+
134
+ this._cookieTimer = setInterval(async () => {
135
+ try { await this._refreshCookies(ctx, funcs, origin); }
136
+ catch (e) { if (!isNetworkError(e)) warn("Heartbeat", "Cookie refresh failed:", e.message); }
137
+ }, this.COOKIE_MS + _jitter(30 * 60_000));
138
+
139
+ [this._healthTimer, this._keepalive, this._cookieTimer].forEach(t => {
140
+ try { t.unref(); } catch (_) {}
141
+ });
142
+
143
+ schedule();
144
+ }
145
+
146
+ _clearAllTimers() {
147
+ ["_timer","_healthTimer","_keepalive","_cookieTimer","_adaptTimer"].forEach(k => {
148
+ if (this[k]) { clearInterval(this[k]); clearTimeout(this[k]); this[k] = null; }
149
+ });
150
+ }
151
+
152
+ async _checkHealth(ctx, funcs, origin) {
153
+ try {
154
+ const net = require("./net");
155
+ const opts = { ...ctx.globalOptions, _skipSessionInspect: true };
156
+ const r = await net.get(
157
+ "https://www.facebook.com/ajax/presence/reconnect.php",
158
+ ctx.jar, { reason: "14", __a: 1 }, opts, { _skipSessionInspect: true }
159
+ );
160
+ const html = typeof r.body === "string" ? r.body : "";
161
+ if (!html && r.statusCode >= 500) return "network_error";
162
+ const ok = html &&
163
+ !html.includes("login_form") &&
164
+ !html.includes('"login_page"') &&
165
+ !html.includes('"checkpoint"');
166
+ return !!ok;
167
+ } catch (e) {
168
+ if (isNetworkError(e)) return "network_error";
169
+ return false;
170
+ }
171
+ }
172
+
173
+ async refreshTokens(ctx, funcs, origin) {
174
+ if (!await this._acquireLock()) return false;
175
+ try {
176
+ const net = require("./net");
177
+ const url = origin || "https://www.facebook.com";
178
+ const r = await net.get(url, ctx.jar, null, ctx.globalOptions, {
179
+ noRef: true, _skipSessionInspect: true,
180
+ });
181
+ const html = typeof r.body === "string" ? r.body : "";
182
+ if (!html) return false;
183
+
184
+ const tryExtract = (key) => {
185
+ const patterns = [
186
+ new RegExp(`"${key}"[^}]*"token":"([^"]+)"`),
187
+ new RegExp(`"${key}",\\[\\],\\{"token":"([^"]+)"`),
188
+ new RegExp(`"token":"([^"]+)"[^}]*"${key}"`),
189
+ ];
190
+ for (const re of patterns) {
191
+ const m = html.match(re);
192
+ if (m?.[1]) return m[1];
193
+ }
194
+ return null;
195
+ };
196
+
197
+ const dtsg = tryExtract("DTSGInitialData") || html.match(/"token":"([^"]{10,})"/)?.[1];
198
+ const lsd = tryExtract("LSD") || html.match(/"LSD"[^}]*"token":"([^"]+)"/)?.[1];
199
+
200
+ if (!dtsg) { this.failureCount++; return false; }
201
+
202
+ ctx.fb_dtsg = dtsg;
203
+ ctx.lsd = lsd || ctx.lsd;
204
+ ctx.jazoest = "2" + Array.from(dtsg).reduce((a, c) => a + c.charCodeAt(0), 0);
205
+
206
+ const spinR = html.match(/"__spin_r":(\d+)/)?.[1];
207
+ const spinB = html.match(/"__spin_b":"([^"]+)"/)?.[1];
208
+ const spinT = html.match(/"__spin_t":(\d+)/)?.[1];
209
+ if (spinR) ctx.__spin_r = spinR;
210
+ if (spinB) ctx.__spin_b = spinB;
211
+ if (spinT) ctx.__spin_t = spinT;
212
+
213
+ const cookies = ctx.jar.getCookiesSync("https://www.facebook.com");
214
+ const cUser = cookies.find(c => c.key === "c_user");
215
+ if (cUser) ctx.userID = cUser.value;
216
+
217
+ this.failureCount = 0;
218
+ this.lastRefresh = Date.now();
219
+ this._tokenCache.set("dtsg", { val: dtsg, at: Date.now() });
220
+ log("Heartbeat", "Tokens refreshed successfully");
221
+ return true;
222
+
223
+ } catch (e) {
224
+ this.failureCount++;
225
+ if (isNetworkError(e)) { warn("Heartbeat", "Token refresh — network error:", e.message); return false; }
226
+ error("Heartbeat", "refreshTokens failed:", e.message);
227
+ if (this.failureCount >= this.MAX_FAILURES && this.onExpiry) this.onExpiry(e);
228
+ return false;
229
+ } finally {
230
+ this._releaseLock();
231
+ }
232
+ }
233
+
234
+ async _refreshAlt(ctx, funcs, origin) {
235
+ try {
236
+ if (!funcs?.post) return false;
237
+ const r = await funcs.post("https://www.facebook.com/ajax/dtsg/", ctx.jar, { __a: 1 }, ctx);
238
+ const body = typeof r.body === "string" ? r.body.replace(/^for\s*\(\s*;\s*;\s*\)\s*;\s*/, "") : null;
239
+ if (!body) return false;
240
+ const parsed = JSON.parse(body);
241
+ const dtsg = parsed?.jsmods?.define?.[0]?.[2]?.token;
242
+ if (dtsg) { ctx.fb_dtsg = dtsg; log("Heartbeat", "Alt token refresh succeeded"); return true; }
243
+ } catch (_) {}
244
+ return false;
245
+ }
246
+
247
+ async _sendKeepalive(ctx) {
248
+ try {
249
+ const net = require("./net");
250
+ await net.get(
251
+ "https://www.facebook.com/ajax/presence/reconnect.php",
252
+ ctx.jar, { reason: "14", __a: 1 }, ctx.globalOptions,
253
+ { _skipSessionInspect: true }
254
+ );
255
+ } catch (_) {}
256
+ }
257
+
258
+ async _refreshCookies(ctx, funcs, origin) {
259
+ try {
260
+ const net = require("./net");
261
+ const r = await net.get("https://www.facebook.com", ctx.jar, null, ctx.globalOptions, {
262
+ noRef: true, _skipSessionInspect: true,
263
+ });
264
+ if (r.statusCode === 200) log("Heartbeat", "Cookie refresh done");
265
+ } catch (_) {}
266
+ }
267
+
268
+ halt() {
269
+ this._clearAllTimers();
270
+ this._releaseLock();
271
+ log("Heartbeat", "Halted");
272
+ }
273
+
274
+ stopAutoRefresh() { this.halt(); }
275
+
276
+ resetFailureCount() { this.failureCount = 0; this._successStreak = 0; }
277
+ getFailureCount() { return this.failureCount; }
278
+
279
+ getStatus() {
280
+ return {
281
+ lastRefresh: this.lastRefresh,
282
+ failures: this.failureCount,
283
+ streak: this._successStreak,
284
+ adaptedCycleMin: Math.round(this._adaptedCycleMs / 60_000),
285
+ lastHealthCheck: this._lastHealthCheck,
286
+ healthHistory: this._healthHistory.slice(-5),
287
+ };
288
+ }
289
+
290
+ getTimeUntilNextRefresh() {
291
+ return Math.max(0, (this.lastRefresh + this._adaptedCycleMs) - Date.now());
292
+ }
293
+ }
294
+
295
+ module.exports = {
296
+ CycleManager,
297
+ TokenRefreshManager: CycleManager,
298
+ };
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+
3
+ const { getRandom } = require("./logger");
4
+
5
+ const BROWSER_POOL = {
6
+ windows: {
7
+ platform: "Windows NT 10.0; Win64; x64",
8
+ chrome: ["136.0.0.0","135.0.7049.52","134.0.6998.88","133.0.6943.98","132.0.6834.110","131.0.6778.86","130.0.6723.92","129.0.6668.101"],
9
+ edge: ["136.0.0.0","135.0.3179.52","134.0.3124.72","133.0.3065.92","131.0.2903.51","130.0.2849.68"],
10
+ firefox: ["134.0","133.0","132.0","131.0","130.0","129.0"],
11
+ platV: '"15.0.0"',
12
+ },
13
+ mac: {
14
+ platform: "Macintosh; Intel Mac OS X 10_15_7",
15
+ chrome: ["136.0.0.0","135.0.7049.52","134.0.6998.88","133.0.6943.98","132.0.6834.110","131.0.6778.86","130.0.6723.92"],
16
+ edge: ["136.0.0.0","135.0.3179.52","134.0.3124.72","133.0.3065.92","131.0.2903.51"],
17
+ firefox: ["134.0","133.0","132.0","131.0","130.0"],
18
+ safari: ["17.4.1","17.3","17.2","17.1","17.0","16.6"],
19
+ platV: '"14.7.0"',
20
+ },
21
+ linux: {
22
+ platform: "X11; Linux x86_64",
23
+ chrome: ["136.0.0.0","135.0.7049.52","134.0.6998.88","133.0.6943.98","132.0.6834.110","131.0.6778.86"],
24
+ firefox: ["134.0","133.0","132.0","131.0","130.0"],
25
+ platV: '""',
26
+ },
27
+ };
28
+
29
+ const ANDROID_MODELS = [
30
+ "Pixel 9 Pro", "Pixel 9", "Pixel 8 Pro", "Pixel 8", "Pixel 7 Pro", "Pixel 7",
31
+ "Samsung Galaxy S25 Ultra", "Samsung Galaxy S25+", "Samsung Galaxy S25",
32
+ "Samsung Galaxy S24 Ultra", "Samsung Galaxy S24+", "Samsung Galaxy S24",
33
+ "Samsung Galaxy S23", "Samsung Galaxy A54", "Samsung Galaxy A34",
34
+ "OnePlus 12", "OnePlus 12R", "OnePlus 11",
35
+ "Xiaomi 14 Ultra", "Xiaomi 14 Pro", "Xiaomi 14",
36
+ "OPPO Find X7 Pro", "OPPO Find X7", "OPPO Reno 12 Pro",
37
+ "realme GT 5 Pro", "realme GT 6", "Vivo X100 Pro",
38
+ "Nothing Phone (2a)", "ASUS ROG Phone 8 Pro",
39
+ ];
40
+
41
+ const ANDROID_VERSIONS = ["13", "14", "15"];
42
+ const ANDROID_LOCALES = [
43
+ "en-US", "en-GB", "en-AU", "en-CA", "en-IN",
44
+ "es-US", "es-MX", "fr-FR", "de-DE",
45
+ "ja-JP", "ko-KR", "pt-BR", "it-IT",
46
+ ];
47
+
48
+ const GREASY_BRANDS = [
49
+ "Not_A Brand",
50
+ "Not;A=Brand",
51
+ "Not A;Brand",
52
+ "Not/A)Brand",
53
+ "NotABrand",
54
+ "Not=A,Brand",
55
+ "Not A Brand",
56
+ ];
57
+
58
+ function _ri(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
59
+ function _rch(arr) { return arr[Math.floor(Math.random() * arr.length)]; }
60
+
61
+ function _buildAndroidBuildID() {
62
+ const prefixes = ["QP1A", "RP1A", "SP1A", "TP1A", "UP1A", "AP1A", "BP1A", "GP1A"];
63
+ return `${_rch(prefixes)}.${_ri(200000, 260000)}.${_ri(10, 99).toString().padStart(3, "0")}`;
64
+ }
65
+
66
+ function _resolution() {
67
+ return _rch([
68
+ { width: 720, height: 1280, density: 2.0 },
69
+ { width: 1080, height: 1920, density: 2.625 },
70
+ { width: 1080, height: 2340, density: 2.75 },
71
+ { width: 1080, height: 2400, density: 3.0 },
72
+ { width: 1440, height: 3040, density: 3.5 },
73
+ { width: 1440, height: 3088, density: 3.625 },
74
+ { width: 1440, height: 3200, density: 4.0 },
75
+ ]);
76
+ }
77
+
78
+ function _buildBrands(bName, major, ver) {
79
+ const greasy = _rch(GREASY_BRANDS);
80
+ const greasyVer = _rch(["8", "24", "99", "100"]);
81
+ const chromiumMaj = major;
82
+ const out = [];
83
+
84
+ if (bName === "Microsoft Edge") {
85
+ out.push(
86
+ `"Chromium";v="${chromiumMaj}"`,
87
+ `"${greasy}";v="${greasyVer}"`,
88
+ `"Microsoft Edge";v="${major}"`,
89
+ );
90
+ } else {
91
+ out.push(
92
+ `"Google Chrome";v="${major}"`,
93
+ `"${greasy}";v="${greasyVer}"`,
94
+ `"Chromium";v="${major}"`,
95
+ );
96
+ }
97
+
98
+ const secChUa = out.join(", ");
99
+ const secChUaFullVersionList = out.map(b => {
100
+ const m = b.match(/v="(\d+)"/);
101
+ return (m && m[1] === major) ? b.replace(`v="${major}"`, `v="${ver}"`) : b;
102
+ }).join(", ");
103
+
104
+ return { secChUa, secChUaFullVersionList };
105
+ }
106
+
107
+ function randomDesktopIdentity() {
108
+ const os = getRandom(Object.keys(BROWSER_POOL));
109
+ const data = BROWSER_POOL[os];
110
+ const rand = Math.random();
111
+ let browser, versions, useFirefox = false, useSafari = false;
112
+
113
+ if (os === "mac" && rand > 0.85 && data.safari) {
114
+ useSafari = true;
115
+ browser = "Safari";
116
+ versions = data.safari;
117
+ } else if (rand > 0.75 && data.firefox) {
118
+ useFirefox = true;
119
+ browser = "Firefox";
120
+ versions = data.firefox;
121
+ } else if (rand > 0.6 && data.edge) {
122
+ browser = "Microsoft Edge";
123
+ versions = data.edge;
124
+ } else {
125
+ browser = "Google Chrome";
126
+ versions = data.chrome;
127
+ }
128
+
129
+ const ver = getRandom(versions);
130
+ const major = ver.split(".")[0];
131
+
132
+ if (useSafari) {
133
+ const ua = `Mozilla/5.0 (${data.platform}) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/${ver} Safari/605.1.15`;
134
+ return {
135
+ userAgent: ua, browser,
136
+ secChUa: "", secChUaFullVersionList: "",
137
+ secChUaPlatform: '"macOS"', secChUaPlatformVersion: data.platV,
138
+ };
139
+ }
140
+
141
+ if (useFirefox) {
142
+ const ua = `Mozilla/5.0 (${data.platform}; rv:${ver}) Gecko/20100101 Firefox/${ver}`;
143
+ const platName = os === "windows" ? "Windows" : os === "mac" ? "macOS" : "Linux";
144
+ return {
145
+ userAgent: ua, browser,
146
+ secChUa: `"Firefox";v="${major}", "Not/A)Brand";v="8"`,
147
+ secChUaFullVersionList: `"Firefox";v="${ver}", "Not/A)Brand";v="8.0.0.0"`,
148
+ secChUaPlatform: `"${platName}"`, secChUaPlatformVersion: data.platV,
149
+ };
150
+ }
151
+
152
+ const useEdge = browser === "Microsoft Edge";
153
+ const { secChUa, secChUaFullVersionList } = _buildBrands(browser, major, ver);
154
+ const platName = os === "windows" ? "Windows" : os === "mac" ? "macOS" : "Linux";
155
+
156
+ const ua = useEdge
157
+ ? `Mozilla/5.0 (${data.platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${ver} Safari/537.36 Edg/${ver}`
158
+ : `Mozilla/5.0 (${data.platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${ver} Safari/537.36`;
159
+
160
+ return {
161
+ userAgent: ua, browser,
162
+ secChUa, secChUaFullVersionList,
163
+ secChUaPlatform: `"${platName}"`,
164
+ secChUaPlatformVersion: data.platV,
165
+ };
166
+ }
167
+
168
+ function randomAndroidIdentity(opts = {}) {
169
+ const model = _rch(ANDROID_MODELS);
170
+ const av = _rch(ANDROID_VERSIONS);
171
+ const locale = _rch(ANDROID_LOCALES);
172
+ const build = _buildAndroidBuildID();
173
+ const res = _resolution();
174
+ const fbVer = opts.fbAndroidVersion || _rch(["467.0.0.35.108","466.0.0.34.107","465.0.0.33.106"]);
175
+ const chromeV = _rch(["126.0.6478.122","125.0.6422.52","124.0.6367.82"]);
176
+ const modelKey = model.replace(/ /g, "_");
177
+
178
+ const ua = `Mozilla/5.0 (Linux; Android ${av}; ${model} Build/${build}; wv) ` +
179
+ `AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/${chromeV} Mobile Safari/537.36 ` +
180
+ `[FBAN/FB4A;FBAV/${fbVer};FBDV/${modelKey};FBMD/${modelKey};` +
181
+ `FBSN/Android;FBSV/${av};FBSS/3;FBID/phone;FBLC/${locale.replace("-", "_")};FBOP/1]`;
182
+
183
+ return {
184
+ userAgent: ua,
185
+ resolution: res,
186
+ locale,
187
+ device: model,
188
+ androidVersion: av,
189
+ buildID: build,
190
+ persona: "android",
191
+ };
192
+ }
193
+
194
+ function generateIdentityByMode(mode, opts = {}) {
195
+ if (mode === "android" || mode === "mobile") return randomAndroidIdentity(opts);
196
+ return randomDesktopIdentity();
197
+ }
198
+
199
+ function cacheIdentityData(cfg, id) {
200
+ if (!cfg || !id) return;
201
+ if (id.androidVersion) {
202
+ cfg.cachedAndroidUA = id.userAgent;
203
+ cfg.cachedAndroidDevice = id.device;
204
+ cfg.cachedAndroidVersion = id.androidVersion;
205
+ cfg.cachedAndroidResolution = id.resolution;
206
+ cfg.cachedAndroidLocale = id.locale;
207
+ cfg.cachedAndroidBuildId = id.buildID;
208
+ } else if (id.userAgent) {
209
+ cfg.cachedUserAgent = id.userAgent;
210
+ cfg.cachedSecChUa = id.secChUa;
211
+ cfg.cachedSecChUaFullVersionList = id.secChUaFullVersionList;
212
+ cfg.cachedSecChUaPlatform = id.secChUaPlatform;
213
+ cfg.cachedSecChUaPlatformVersion = id.secChUaPlatformVersion;
214
+ cfg.cachedBrowser = id.browser;
215
+ }
216
+ }
217
+
218
+ function isConsistentIdentity(cfg) {
219
+ const hasDesktop = !!(cfg.cachedUserAgent && cfg.cachedSecChUa);
220
+ const hasAndroid = !!(cfg.cachedAndroidUA && cfg.cachedAndroidDevice);
221
+ return hasDesktop || hasAndroid;
222
+ }
223
+
224
+ module.exports = {
225
+ randomDesktopIdentity,
226
+ randomAndroidIdentity,
227
+ generateIdentityByMode,
228
+ cacheIdentityData,
229
+ isConsistentIdentity,
230
+ randomUserAgent: randomDesktopIdentity,
231
+ generateUserAgentByPersona: (mode, opts) => generateIdentityByMode(mode, opts),
232
+ ANDROID_MODELS,
233
+ ANDROID_VERSIONS,
234
+ BROWSER_POOL,
235
+ };