|
|
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;
|
|
|
|
|
|
logger.debug(`[RecaptchaSolver] Setting proxy: ${proxyRules}`);
|
|
|
|
|
|
|
|
|
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;
|
|
|
|