open-vtop 1.0.9 → 1.0.10
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/dist/session-manager.js +138 -93
- package/package.json +1 -1
package/dist/session-manager.js
CHANGED
|
@@ -139,118 +139,163 @@ class VTOPSessionManager {
|
|
|
139
139
|
this.state.username = null;
|
|
140
140
|
await this.initialize();
|
|
141
141
|
}
|
|
142
|
-
async login(username, password, maxAttempts =
|
|
142
|
+
async login(username, password, maxAttempts = 30, // Increased default for parallel attempts total buffer
|
|
143
|
+
concurrency = 3) {
|
|
144
|
+
const startTime = Date.now();
|
|
143
145
|
if (!this.state.initialized) {
|
|
144
146
|
console.log("Session not initialized, initializing first...");
|
|
145
147
|
await this.initialize();
|
|
146
148
|
}
|
|
147
|
-
console.log(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
console.log(`Starting parallel VTOP login (concurrency=${concurrency})...`);
|
|
150
|
+
this.events.emit("log", `Starting parallel login with ${concurrency} workers...`);
|
|
151
|
+
const abortController = new AbortController();
|
|
152
|
+
const signal = abortController.signal;
|
|
153
|
+
let winnerDetected = false;
|
|
154
|
+
let loginSuccess = false;
|
|
155
|
+
// We'll trust the first worker that finds a text captcha to handle the login.
|
|
156
|
+
// If it fails the POST, we fail the whole batch for simplicity,
|
|
157
|
+
// or we could retry, but let's stick to "first valid captcha wins" for now.
|
|
158
|
+
const pollWorker = async (workerId) => {
|
|
159
|
+
let attempts = 0;
|
|
160
|
+
const logPrefix = `[Worker ${workerId}]`;
|
|
152
161
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
162
|
+
while (!signal.aborted && !winnerDetected) {
|
|
163
|
+
attempts++;
|
|
164
|
+
if (attempts > maxAttempts) {
|
|
165
|
+
console.log(`${logPrefix} Max attempts reached.`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
// Check signal before fetch
|
|
170
|
+
if (signal.aborted)
|
|
171
|
+
break;
|
|
172
|
+
// console.log(`${logPrefix} Fetching login page...`); // verbose
|
|
173
|
+
const res = await this.fetchWithCookies(LOGIN_PAGE, {
|
|
174
|
+
signal,
|
|
175
|
+
});
|
|
176
|
+
// Check signal after fetch (in case it aborted during fetch)
|
|
177
|
+
if (signal.aborted)
|
|
178
|
+
break;
|
|
179
|
+
const body = await res.text();
|
|
180
|
+
// Re-check detection
|
|
181
|
+
if (winnerDetected || signal.aborted)
|
|
182
|
+
break;
|
|
183
|
+
const { isTextCaptcha, isRecaptcha, csrf, imgDataUri } = detectCaptcha(body);
|
|
184
|
+
this.events.emit("log", `${logPrefix} text=${isTextCaptcha} recap=${isRecaptcha}`);
|
|
185
|
+
if (isTextCaptcha && !winnerDetected) {
|
|
186
|
+
// ATOMIC CLAIM
|
|
187
|
+
if (winnerDetected)
|
|
188
|
+
break; // Double check
|
|
189
|
+
winnerDetected = true;
|
|
190
|
+
console.log(`${logPrefix} !!! WINNER detected Text CAPTCHA !!!`);
|
|
191
|
+
this.events.emit("log", `${logPrefix} WINNER! Claiming login task.`);
|
|
192
|
+
// Cancel other workers immediately
|
|
193
|
+
abortController.abort();
|
|
194
|
+
// Proceed with solving and login
|
|
195
|
+
const curJsession = this.state.cookies.get("JSESSIONID") || "(?)";
|
|
196
|
+
const curServerID = this.state.cookies.get("SERVERID") || "(?)";
|
|
197
|
+
let solvedCaptcha = "";
|
|
198
|
+
if (imgDataUri) {
|
|
199
|
+
const cleanDataUri = imgDataUri.trim();
|
|
200
|
+
try {
|
|
201
|
+
solvedCaptcha = await solve(cleanDataUri);
|
|
202
|
+
console.log(`${logPrefix} Solved: ${solvedCaptcha}`);
|
|
203
|
+
this.events.emit("log", `${logPrefix} Solved CAPTCHA: ${solvedCaptcha}`);
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
console.error(`${logPrefix} Solve failed:`, e);
|
|
207
|
+
this.events.emit("log", `${logPrefix} Solve failed.`);
|
|
208
|
+
}
|
|
170
209
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
210
|
+
console.log(`${logPrefix} Submitting Login POST...`);
|
|
211
|
+
this.events.emit("log", `${logPrefix} Submitting credentials...`);
|
|
212
|
+
const loginForm = new URLSearchParams();
|
|
213
|
+
if (csrf)
|
|
214
|
+
loginForm.set("_csrf", csrf);
|
|
215
|
+
loginForm.set("username", username);
|
|
216
|
+
loginForm.set("password", password);
|
|
217
|
+
loginForm.set("captchaStr", solvedCaptcha);
|
|
218
|
+
const _cookies = `JSESSIONID=${curJsession}; SERVERID=${curServerID}`;
|
|
219
|
+
const postHeaders = {
|
|
220
|
+
...LOGIN_POST_HEADERS,
|
|
221
|
+
Cookie: _cookies,
|
|
222
|
+
};
|
|
223
|
+
const postRes = await fetch(LOGIN_PAGE, {
|
|
224
|
+
method: "POST",
|
|
225
|
+
headers: postHeaders,
|
|
226
|
+
body: loginForm.toString(),
|
|
227
|
+
redirect: "manual",
|
|
228
|
+
});
|
|
229
|
+
this.storeCookies(postRes);
|
|
230
|
+
// Follow redirect if existing
|
|
231
|
+
const location = postRes.headers.get("Location");
|
|
232
|
+
if (location) {
|
|
233
|
+
const redirectUrl = location.startsWith("http")
|
|
234
|
+
? location
|
|
235
|
+
: new URL(location, LOGIN_PAGE).toString();
|
|
236
|
+
await this.fetchWithCookies(redirectUrl);
|
|
176
237
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
238
|
+
this.state.loggedIn = true;
|
|
239
|
+
this.state.username = username;
|
|
240
|
+
// Save credentials asynchronously
|
|
241
|
+
this.saveCredentials(username, password).catch((err) => console.error("Save creds failed", err));
|
|
242
|
+
const duration = (Date.now() - startTime) / 1000;
|
|
243
|
+
console.log(`${logPrefix} Login success in ${duration}s`);
|
|
244
|
+
this.events.emit("log", `${logPrefix} Login SUCCESS in ${duration}s`);
|
|
245
|
+
const logEntry = `[${new Date().toISOString()}] Login took ${duration} seconds (Workers: ${concurrency})\n`;
|
|
181
246
|
try {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
await fs.writeFile(failPath, imgDataUri);
|
|
185
|
-
console.log(`Saved failed captcha data URI to ${failPath}`);
|
|
247
|
+
await fs.appendFile("login_times.txt", logEntry);
|
|
248
|
+
console.log(`${logPrefix} Login time recorded to file.`);
|
|
186
249
|
}
|
|
187
|
-
catch (
|
|
188
|
-
console.error(
|
|
250
|
+
catch (err) {
|
|
251
|
+
console.error(`${logPrefix} Failed to write login time:`, err);
|
|
189
252
|
}
|
|
253
|
+
loginSuccess = true;
|
|
254
|
+
return;
|
|
190
255
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
loginForm.set("username", username);
|
|
200
|
-
loginForm.set("password", password);
|
|
201
|
-
loginForm.set("captchaStr", solvedCaptcha);
|
|
202
|
-
const _cookies = `JSESSIONID=${curJsession}; SERVERID=${curServerID}`;
|
|
203
|
-
const postHeaders = {
|
|
204
|
-
...LOGIN_POST_HEADERS,
|
|
205
|
-
Cookie: _cookies,
|
|
206
|
-
};
|
|
207
|
-
try {
|
|
208
|
-
const postRes = await fetch(LOGIN_PAGE, {
|
|
209
|
-
method: "POST",
|
|
210
|
-
headers: postHeaders,
|
|
211
|
-
body: loginForm.toString(),
|
|
212
|
-
redirect: "manual",
|
|
213
|
-
});
|
|
214
|
-
console.log(` -> Login POST status: ${postRes.status}`);
|
|
215
|
-
this.storeCookies(postRes);
|
|
216
|
-
const setCookie = postRes.headers.getSetCookie?.() || [];
|
|
217
|
-
if (setCookie.length > 0) {
|
|
218
|
-
console.log(" -> New cookies received");
|
|
219
|
-
}
|
|
220
|
-
const location = postRes.headers.get("Location");
|
|
221
|
-
if (location) {
|
|
222
|
-
console.log(` -> Redirect to: ${location}`);
|
|
223
|
-
const redirectUrl = location.startsWith("http")
|
|
224
|
-
? location
|
|
225
|
-
: new URL(location, LOGIN_PAGE).toString();
|
|
226
|
-
await this.fetchWithCookies(redirectUrl);
|
|
256
|
+
else {
|
|
257
|
+
// Not text captcha, retry
|
|
258
|
+
if (isRecaptcha) {
|
|
259
|
+
this.events.emit("log", `${logPrefix} Saw reCAPTCHA, skipping...`);
|
|
260
|
+
}
|
|
261
|
+
// Random delay/jitter to desynchronize workers
|
|
262
|
+
const delay = 500 + Math.random() * 500;
|
|
263
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
227
264
|
}
|
|
228
|
-
console.log("Login POST submitted successfully!");
|
|
229
|
-
this.state.loggedIn = true;
|
|
230
|
-
this.state.username = username;
|
|
231
|
-
await this.saveCredentials(username, password);
|
|
232
|
-
this.events.emit("login-complete");
|
|
233
|
-
return true;
|
|
234
265
|
}
|
|
235
266
|
catch (e) {
|
|
236
|
-
|
|
267
|
+
if (e.name === "AbortError") {
|
|
268
|
+
// Ignore aborts
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
console.error(`${logPrefix} error:`, e);
|
|
272
|
+
this.events.emit("log", `${logPrefix} error: ${e.message}`);
|
|
273
|
+
// Short delay on error
|
|
274
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
237
275
|
}
|
|
238
276
|
}
|
|
239
|
-
if (isRecaptcha) {
|
|
240
|
-
console.log("reCAPTCHA detected - cannot solve automatically");
|
|
241
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
245
277
|
}
|
|
246
|
-
catch (
|
|
247
|
-
|
|
248
|
-
|
|
278
|
+
catch (err) {
|
|
279
|
+
if (err.name !== "AbortError") {
|
|
280
|
+
console.error(`${logPrefix} unexpected fatal error:`, err);
|
|
281
|
+
}
|
|
249
282
|
}
|
|
283
|
+
};
|
|
284
|
+
const workers = [];
|
|
285
|
+
for (let i = 1; i <= concurrency; i++) {
|
|
286
|
+
workers.push(pollWorker(i));
|
|
287
|
+
}
|
|
288
|
+
await Promise.all(workers);
|
|
289
|
+
if (loginSuccess) {
|
|
290
|
+
this.events.emit("login-complete");
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
console.log("All workers finished without success.");
|
|
295
|
+
this.events.emit("log", "All workers failed/exhausted.");
|
|
296
|
+
this.events.emit("login-complete");
|
|
297
|
+
return false;
|
|
250
298
|
}
|
|
251
|
-
console.log(`Login failed after ${maxAttempts} attempts`);
|
|
252
|
-
this.events.emit("login-complete");
|
|
253
|
-
return false;
|
|
254
299
|
}
|
|
255
300
|
isLoggedIn() {
|
|
256
301
|
return this.state.loggedIn;
|
package/package.json
CHANGED