luadao / solver.js
vipsphi's picture
Upload 11 files
58461df verified
const { BrowserWindow, session } = require("electron");
const logger = require("./logger");
class RecaptchaSolver {
constructor() {
this.solverWindow = null;
}
findChrome() {
return "Electron";
}
async createSolverWindow(targetUrl, proxy = null) {
if (this.solverWindow && !this.solverWindow.isDestroyed()) {
this.solverWindow.destroy();
}
const partition = `temp:solver-${Date.now()}-${Math.random().toString(36).substring(7)}`;
const ses = session.fromPartition(partition);
if (proxy) {
try {
const proxyUrl = new URL(proxy);
const proxyRules = proxyUrl.host; // ip:port
logger.debug(`[RecaptchaSolver] Setting proxy: ${proxyRules}`);
// Store credentials in session for app-level login handler (app.on('login'))
ses.proxyCredentials = {
username: proxyUrl.username,
password: proxyUrl.password
};
await ses.setProxy({ proxyRules });
} catch (e) {
logger.error(`[RecaptchaSolver] Proxy format error: ${proxy}. Skipping.`);
}
}
this.solverWindow = new BrowserWindow({
width: 360,
height: 640,
show: true,
x: -32000,
y: -32000,
frame: false,
skipTaskbar: true,
focusable: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: false,
session: ses,
webSecurity: false,
backgroundThrottling: false,
},
});
this.solverWindow.webContents.session.webRequest.onHeadersReceived(
(details, callback) => {
const responseHeaders = Object.assign({}, details.responseHeaders);
if (responseHeaders["content-security-policy"])
delete responseHeaders["content-security-policy"];
if (responseHeaders["x-frame-options"])
delete responseHeaders["x-frame-options"];
callback({ responseHeaders, cancel: false });
}
);
this.solverWindow.webContents.setUserAgent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
);
this.solverWindow.webContents.setAudioMuted(true);
this.solverWindow.webContents.on("did-fail-load", (event, errorCode, errorDescription, validatedURL) => {
logger.warn(`[RecaptchaSolver] Failed to load URL: ${validatedURL} | Error: ${errorDescription} (${errorCode})`);
});
await this.solverWindow.loadURL(targetUrl);
}
async simulateHumanInteraction() {
if (!this.solverWindow || this.solverWindow.isDestroyed()) return;
const contents = this.solverWindow.webContents;
try {
contents.sendInputEvent({ type: "mouseEnter", x: 10, y: 10 });
contents.sendInputEvent({ type: "mouseMove", x: 100, y: 100 });
await new Promise((r) => setTimeout(r, 100));
contents.sendInputEvent({ type: "mouseMove", x: 200, y: 150 });
contents.sendInputEvent({
type: "mouseDown",
x: 200,
y: 150,
button: "left",
clickCount: 1,
});
await new Promise((r) => setTimeout(r, 50));
contents.sendInputEvent({
type: "mouseUp",
x: 200,
y: 150,
button: "left",
clickCount: 1,
});
} catch (e) { }
}
async getRecaptchaToken(websiteURL, websiteKey, pageAction, proxy = null) {
const FIXED_URL = "https://labs.google";
const FIXED_KEY = "6LdsFiUsAAAAAIjVDZcuLhaHiDn5nnHVXVRQGeMV";
try {
await this.createSolverWindow(FIXED_URL, proxy);
await this.simulateHumanInteraction();
const token = await this.solverWindow.webContents.executeJavaScript(
`
(async function() {
const siteKey = '${FIXED_KEY}';
const action = '${pageAction}';
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function ensureLibrary() {
if (window.grecaptcha && window.grecaptcha.execute) return;
const old = document.getElementById('recaptcha-solver-script');
if (old) old.remove();
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.id = 'recaptcha-solver-script';
script.src = 'https://www.google.com/recaptcha/api.js?render=' + siteKey;
script.onload = () => {
// Đợi thêm 1s để library init xong
setTimeout(resolve, 1000);
};
script.onerror = () => reject("Load script failed");
document.head.appendChild(script);
});
}
try {
await ensureLibrary();
let attempts = 0;
while (!window.grecaptcha || !window.grecaptcha.execute) {
if (attempts++ > 20) throw new Error("Timeout waiting for grecaptcha");
await wait(200);
}
return new Promise((resolve, reject) => {
window.grecaptcha.ready(() => {
window.grecaptcha.execute(siteKey, { action: action })
.then(token => resolve(token))
.catch(err => reject("Execute Error: " + err.message));
});
});
} catch (e) {
return "ERROR: " + e.message;
}
})();
`,
true
);
if (!token || typeof token !== "string" || token.startsWith("ERROR:")) {
throw new Error("Token lỗi: " + token);
}
logger.debug(`[Solver] Token generated: ${token.substring(0, 20)}...`);
this.close();
return token;
} catch (error) {
logger.error(`[Solver] Error: ${error.message}`);
this.close();
return "ERROR: " + error.message;
}
}
async close() {
if (this.solverWindow && !this.solverWindow.isDestroyed()) {
this.solverWindow.destroy();
this.solverWindow = null;
}
}
}
module.exports = RecaptchaSolver;