@lazyneoaz/testfca 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/CHANGELOG.md +229 -0
- package/COOKIE_LOGIN.md +208 -0
- package/LICENSE +3 -0
- package/README.md +492 -0
- package/index.js +2 -0
- package/package.json +120 -0
- package/scripts/build-go.mjs +54 -0
- package/scripts/detect-platform.mjs +36 -0
- package/scripts/download-prebuilt.mjs +119 -0
- package/scripts/package.mjs +6 -0
- package/scripts/postinstall.mjs +113 -0
- package/src/apis/addExternalModule.js +24 -0
- package/src/apis/addUserToGroup.js +108 -0
- package/src/apis/changeAdminStatus.js +148 -0
- package/src/apis/changeArchivedStatus.js +61 -0
- package/src/apis/changeAvatar.js +103 -0
- package/src/apis/changeBio.js +69 -0
- package/src/apis/changeBlockedStatus.js +54 -0
- package/src/apis/changeGroupImage.js +136 -0
- package/src/apis/changeThreadColor.js +116 -0
- package/src/apis/changeThreadEmoji.js +53 -0
- package/src/apis/comment.js +207 -0
- package/src/apis/createAITheme.js +129 -0
- package/src/apis/createNewGroup.js +79 -0
- package/src/apis/createPoll.js +73 -0
- package/src/apis/deleteMessage.js +52 -0
- package/src/apis/deleteThread.js +52 -0
- package/src/apis/e2ee.js +170 -0
- package/src/apis/editMessage.js +78 -0
- package/src/apis/emoji.js +124 -0
- package/src/apis/fetchThemeData.js +82 -0
- package/src/apis/follow.js +81 -0
- package/src/apis/forwardMessage.js +52 -0
- package/src/apis/friend.js +243 -0
- package/src/apis/gcmember.js +122 -0
- package/src/apis/gcname.js +123 -0
- package/src/apis/gcrule.js +119 -0
- package/src/apis/getAccess.js +111 -0
- package/src/apis/getBotInfo.js +88 -0
- package/src/apis/getBotInitialData.js +43 -0
- package/src/apis/getFriendsList.js +79 -0
- package/src/apis/getMessage.js +423 -0
- package/src/apis/getTheme.js +95 -0
- package/src/apis/getThemeInfo.js +116 -0
- package/src/apis/getThreadHistory.js +239 -0
- package/src/apis/getThreadInfo.js +267 -0
- package/src/apis/getThreadList.js +232 -0
- package/src/apis/getThreadPictures.js +58 -0
- package/src/apis/getUserID.js +117 -0
- package/src/apis/getUserInfo.js +513 -0
- package/src/apis/getUserInfoV2.js +146 -0
- package/src/apis/handleMessageRequest.js +50 -0
- package/src/apis/httpGet.js +63 -0
- package/src/apis/httpPost.js +89 -0
- package/src/apis/httpPostFormData.js +69 -0
- package/src/apis/listenMqtt.js +1236 -0
- package/src/apis/listenSpeed.js +179 -0
- package/src/apis/logout.js +93 -0
- package/src/apis/markAsDelivered.js +47 -0
- package/src/apis/markAsRead.js +115 -0
- package/src/apis/markAsReadAll.js +40 -0
- package/src/apis/markAsSeen.js +70 -0
- package/src/apis/mqttDeltaValue.js +250 -0
- package/src/apis/muteThread.js +45 -0
- package/src/apis/nickname.js +132 -0
- package/src/apis/notes.js +163 -0
- package/src/apis/pinMessage.js +150 -0
- package/src/apis/produceMetaTheme.js +180 -0
- package/src/apis/realtime.js +182 -0
- package/src/apis/removeUserFromGroup.js +117 -0
- package/src/apis/resolvePhotoUrl.js +58 -0
- package/src/apis/searchForThread.js +154 -0
- package/src/apis/sendMessage.js +346 -0
- package/src/apis/sendMessageMqtt.js +248 -0
- package/src/apis/sendTypingIndicator.js +105 -0
- package/src/apis/setMessageReaction.js +38 -0
- package/src/apis/setMessageReactionMqtt.js +61 -0
- package/src/apis/setThreadTheme.js +260 -0
- package/src/apis/setThreadThemeMqtt.js +94 -0
- package/src/apis/share.js +107 -0
- package/src/apis/shareContact.js +66 -0
- package/src/apis/stickers.js +257 -0
- package/src/apis/story.js +181 -0
- package/src/apis/theme.js +233 -0
- package/src/apis/unfriend.js +47 -0
- package/src/apis/unsendMessage.js +25 -0
- package/src/database/appStateBackup.js +298 -0
- package/src/database/models/index.js +56 -0
- package/src/database/models/thread.js +31 -0
- package/src/database/models/user.js +32 -0
- package/src/database/threadData.js +101 -0
- package/src/database/userData.js +90 -0
- package/src/e2ee/bridge.js +275 -0
- package/src/e2ee/index.js +60 -0
- package/src/engine/client.js +95 -0
- package/src/engine/models/buildAPI.js +152 -0
- package/src/engine/models/loginHelper.js +574 -0
- package/src/engine/models/setOptions.js +88 -0
- package/src/types/index.d.ts +574 -0
- package/src/utils/antiSuspension.js +529 -0
- package/src/utils/auth-helpers.js +149 -0
- package/src/utils/autoReLogin.js +336 -0
- package/src/utils/axios.js +436 -0
- package/src/utils/cache.js +54 -0
- package/src/utils/clients.js +282 -0
- package/src/utils/constants.js +410 -0
- package/src/utils/formatters/data/formatAttachment.js +370 -0
- package/src/utils/formatters/data/formatDelta.js +109 -0
- package/src/utils/formatters/index.js +159 -0
- package/src/utils/formatters/value/formatCookie.js +91 -0
- package/src/utils/formatters/value/formatDate.js +36 -0
- package/src/utils/formatters/value/formatID.js +16 -0
- package/src/utils/formatters.js +1373 -0
- package/src/utils/headers.js +235 -0
- package/src/utils/index.js +153 -0
- package/src/utils/monitoring.js +333 -0
- package/src/utils/rateLimiter.js +319 -0
- package/src/utils/tokenRefresh.js +680 -0
- package/src/utils/user-agents.js +238 -0
- package/src/utils/validation.js +157 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/* eslint-disable no-prototype-builtins */
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const axios = require("axios");
|
|
5
|
+
const { CookieJar } = require("tough-cookie");
|
|
6
|
+
const { wrapper } = require("axios-cookiejar-support");
|
|
7
|
+
const FormData = require("form-data");
|
|
8
|
+
const { getHeaders } = require("./headers");
|
|
9
|
+
const { getType } = require("./constants");
|
|
10
|
+
const { globalRateLimiter } = require("./rateLimiter");
|
|
11
|
+
|
|
12
|
+
const jar = new CookieJar();
|
|
13
|
+
const client = wrapper(axios.create({ jar }));
|
|
14
|
+
|
|
15
|
+
let proxyConfig = {};
|
|
16
|
+
|
|
17
|
+
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
18
|
+
|
|
19
|
+
// Fast path for simple delays - use setImmediate for 0ms delays
|
|
20
|
+
const fastDelay = (ms) => {
|
|
21
|
+
if (ms <= 0) return Promise.resolve();
|
|
22
|
+
if (ms <= 1) return new Promise(resolve => setImmediate(resolve));
|
|
23
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function adaptResponse(res) {
|
|
27
|
+
const response = res.response || res;
|
|
28
|
+
return {
|
|
29
|
+
...response,
|
|
30
|
+
body: response.data,
|
|
31
|
+
statusCode: response.status,
|
|
32
|
+
request: {
|
|
33
|
+
uri: new URL(response.config.url),
|
|
34
|
+
headers: response.config.headers,
|
|
35
|
+
method: response.config.method.toUpperCase(),
|
|
36
|
+
form: response.config.data,
|
|
37
|
+
formData: response.config.data
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Inspects an API response body for signs of session expiry or Facebook
|
|
44
|
+
* bot-detection checkpoints and emits the appropriate signals on ctx.
|
|
45
|
+
*
|
|
46
|
+
* Returns true if the response looks like a valid authenticated response,
|
|
47
|
+
* false if it signals logout / checkpoint.
|
|
48
|
+
*
|
|
49
|
+
* When a logout is detected and ctx.performAutoLogin is available the
|
|
50
|
+
* function fires it (non-blocking) and throws so the caller knows the
|
|
51
|
+
* original response is unusable.
|
|
52
|
+
*/
|
|
53
|
+
async function inspectResponseForSessionIssues(adapted, ctx) {
|
|
54
|
+
if (!ctx || ctx._skipSessionInspect) return;
|
|
55
|
+
|
|
56
|
+
const body = adapted.body;
|
|
57
|
+
if (!body) return;
|
|
58
|
+
|
|
59
|
+
const bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
|
|
60
|
+
|
|
61
|
+
// Facebook bot-detection checkpoint IDs
|
|
62
|
+
const isCheckpoint282 = bodyStr.includes('1501092823525282');
|
|
63
|
+
const isCheckpoint956 = bodyStr.includes('828281030927956');
|
|
64
|
+
const isScrapingWarning = bodyStr.includes('XCheckpointFBScrapingWarningController');
|
|
65
|
+
|
|
66
|
+
if (isCheckpoint282) {
|
|
67
|
+
const err = new Error('Bot checkpoint 282 detected. Please verify the account.');
|
|
68
|
+
err.error = 'checkpoint_282';
|
|
69
|
+
err.res = body;
|
|
70
|
+
if (ctx._emitter && typeof ctx._emitter.emit === 'function') {
|
|
71
|
+
ctx._emitter.emit('checkpoint_282', { res: body });
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (isCheckpoint956) {
|
|
77
|
+
const err = new Error('Bot checkpoint 956 detected. Please verify the account.');
|
|
78
|
+
err.error = 'checkpoint_956';
|
|
79
|
+
err.res = body;
|
|
80
|
+
if (ctx._emitter && typeof ctx._emitter.emit === 'function') {
|
|
81
|
+
ctx._emitter.emit('checkpoint_956', { res: body });
|
|
82
|
+
}
|
|
83
|
+
throw err;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (isScrapingWarning) {
|
|
87
|
+
const err = new Error('Facebook scraping warning checkpoint detected.');
|
|
88
|
+
err.error = 'checkpoint_scraping';
|
|
89
|
+
err.res = body;
|
|
90
|
+
if (ctx._emitter && typeof ctx._emitter.emit === 'function') {
|
|
91
|
+
ctx._emitter.emit('checkpoint', { type: 'scraping_warning', res: body });
|
|
92
|
+
}
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Detect session expiry / forced logout.
|
|
97
|
+
// IMPORTANT: Facebook's authenticated homepage also contains login.php links
|
|
98
|
+
// in its nav/footer and has <title>Facebook</title> — any broad HTML-content
|
|
99
|
+
// check will produce false positives and break valid sessions.
|
|
100
|
+
// We ONLY flag a response as a login redirect when:
|
|
101
|
+
// • The page contains the actual login form elements, OR
|
|
102
|
+
// • A parsed JSON body explicitly redirects to login.php via a "redirect" field.
|
|
103
|
+
const isLoginRedirect =
|
|
104
|
+
bodyStr.includes('<form id="login_form"') ||
|
|
105
|
+
bodyStr.includes('id="loginbutton"') ||
|
|
106
|
+
bodyStr.includes('"login_page"') ||
|
|
107
|
+
// JSON responses that carry an explicit redirect to the login page.
|
|
108
|
+
// "next" appears alongside login.php only in unauthenticated redirect payloads.
|
|
109
|
+
(bodyStr.includes('login.php') && bodyStr.includes('"next":"'));
|
|
110
|
+
|
|
111
|
+
const isLoginBlocked =
|
|
112
|
+
typeof body === 'object' && body !== null && body.error === 1357001;
|
|
113
|
+
|
|
114
|
+
if (isLoginBlocked) {
|
|
115
|
+
const err = new Error('Facebook blocked the login.');
|
|
116
|
+
err.error = 'login_blocked';
|
|
117
|
+
err.res = body;
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (isLoginRedirect) {
|
|
122
|
+
if (ctx._emitter && typeof ctx._emitter.emit === 'function') {
|
|
123
|
+
ctx._emitter.emit('sessionExpired', { res: body });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!ctx.auto_login && typeof ctx.performAutoLogin === 'function') {
|
|
127
|
+
ctx.auto_login = true;
|
|
128
|
+
// Safety: reset the flag after 2 minutes no matter what so future
|
|
129
|
+
// session expiries are never silently swallowed.
|
|
130
|
+
const autoLoginSafetyTimer = setTimeout(() => { ctx.auto_login = false; }, 120000);
|
|
131
|
+
try {
|
|
132
|
+
const ok = await ctx.performAutoLogin();
|
|
133
|
+
clearTimeout(autoLoginSafetyTimer);
|
|
134
|
+
ctx.auto_login = false;
|
|
135
|
+
if (!ok) {
|
|
136
|
+
const err = new Error('Not logged in. Auto re-login failed.');
|
|
137
|
+
err.error = 'Not logged in.';
|
|
138
|
+
err.res = body;
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
} catch (autoErr) {
|
|
142
|
+
clearTimeout(autoLoginSafetyTimer);
|
|
143
|
+
ctx.auto_login = false;
|
|
144
|
+
throw autoErr;
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
const err = new Error('Not logged in. Session has expired.');
|
|
148
|
+
err.error = 'Not logged in.';
|
|
149
|
+
err.res = body;
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function requestWithRetry(requestFunction, retries = 5, endpoint = '', threadID = '', ctx = null) {
|
|
156
|
+
// Fast path for simple requests
|
|
157
|
+
const isSimpleRequest = !endpoint && !threadID;
|
|
158
|
+
|
|
159
|
+
// Acquire rate limit slot with optimized path
|
|
160
|
+
let releaseSlot = null;
|
|
161
|
+
if (!isSimpleRequest) {
|
|
162
|
+
releaseSlot = await globalRateLimiter.checkRateLimit(false, endpoint);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check cooldowns efficiently
|
|
166
|
+
if (!isSimpleRequest) {
|
|
167
|
+
if (globalRateLimiter.isEndpointOnCooldown("__GLOBAL__")) {
|
|
168
|
+
const cooldown = globalRateLimiter.getEndpointCooldownRemaining("__GLOBAL__");
|
|
169
|
+
if (cooldown > 0) {
|
|
170
|
+
console.warn(`Global cooldown active. Waiting ${cooldown}ms...`);
|
|
171
|
+
await delay(cooldown);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (endpoint && globalRateLimiter.isEndpointOnCooldown(endpoint)) {
|
|
176
|
+
const cooldown = globalRateLimiter.getEndpointCooldownRemaining(endpoint);
|
|
177
|
+
if (cooldown > 0) {
|
|
178
|
+
console.warn(`Endpoint ${endpoint} on cooldown. Waiting ${cooldown}ms...`);
|
|
179
|
+
await delay(cooldown);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (threadID && globalRateLimiter.isThreadOnCooldown(threadID)) {
|
|
184
|
+
const cooldown = globalRateLimiter.getCooldownRemaining(threadID);
|
|
185
|
+
if (cooldown > 0) {
|
|
186
|
+
console.warn(`Thread ${threadID} on cooldown. Waiting ${cooldown}ms...`);
|
|
187
|
+
await delay(cooldown);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const checkAndApplyRateLimitCooldowns = (responseBody) => {
|
|
193
|
+
const ERROR_COOLDOWNS = {
|
|
194
|
+
1545012: 60000,
|
|
195
|
+
1675004: 30000,
|
|
196
|
+
368: 120000,
|
|
197
|
+
404: 5000,
|
|
198
|
+
500: 10000,
|
|
199
|
+
503: 30000
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const applyCooldown = (errorCode) => {
|
|
203
|
+
if (errorCode && ERROR_COOLDOWNS[errorCode]) {
|
|
204
|
+
if (threadID) {
|
|
205
|
+
globalRateLimiter.setThreadCooldown(threadID, ERROR_COOLDOWNS[errorCode]);
|
|
206
|
+
}
|
|
207
|
+
if (endpoint) {
|
|
208
|
+
globalRateLimiter.setEndpointCooldown(endpoint, ERROR_COOLDOWNS[errorCode]);
|
|
209
|
+
}
|
|
210
|
+
console.warn(`Rate limit detected (error ${errorCode}). Applied cooldown.`);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
if (!responseBody || typeof responseBody !== 'object') {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (applyCooldown(responseBody.error)) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (Array.isArray(responseBody)) {
|
|
225
|
+
for (const item of responseBody) {
|
|
226
|
+
if (item && typeof item === 'object') {
|
|
227
|
+
if (applyCooldown(item.error)) return true;
|
|
228
|
+
if (item.errors && Array.isArray(item.errors)) {
|
|
229
|
+
for (const err of item.errors) {
|
|
230
|
+
const code = err.code || err.extensions?.code;
|
|
231
|
+
if (applyCooldown(code)) return true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (responseBody.errors && Array.isArray(responseBody.errors)) {
|
|
239
|
+
for (const err of responseBody.errors) {
|
|
240
|
+
const code = err.code || err.extensions?.code;
|
|
241
|
+
if (applyCooldown(code)) return true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return false;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
for (let i = 0; i < retries; i++) {
|
|
250
|
+
try {
|
|
251
|
+
const res = await requestFunction();
|
|
252
|
+
const adapted = adaptResponse(res);
|
|
253
|
+
|
|
254
|
+
checkAndApplyRateLimitCooldowns(adapted.body);
|
|
255
|
+
|
|
256
|
+
// Inspect for session expiry / bot-detection checkpoints
|
|
257
|
+
await inspectResponseForSessionIssues(adapted, ctx);
|
|
258
|
+
|
|
259
|
+
return adapted;
|
|
260
|
+
} catch (error) {
|
|
261
|
+
// If this is a session/checkpoint error we already raised, propagate immediately
|
|
262
|
+
if (error.error === 'Not logged in.' ||
|
|
263
|
+
error.error === 'checkpoint_282' ||
|
|
264
|
+
error.error === 'checkpoint_956' ||
|
|
265
|
+
error.error === 'checkpoint_scraping' ||
|
|
266
|
+
error.error === 'login_blocked') {
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Abort immediately on invalid header characters - retrying won't help
|
|
271
|
+
if (error.code === 'ERR_INVALID_CHAR' ||
|
|
272
|
+
(error.message && error.message.includes('Invalid character in header'))) {
|
|
273
|
+
const e = new Error('Invalid header content detected. Request aborted.');
|
|
274
|
+
e.error = 'invalid_header';
|
|
275
|
+
e.code = 'ERR_INVALID_CHAR';
|
|
276
|
+
e.originalError = error;
|
|
277
|
+
throw e;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Network errors - might be transient
|
|
281
|
+
const isNetworkError = error.code && [
|
|
282
|
+
'ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED', 'ENETUNREACH',
|
|
283
|
+
'EHOSTUNREACH', 'EAI_AGAIN', 'ENOTFOUND', 'ESOCKETTIMEDOUT'
|
|
284
|
+
].includes(error.code);
|
|
285
|
+
|
|
286
|
+
if (error.response) {
|
|
287
|
+
const adapted = adaptResponse(error.response);
|
|
288
|
+
checkAndApplyRateLimitCooldowns(adapted.body);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (i === retries - 1) {
|
|
292
|
+
console.error(`Request failed after ${retries} attempts:`, error.message);
|
|
293
|
+
if (error.response) {
|
|
294
|
+
return adaptResponse(error.response);
|
|
295
|
+
}
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Adaptive backoff: network errors get shorter delays
|
|
300
|
+
const baseMultiplier = isNetworkError ? 0.5 : 1;
|
|
301
|
+
const backoffTime = Math.min(
|
|
302
|
+
(Math.pow(2, i) * 1000 * baseMultiplier) + Math.floor(Math.random() * 1000),
|
|
303
|
+
30000 // Cap at 30 seconds
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
if (backoffTime > 100) {
|
|
307
|
+
console.warn(`Request attempt ${i + 1} failed. Retrying in ${Math.round(backoffTime)}ms...`);
|
|
308
|
+
await delay(backoffTime);
|
|
309
|
+
} else {
|
|
310
|
+
// Fast path for very short delays
|
|
311
|
+
await fastDelay(backoffTime);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
} finally {
|
|
316
|
+
// Always release the concurrency slot regardless of outcome.
|
|
317
|
+
if (typeof releaseSlot === 'function') releaseSlot();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function setProxy(proxyUrl) {
|
|
322
|
+
if (proxyUrl) {
|
|
323
|
+
try {
|
|
324
|
+
const parsedProxy = new URL(proxyUrl);
|
|
325
|
+
proxyConfig = {
|
|
326
|
+
proxy: {
|
|
327
|
+
host: parsedProxy.hostname,
|
|
328
|
+
port: parsedProxy.port,
|
|
329
|
+
protocol: parsedProxy.protocol.replace(":", ""),
|
|
330
|
+
auth: parsedProxy.username && parsedProxy.password ? {
|
|
331
|
+
username: parsedProxy.username,
|
|
332
|
+
password: parsedProxy.password,
|
|
333
|
+
} : undefined,
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
} catch (e) {
|
|
337
|
+
console.error("Invalid proxy URL. Please use a full URL format (e.g., http://user:pass@host:port).");
|
|
338
|
+
proxyConfig = {};
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
proxyConfig = {};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function cleanGet(url) {
|
|
346
|
+
const fn = () => client.get(url, { timeout: 60000, ...proxyConfig });
|
|
347
|
+
return requestWithRetry(fn);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function get(url, reqJar, qs, options, ctx, customHeader) {
|
|
351
|
+
const config = {
|
|
352
|
+
headers: getHeaders(url, options, ctx, customHeader),
|
|
353
|
+
timeout: 60000,
|
|
354
|
+
params: qs,
|
|
355
|
+
...proxyConfig,
|
|
356
|
+
validateStatus: (status) => status >= 200 && status < 600,
|
|
357
|
+
// Enable response compression for faster transfers
|
|
358
|
+
decompress: true,
|
|
359
|
+
// Optimize for performance
|
|
360
|
+
maxContentLength: 50 * 1024 * 1024, // 50MB max
|
|
361
|
+
maxBodyLength: 50 * 1024 * 1024
|
|
362
|
+
};
|
|
363
|
+
const endpoint = new URL(url).pathname;
|
|
364
|
+
const threadHint = ctx && ctx.requestThreadID ? String(ctx.requestThreadID) : '';
|
|
365
|
+
return requestWithRetry(async () => await client.get(url, config), 3, endpoint, threadHint, ctx);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function post(url, reqJar, form, options, ctx, customHeader) {
|
|
369
|
+
const headers = getHeaders(url, options, ctx, customHeader, 'xhr');
|
|
370
|
+
let data = form;
|
|
371
|
+
let contentType = headers['Content-Type'] || 'application/x-www-form-urlencoded';
|
|
372
|
+
|
|
373
|
+
if (contentType.includes('json')) {
|
|
374
|
+
data = JSON.stringify(form);
|
|
375
|
+
} else {
|
|
376
|
+
// Use URLSearchParams for better performance on large forms
|
|
377
|
+
const transformedForm = new URLSearchParams();
|
|
378
|
+
for (const key in form) {
|
|
379
|
+
if (form.hasOwnProperty(key)) {
|
|
380
|
+
let value = form[key];
|
|
381
|
+
if (getType(value) === "Object") {
|
|
382
|
+
value = JSON.stringify(value);
|
|
383
|
+
}
|
|
384
|
+
transformedForm.append(key, value);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
data = transformedForm.toString();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
headers['Content-Type'] = contentType;
|
|
391
|
+
|
|
392
|
+
const config = {
|
|
393
|
+
headers,
|
|
394
|
+
timeout: 60000,
|
|
395
|
+
...proxyConfig,
|
|
396
|
+
validateStatus: (status) => status >= 200 && status < 600,
|
|
397
|
+
decompress: true,
|
|
398
|
+
maxContentLength: 50 * 1024 * 1024,
|
|
399
|
+
maxBodyLength: 50 * 1024 * 1024
|
|
400
|
+
};
|
|
401
|
+
const endpoint = new URL(url).pathname;
|
|
402
|
+
const threadHint = ctx && ctx.requestThreadID ? String(ctx.requestThreadID) : '';
|
|
403
|
+
return requestWithRetry(async () => await client.post(url, data, config), 3, endpoint, threadHint, ctx);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async function postFormData(url, reqJar, form, qs, options, ctx) {
|
|
407
|
+
const formData = new FormData();
|
|
408
|
+
for (const key in form) {
|
|
409
|
+
if (form.hasOwnProperty(key)) {
|
|
410
|
+
formData.append(key, form[key]);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const customHeader = { "Content-Type": `multipart/form-data; boundary=${formData.getBoundary()}` };
|
|
415
|
+
|
|
416
|
+
const config = {
|
|
417
|
+
headers: getHeaders(url, options, ctx, customHeader, 'xhr'),
|
|
418
|
+
timeout: 60000,
|
|
419
|
+
params: qs,
|
|
420
|
+
...proxyConfig,
|
|
421
|
+
validateStatus: (status) => status >= 200 && status < 600,
|
|
422
|
+
};
|
|
423
|
+
const endpoint = new URL(url).pathname;
|
|
424
|
+
const threadHint = ctx && ctx.requestThreadID ? String(ctx.requestThreadID) : '';
|
|
425
|
+
return requestWithRetry(async () => await client.post(url, formData, config), 3, endpoint, threadHint, ctx);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
module.exports = {
|
|
429
|
+
cleanGet,
|
|
430
|
+
get,
|
|
431
|
+
post,
|
|
432
|
+
postFormData,
|
|
433
|
+
getJar: () => jar,
|
|
434
|
+
setProxy,
|
|
435
|
+
requestWithRetry
|
|
436
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
class SimpleCache {
|
|
4
|
+
constructor(defaultTTL = 300000) { // 5 minutes default
|
|
5
|
+
this.cache = new Map();
|
|
6
|
+
this.defaultTTL = defaultTTL;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
set(key, value, ttl = this.defaultTTL) {
|
|
10
|
+
const expiresAt = Date.now() + ttl;
|
|
11
|
+
this.cache.set(key, { value, expiresAt });
|
|
12
|
+
|
|
13
|
+
// Clean up expired entries occasionally
|
|
14
|
+
if (Math.random() < 0.01) { // 1% chance
|
|
15
|
+
this.cleanup();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get(key) {
|
|
20
|
+
const item = this.cache.get(key);
|
|
21
|
+
if (!item) return null;
|
|
22
|
+
|
|
23
|
+
if (Date.now() > item.expiresAt) {
|
|
24
|
+
this.cache.delete(key);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return item.value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
delete(key) {
|
|
32
|
+
return this.cache.delete(key);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
clear() {
|
|
36
|
+
this.cache.clear();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
cleanup() {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
for (const [key, item] of this.cache.entries()) {
|
|
42
|
+
if (now > item.expiresAt) {
|
|
43
|
+
this.cache.delete(key);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
size() {
|
|
49
|
+
this.cleanup();
|
|
50
|
+
return this.cache.size;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = SimpleCache;
|