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;