@lazyneoaz/nkxchat 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 (123) hide show
  1. package/LICENSE +3 -0
  2. package/README.md +199 -0
  3. package/examples/login-with-cookies.js +102 -0
  4. package/examples/verify.js +70 -0
  5. package/index.js +2 -0
  6. package/package.json +84 -0
  7. package/src/apis/addExternalModule.js +24 -0
  8. package/src/apis/addUserToGroup.js +108 -0
  9. package/src/apis/changeAdminStatus.js +148 -0
  10. package/src/apis/changeArchivedStatus.js +61 -0
  11. package/src/apis/changeAvatar.js +103 -0
  12. package/src/apis/changeBio.js +69 -0
  13. package/src/apis/changeBlockedStatus.js +54 -0
  14. package/src/apis/changeGroupImage.js +136 -0
  15. package/src/apis/changeThreadColor.js +116 -0
  16. package/src/apis/changeThreadEmoji.js +53 -0
  17. package/src/apis/comment.js +207 -0
  18. package/src/apis/createAITheme.js +129 -0
  19. package/src/apis/createNewGroup.js +79 -0
  20. package/src/apis/createPoll.js +73 -0
  21. package/src/apis/deleteMessage.js +44 -0
  22. package/src/apis/deleteThread.js +52 -0
  23. package/src/apis/editMessage.js +70 -0
  24. package/src/apis/emoji.js +124 -0
  25. package/src/apis/enableAutoSaveAppState.js +69 -0
  26. package/src/apis/fetchThemeData.js +113 -0
  27. package/src/apis/follow.js +81 -0
  28. package/src/apis/forwardAttachment.js +178 -0
  29. package/src/apis/forwardMessage.js +52 -0
  30. package/src/apis/friend.js +243 -0
  31. package/src/apis/gcmember.js +122 -0
  32. package/src/apis/gcname.js +123 -0
  33. package/src/apis/gcrule.js +119 -0
  34. package/src/apis/getAccess.js +111 -0
  35. package/src/apis/getBotInfo.js +88 -0
  36. package/src/apis/getBotInitialData.js +43 -0
  37. package/src/apis/getEmojiUrl.js +40 -0
  38. package/src/apis/getFriendsList.js +79 -0
  39. package/src/apis/getMessage.js +423 -0
  40. package/src/apis/getTheme.js +123 -0
  41. package/src/apis/getThemeInfo.js +116 -0
  42. package/src/apis/getThemePictures.js +87 -0
  43. package/src/apis/getThreadColors.js +119 -0
  44. package/src/apis/getThreadHistory.js +239 -0
  45. package/src/apis/getThreadInfo.js +267 -0
  46. package/src/apis/getThreadList.js +232 -0
  47. package/src/apis/getThreadPictures.js +58 -0
  48. package/src/apis/getUserID.js +117 -0
  49. package/src/apis/getUserInfo.js +513 -0
  50. package/src/apis/getUserInfoV2.js +146 -0
  51. package/src/apis/handleFriendRequest.js +66 -0
  52. package/src/apis/handleMessageRequest.js +50 -0
  53. package/src/apis/httpGet.js +63 -0
  54. package/src/apis/httpPost.js +89 -0
  55. package/src/apis/httpPostFormData.js +69 -0
  56. package/src/apis/listenMqtt.js +924 -0
  57. package/src/apis/listenSpeed.js +178 -0
  58. package/src/apis/logout.js +63 -0
  59. package/src/apis/markAsDelivered.js +47 -0
  60. package/src/apis/markAsRead.js +95 -0
  61. package/src/apis/markAsReadAll.js +40 -0
  62. package/src/apis/markAsSeen.js +70 -0
  63. package/src/apis/mqttDeltaValue.js +252 -0
  64. package/src/apis/muteThread.js +45 -0
  65. package/src/apis/nickname.js +132 -0
  66. package/src/apis/notes.js +163 -0
  67. package/src/apis/pinMessage.js +150 -0
  68. package/src/apis/produceMetaTheme.js +160 -0
  69. package/src/apis/realtime.js +182 -0
  70. package/src/apis/refreshFb_dtsg.js +94 -0
  71. package/src/apis/removeUserFromGroup.js +117 -0
  72. package/src/apis/resolvePhotoUrl.js +58 -0
  73. package/src/apis/searchForThread.js +154 -0
  74. package/src/apis/sendEffect.js +306 -0
  75. package/src/apis/sendMessage.js +353 -0
  76. package/src/apis/sendMessageMqtt.js +255 -0
  77. package/src/apis/sendTypingIndicator.js +40 -0
  78. package/src/apis/setMessageReaction.js +27 -0
  79. package/src/apis/setMessageReactionMqtt.js +61 -0
  80. package/src/apis/setPostReaction.js +118 -0
  81. package/src/apis/setThreadTheme.js +210 -0
  82. package/src/apis/setThreadThemeMqtt.js +94 -0
  83. package/src/apis/setTitle.js +26 -0
  84. package/src/apis/share.js +106 -0
  85. package/src/apis/shareContact.js +66 -0
  86. package/src/apis/stickers.js +257 -0
  87. package/src/apis/story.js +181 -0
  88. package/src/apis/theme.js +233 -0
  89. package/src/apis/unfriend.js +47 -0
  90. package/src/apis/unsendMessage.js +17 -0
  91. package/src/apis/uploadAttachment.js +87 -0
  92. package/src/database/appStateBackup.js +189 -0
  93. package/src/database/models/index.js +56 -0
  94. package/src/database/models/thread.js +31 -0
  95. package/src/database/models/user.js +32 -0
  96. package/src/database/threadData.js +101 -0
  97. package/src/database/userData.js +90 -0
  98. package/src/engine/client.js +92 -0
  99. package/src/engine/models/buildAPI.js +118 -0
  100. package/src/engine/models/loginHelper.js +492 -0
  101. package/src/engine/models/setOptions.js +88 -0
  102. package/src/types/index.d.ts +498 -0
  103. package/src/utils/antiSuspension.js +516 -0
  104. package/src/utils/auth-helpers.js +149 -0
  105. package/src/utils/autoReLogin.js +237 -0
  106. package/src/utils/axios.js +368 -0
  107. package/src/utils/cache.js +54 -0
  108. package/src/utils/clients.js +279 -0
  109. package/src/utils/constants.js +525 -0
  110. package/src/utils/formatters/data/formatAttachment.js +370 -0
  111. package/src/utils/formatters/data/formatDelta.js +109 -0
  112. package/src/utils/formatters/index.js +159 -0
  113. package/src/utils/formatters/value/formatCookie.js +91 -0
  114. package/src/utils/formatters/value/formatDate.js +36 -0
  115. package/src/utils/formatters/value/formatID.js +16 -0
  116. package/src/utils/formatters.js +1369 -0
  117. package/src/utils/headers.js +235 -0
  118. package/src/utils/index.js +152 -0
  119. package/src/utils/monitoring.js +333 -0
  120. package/src/utils/rateLimiter.js +251 -0
  121. package/src/utils/tokenRefresh.js +285 -0
  122. package/src/utils/user-agents.js +238 -0
  123. package/src/utils/validation.js +157 -0
@@ -0,0 +1,237 @@
1
+ "use strict";
2
+
3
+ const utils = require('./index');
4
+
5
+ class AutoReLoginManager {
6
+ constructor() {
7
+ this.credentials = null;
8
+ this.loginOptions = null;
9
+ this.loginCallback = null;
10
+ this.isReLoggingIn = false;
11
+ this.pendingRequests = [];
12
+ this.maxRetries = 3; // Reduced: too many re-logins look highly suspicious to Facebook
13
+ this.retryCount = 0;
14
+ this.onReLoginSuccess = null;
15
+ this.onReLoginFailure = null;
16
+ this.enabled = false;
17
+ this.reLoginInterval = 1000 * 60 * 60 * 24; // 24 hours
18
+ this.sessionMonitorInterval = null;
19
+ // Removed separate 15-minute session polling - tokenRefresh already handles this.
20
+ // Duplicate polling from two systems every 15-30 min looked like bot traffic to Facebook.
21
+ this.sessionCheckInterval = 1000 * 60 * 120; // 2 hours
22
+ }
23
+
24
+ setCredentials(credentials, options, callback) {
25
+ this.credentials = credentials;
26
+ this.loginOptions = options || {};
27
+ this.loginCallback = callback;
28
+ this.enabled = true;
29
+ // Reset retry counter on fresh credential set so old failures
30
+ // from a previous session do not permanently lock re-login.
31
+ this.retryCount = 0;
32
+ // Do NOT call startSessionMonitoring() here — the api object is not
33
+ // available yet. loginHelper calls startSessionMonitoring(api) once
34
+ // all api methods are registered.
35
+ }
36
+
37
+ startSessionMonitoring(api) {
38
+ if (this.sessionMonitorInterval) {
39
+ clearInterval(this.sessionMonitorInterval);
40
+ }
41
+
42
+ if (!this.enabled || !api) return;
43
+
44
+ this.sessionMonitorInterval = setInterval(async () => {
45
+ if (this.isReLoggingIn) return; // Skip if already re-logging in
46
+
47
+ try {
48
+ const isValid = await api.isSessionValid();
49
+ if (!isValid) {
50
+ utils.warn("AutoReLogin", "Session health check failed, triggering automatic re-login");
51
+ await this.handleSessionExpiry(api, 'https://www.facebook.com', "Session expired during monitoring");
52
+ }
53
+ } catch (error) {
54
+ utils.error("AutoReLogin", "Session monitoring error:", error.message);
55
+ }
56
+ }, this.sessionCheckInterval);
57
+
58
+ utils.log("AutoReLogin", `Session monitoring started (interval: ${this.sessionCheckInterval}ms)`);
59
+ }
60
+
61
+ stopSessionMonitoring() {
62
+ if (this.sessionMonitorInterval) {
63
+ clearInterval(this.sessionMonitorInterval);
64
+ this.sessionMonitorInterval = null;
65
+ utils.log("AutoReLogin", "Session monitoring stopped");
66
+ }
67
+ }
68
+
69
+ isEnabled() {
70
+ return this.enabled && this.credentials !== null;
71
+ }
72
+
73
+ async handleSessionExpiry(api, fbLink, ERROR_RETRIEVING) {
74
+ if (!this.isEnabled()) {
75
+ utils.warn("AutoReLogin", "Auto re-login not enabled. Credentials not stored.");
76
+ return false;
77
+ }
78
+
79
+ if (this.isReLoggingIn) {
80
+ utils.log("AutoReLogin", "Re-login already in progress. Queuing request...");
81
+ return new Promise((resolve, reject) => {
82
+ this.pendingRequests.push({ resolve, reject });
83
+ });
84
+ }
85
+
86
+ if (this.retryCount >= this.maxRetries) {
87
+ utils.error("AutoReLogin", `Maximum re-login attempts (${this.maxRetries}) exceeded`);
88
+ if (this.onReLoginFailure) {
89
+ this.onReLoginFailure(new Error("Max re-login retries exceeded"));
90
+ }
91
+ return false;
92
+ }
93
+
94
+ this.isReLoggingIn = true;
95
+ this.retryCount++;
96
+ utils.log("AutoReLogin", `Starting automatic re-login (attempt ${this.retryCount}/${this.maxRetries})...`);
97
+
98
+ try {
99
+ await this.pauseAPIRequests();
100
+
101
+ const loginHelperModel = require('../engine/models/loginHelper');
102
+ const setOptionsModel = require('../engine/models/setOptions');
103
+ const buildAPIModel = require('../engine/models/buildAPI');
104
+
105
+ await new Promise((resolve, reject) => {
106
+ loginHelperModel(
107
+ this.credentials,
108
+ this.loginOptions,
109
+ (loginError, newApi) => {
110
+ if (loginError) {
111
+ reject(loginError);
112
+ return;
113
+ }
114
+
115
+ if (api) {
116
+ api.ctx = newApi.ctx;
117
+ api.defaultFuncs = newApi.defaultFuncs;
118
+
119
+ if (api.tokenRefreshManager) {
120
+ api.tokenRefreshManager.resetFailureCount();
121
+ }
122
+ }
123
+
124
+ resolve(newApi);
125
+ },
126
+ setOptionsModel,
127
+ buildAPIModel,
128
+ api,
129
+ fbLink,
130
+ ERROR_RETRIEVING
131
+ );
132
+ });
133
+
134
+ utils.log("AutoReLogin", "Re-login successful! Session restored.");
135
+ this.retryCount = 0;
136
+ this.isReLoggingIn = false;
137
+
138
+ this.resolvePendingRequests(true);
139
+
140
+ if (this.onReLoginSuccess) {
141
+ this.onReLoginSuccess();
142
+ }
143
+
144
+ try {
145
+ if (api && api.listenMqtt && api.ctx && api.ctx._listeningActive) {
146
+ try {
147
+ if (typeof api.stopListening === 'function') {
148
+ try { api.stopListening(); } catch (_) {}
149
+ }
150
+ const cb = api.ctx._lastListenCallback || null;
151
+ if (cb) {
152
+ api.listenMqtt(cb);
153
+ } else {
154
+ api.listenMqtt();
155
+ }
156
+ } catch (_) {}
157
+ }
158
+ } catch (_) {}
159
+
160
+ return true;
161
+ } catch (error) {
162
+ utils.error("AutoReLogin", `Re-login failed:`, error.message);
163
+ this.isReLoggingIn = false;
164
+
165
+ if (this.retryCount >= this.maxRetries) {
166
+ this.resolvePendingRequests(false);
167
+ if (this.onReLoginFailure) {
168
+ this.onReLoginFailure(error);
169
+ }
170
+ return false;
171
+ }
172
+
173
+ const backoffDelay = Math.min(30000, Math.pow(2, this.retryCount) * 1000);
174
+ utils.log("AutoReLogin", `Retrying re-login in ${backoffDelay}ms...`);
175
+ await new Promise(resolve => setTimeout(resolve, backoffDelay));
176
+
177
+ return await this.handleSessionExpiry(api, fbLink, ERROR_RETRIEVING);
178
+ }
179
+ }
180
+
181
+ async pauseAPIRequests() {
182
+ utils.log("AutoReLogin", "Pausing API requests during re-login...");
183
+ await new Promise(resolve => setTimeout(resolve, 1000));
184
+ }
185
+
186
+ resolvePendingRequests(success) {
187
+ utils.log("AutoReLogin", `Resolving ${this.pendingRequests.length} pending requests (success: ${success})`);
188
+
189
+ this.pendingRequests.forEach(({ resolve, reject }) => {
190
+ if (success) {
191
+ resolve(true);
192
+ } else {
193
+ reject(new Error("Re-login failed"));
194
+ }
195
+ });
196
+
197
+ this.pendingRequests = [];
198
+ }
199
+
200
+ setReLoginSuccessCallback(callback) {
201
+ this.onReLoginSuccess = callback;
202
+ }
203
+
204
+ setReLoginFailureCallback(callback) {
205
+ this.onReLoginFailure = callback;
206
+ }
207
+
208
+ updateAppState(appState) {
209
+ if (!this.credentials) return;
210
+ if (!Array.isArray(appState) || appState.length === 0) return;
211
+ if (!this.credentials.appState || Array.isArray(this.credentials.appState) || typeof this.credentials.appState === "string") {
212
+ this.credentials.appState = appState;
213
+ }
214
+ }
215
+
216
+ disable() {
217
+ this.enabled = false;
218
+ this.stopSessionMonitoring();
219
+ this.credentials = null;
220
+ this.loginOptions = null;
221
+ this.loginCallback = null;
222
+ utils.log("AutoReLogin", "Auto re-login disabled and credentials cleared");
223
+ }
224
+
225
+ reset() {
226
+ this.retryCount = 0;
227
+ this.isReLoggingIn = false;
228
+ this.pendingRequests = [];
229
+ }
230
+ }
231
+
232
+ const globalAutoReLoginManager = new AutoReLoginManager();
233
+
234
+ module.exports = {
235
+ AutoReLoginManager,
236
+ globalAutoReLoginManager
237
+ };
@@ -0,0 +1,368 @@
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
+ function adaptResponse(res) {
20
+ const response = res.response || res;
21
+ return {
22
+ ...response,
23
+ body: response.data,
24
+ statusCode: response.status,
25
+ request: {
26
+ uri: new URL(response.config.url),
27
+ headers: response.config.headers,
28
+ method: response.config.method.toUpperCase(),
29
+ form: response.config.data,
30
+ formData: response.config.data
31
+ },
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Inspects an API response body for signs of session expiry or Facebook
37
+ * bot-detection checkpoints and emits the appropriate signals on ctx.
38
+ *
39
+ * Returns true if the response looks like a valid authenticated response,
40
+ * false if it signals logout / checkpoint.
41
+ *
42
+ * When a logout is detected and ctx.performAutoLogin is available the
43
+ * function fires it (non-blocking) and throws so the caller knows the
44
+ * original response is unusable.
45
+ */
46
+ async function inspectResponseForSessionIssues(adapted, ctx) {
47
+ if (!ctx || ctx._skipSessionInspect) return;
48
+
49
+ const body = adapted.body;
50
+ if (!body) return;
51
+
52
+ const bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
53
+
54
+ // Facebook bot-detection checkpoint IDs
55
+ const isCheckpoint282 = bodyStr.includes('1501092823525282');
56
+ const isCheckpoint956 = bodyStr.includes('828281030927956');
57
+ const isScrapingWarning = bodyStr.includes('XCheckpointFBScrapingWarningController');
58
+
59
+ if (isCheckpoint282) {
60
+ const err = new Error('Bot checkpoint 282 detected. Please verify the account.');
61
+ err.error = 'checkpoint_282';
62
+ err.res = body;
63
+ if (ctx._emitter && typeof ctx._emitter.emit === 'function') {
64
+ ctx._emitter.emit('checkpoint_282', { res: body });
65
+ }
66
+ throw err;
67
+ }
68
+
69
+ if (isCheckpoint956) {
70
+ const err = new Error('Bot checkpoint 956 detected. Please verify the account.');
71
+ err.error = 'checkpoint_956';
72
+ err.res = body;
73
+ if (ctx._emitter && typeof ctx._emitter.emit === 'function') {
74
+ ctx._emitter.emit('checkpoint_956', { res: body });
75
+ }
76
+ throw err;
77
+ }
78
+
79
+ if (isScrapingWarning) {
80
+ const err = new Error('Facebook scraping warning checkpoint detected.');
81
+ err.error = 'checkpoint_scraping';
82
+ err.res = body;
83
+ if (ctx._emitter && typeof ctx._emitter.emit === 'function') {
84
+ ctx._emitter.emit('checkpoint', { type: 'scraping_warning', res: body });
85
+ }
86
+ throw err;
87
+ }
88
+
89
+ // Detect session expiry / forced logout
90
+ const isLoginRedirect =
91
+ bodyStr.includes('"login.php?') ||
92
+ bodyStr.includes('https://www.facebook.com/login.php') ||
93
+ bodyStr.includes('<form id="login_form"') ||
94
+ bodyStr.includes('id="loginbutton"') ||
95
+ bodyStr.includes('"login_page"');
96
+
97
+ const isLoginBlocked =
98
+ typeof body === 'object' && body !== null && body.error === 1357001;
99
+
100
+ if (isLoginBlocked) {
101
+ const err = new Error('Facebook blocked the login.');
102
+ err.error = 'login_blocked';
103
+ err.res = body;
104
+ throw err;
105
+ }
106
+
107
+ if (isLoginRedirect) {
108
+ if (ctx._emitter && typeof ctx._emitter.emit === 'function') {
109
+ ctx._emitter.emit('sessionExpired', { res: body });
110
+ }
111
+
112
+ if (!ctx.auto_login && typeof ctx.performAutoLogin === 'function') {
113
+ ctx.auto_login = true;
114
+ try {
115
+ const ok = await ctx.performAutoLogin();
116
+ ctx.auto_login = false;
117
+ if (!ok) {
118
+ const err = new Error('Not logged in. Auto re-login failed.');
119
+ err.error = 'Not logged in.';
120
+ err.res = body;
121
+ throw err;
122
+ }
123
+ } catch (autoErr) {
124
+ ctx.auto_login = false;
125
+ throw autoErr;
126
+ }
127
+ } else {
128
+ const err = new Error('Not logged in. Session has expired.');
129
+ err.error = 'Not logged in.';
130
+ err.res = body;
131
+ throw err;
132
+ }
133
+ }
134
+ }
135
+
136
+ async function requestWithRetry(requestFunction, retries = 5, endpoint = '', threadID = '', ctx = null) {
137
+ await globalRateLimiter.checkRateLimit();
138
+
139
+ if (globalRateLimiter.isEndpointOnCooldown("__GLOBAL__")) {
140
+ const cooldown = globalRateLimiter.getEndpointCooldownRemaining("__GLOBAL__");
141
+ console.warn(`Global cooldown active. Waiting ${cooldown}ms...`);
142
+ await delay(cooldown);
143
+ }
144
+
145
+ if (endpoint && globalRateLimiter.isEndpointOnCooldown(endpoint)) {
146
+ const cooldown = globalRateLimiter.getEndpointCooldownRemaining(endpoint);
147
+ console.warn(`Endpoint ${endpoint} on cooldown. Waiting ${cooldown}ms...`);
148
+ await delay(cooldown);
149
+ }
150
+
151
+ if (threadID && globalRateLimiter.isThreadOnCooldown(threadID)) {
152
+ const cooldown = globalRateLimiter.getCooldownRemaining(threadID);
153
+ console.warn(`Thread ${threadID} on cooldown. Waiting ${cooldown}ms...`);
154
+ await delay(cooldown);
155
+ }
156
+
157
+ const checkAndApplyRateLimitCooldowns = (responseBody) => {
158
+ const ERROR_COOLDOWNS = {
159
+ 1545012: 60000,
160
+ 1675004: 30000,
161
+ 368: 120000,
162
+ 404: 5000,
163
+ 500: 10000,
164
+ 503: 30000
165
+ };
166
+
167
+ const applyCooldown = (errorCode) => {
168
+ if (errorCode && ERROR_COOLDOWNS[errorCode]) {
169
+ if (threadID) {
170
+ globalRateLimiter.setThreadCooldown(threadID, ERROR_COOLDOWNS[errorCode]);
171
+ }
172
+ if (endpoint) {
173
+ globalRateLimiter.setEndpointCooldown(endpoint, ERROR_COOLDOWNS[errorCode]);
174
+ }
175
+ console.warn(`Rate limit detected (error ${errorCode}). Applied cooldown.`);
176
+ return true;
177
+ }
178
+ return false;
179
+ };
180
+
181
+ if (!responseBody || typeof responseBody !== 'object') {
182
+ return false;
183
+ }
184
+
185
+ if (applyCooldown(responseBody.error)) {
186
+ return true;
187
+ }
188
+
189
+ if (Array.isArray(responseBody)) {
190
+ for (const item of responseBody) {
191
+ if (item && typeof item === 'object') {
192
+ if (applyCooldown(item.error)) return true;
193
+ if (item.errors && Array.isArray(item.errors)) {
194
+ for (const err of item.errors) {
195
+ const code = err.code || err.extensions?.code;
196
+ if (applyCooldown(code)) return true;
197
+ }
198
+ }
199
+ }
200
+ }
201
+ }
202
+
203
+ if (responseBody.errors && Array.isArray(responseBody.errors)) {
204
+ for (const err of responseBody.errors) {
205
+ const code = err.code || err.extensions?.code;
206
+ if (applyCooldown(code)) return true;
207
+ }
208
+ }
209
+
210
+ return false;
211
+ };
212
+
213
+ for (let i = 0; i < retries; i++) {
214
+ try {
215
+ const res = await requestFunction();
216
+ const adapted = adaptResponse(res);
217
+
218
+ checkAndApplyRateLimitCooldowns(adapted.body);
219
+
220
+ // Inspect for session expiry / bot-detection checkpoints
221
+ await inspectResponseForSessionIssues(adapted, ctx);
222
+
223
+ return adapted;
224
+ } catch (error) {
225
+ // If this is a session/checkpoint error we already raised, propagate immediately
226
+ if (error.error === 'Not logged in.' ||
227
+ error.error === 'checkpoint_282' ||
228
+ error.error === 'checkpoint_956' ||
229
+ error.error === 'checkpoint_scraping' ||
230
+ error.error === 'login_blocked') {
231
+ throw error;
232
+ }
233
+
234
+ // Abort immediately on invalid header characters - retrying won't help
235
+ if (error.code === 'ERR_INVALID_CHAR' ||
236
+ (error.message && error.message.includes('Invalid character in header'))) {
237
+ const e = new Error('Invalid header content detected. Request aborted.');
238
+ e.error = 'invalid_header';
239
+ e.code = 'ERR_INVALID_CHAR';
240
+ e.originalError = error;
241
+ throw e;
242
+ }
243
+
244
+ if (error.response) {
245
+ const adapted = adaptResponse(error.response);
246
+ checkAndApplyRateLimitCooldowns(adapted.body);
247
+ }
248
+
249
+ if (i === retries - 1) {
250
+ console.error(`Request failed after ${retries} attempts:`, error.message);
251
+ if (error.response) {
252
+ return adaptResponse(error.response);
253
+ }
254
+ throw error;
255
+ }
256
+ const backoffTime = (Math.pow(2, i)) * 1000 + Math.floor(Math.random() * 1000);
257
+ console.warn(`Request attempt ${i + 1} failed. Retrying in ${backoffTime}ms...`);
258
+ await delay(backoffTime);
259
+ }
260
+ }
261
+ }
262
+
263
+ function setProxy(proxyUrl) {
264
+ if (proxyUrl) {
265
+ try {
266
+ const parsedProxy = new URL(proxyUrl);
267
+ proxyConfig = {
268
+ proxy: {
269
+ host: parsedProxy.hostname,
270
+ port: parsedProxy.port,
271
+ protocol: parsedProxy.protocol.replace(":", ""),
272
+ auth: parsedProxy.username && parsedProxy.password ? {
273
+ username: parsedProxy.username,
274
+ password: parsedProxy.password,
275
+ } : undefined,
276
+ },
277
+ };
278
+ } catch (e) {
279
+ console.error("Invalid proxy URL. Please use a full URL format (e.g., http://user:pass@host:port).");
280
+ proxyConfig = {};
281
+ }
282
+ } else {
283
+ proxyConfig = {};
284
+ }
285
+ }
286
+
287
+ function cleanGet(url) {
288
+ const fn = () => client.get(url, { timeout: 60000, ...proxyConfig });
289
+ return requestWithRetry(fn);
290
+ }
291
+
292
+ async function get(url, reqJar, qs, options, ctx, customHeader) {
293
+ const config = {
294
+ headers: getHeaders(url, options, ctx, customHeader),
295
+ timeout: 60000,
296
+ params: qs,
297
+ ...proxyConfig,
298
+ validateStatus: (status) => status >= 200 && status < 600,
299
+ };
300
+ const endpoint = new URL(url).pathname;
301
+ const threadHint = ctx && ctx.requestThreadID ? String(ctx.requestThreadID) : '';
302
+ return requestWithRetry(async () => await client.get(url, config), 3, endpoint, threadHint, ctx);
303
+ }
304
+
305
+ async function post(url, reqJar, form, options, ctx, customHeader) {
306
+ const headers = getHeaders(url, options, ctx, customHeader, 'xhr');
307
+ let data = form;
308
+ let contentType = headers['Content-Type'] || 'application/x-www-form-urlencoded';
309
+
310
+ if (contentType.includes('json')) {
311
+ data = JSON.stringify(form);
312
+ } else {
313
+ const transformedForm = new URLSearchParams();
314
+ for (const key in form) {
315
+ if (form.hasOwnProperty(key)) {
316
+ let value = form[key];
317
+ if (getType(value) === "Object") {
318
+ value = JSON.stringify(value);
319
+ }
320
+ transformedForm.append(key, value);
321
+ }
322
+ }
323
+ data = transformedForm.toString();
324
+ }
325
+
326
+ headers['Content-Type'] = contentType;
327
+
328
+ const config = {
329
+ headers,
330
+ timeout: 60000,
331
+ ...proxyConfig,
332
+ validateStatus: (status) => status >= 200 && status < 600,
333
+ };
334
+ const endpoint = new URL(url).pathname;
335
+ const threadHint = ctx && ctx.requestThreadID ? String(ctx.requestThreadID) : '';
336
+ return requestWithRetry(async () => await client.post(url, data, config), 3, endpoint, threadHint, ctx);
337
+ }
338
+
339
+ async function postFormData(url, reqJar, form, qs, options, ctx) {
340
+ const formData = new FormData();
341
+ for (const key in form) {
342
+ if (form.hasOwnProperty(key)) {
343
+ formData.append(key, form[key]);
344
+ }
345
+ }
346
+
347
+ const customHeader = { "Content-Type": `multipart/form-data; boundary=${formData.getBoundary()}` };
348
+
349
+ const config = {
350
+ headers: getHeaders(url, options, ctx, customHeader, 'xhr'),
351
+ timeout: 60000,
352
+ params: qs,
353
+ ...proxyConfig,
354
+ validateStatus: (status) => status >= 200 && status < 600,
355
+ };
356
+ const endpoint = new URL(url).pathname;
357
+ const threadHint = ctx && ctx.requestThreadID ? String(ctx.requestThreadID) : '';
358
+ return requestWithRetry(async () => await client.post(url, formData, config), 3, endpoint, threadHint, ctx);
359
+ }
360
+
361
+ module.exports = {
362
+ cleanGet,
363
+ get,
364
+ post,
365
+ postFormData,
366
+ getJar: () => jar,
367
+ setProxy,
368
+ };
@@ -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;