GG / index.js
maylinejix's picture
Rename Dockerfile to index.js
ff58ade verified
const express = require('express');
const { chromium } = require('playwright');
const tf = require('@tensorflow/tfjs-node');
const axios = require('axios');
const sharp = require('sharp');
const app = express();
app.use(express.json());
const HCAPTCHA_FRAME = "iframe[src*='hcaptcha.com/captcha']";
const HCAPTCHA_CHECKBOX = "iframe[src*='hcaptcha.com/checkbox']";
const CHALLENGE_CONTAINER = ".challenge-container";
let model = null;
function log(...args) {
console.log('[hCaptcha API]', new Date().toISOString(), ...args);
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function randomDelay(min, max) {
return delay(Math.floor(Math.random() * (max - min + 1)) + min);
}
async function loadModel() {
if (!model) {
try {
log('Loading ML model...');
model = await tf.loadLayersModel('https://tfhub.dev/google/tfjs-model/imagenet/mobilenet_v2_100_224/classification/3/default/1', {
fromTFHub: true
});
log('ML model loaded successfully');
} catch (error) {
log('Failed to load model, using fallback:', error.message);
model = null;
}
}
return model;
}
async function preprocessImage(imageBuffer) {
const processedImage = await sharp(imageBuffer)
.resize(224, 224)
.toBuffer();
const tensor = tf.node.decodeImage(processedImage, 3)
.toFloat()
.div(255.0)
.expandDims(0);
return tensor;
}
async function analyzeImageWithML(imageUrl) {
try {
const response = await axios.get(imageUrl, { responseType: 'arraybuffer' });
const imageBuffer = Buffer.from(response.data);
const tensor = await preprocessImage(imageBuffer);
if (model) {
const predictions = await model.predict(tensor);
const predArray = await predictions.data();
const topPredictions = Array.from(predArray)
.map((prob, idx) => ({ class: idx, probability: prob }))
.sort((a, b) => b.probability - a.probability)
.slice(0, 5);
tensor.dispose();
predictions.dispose();
return topPredictions;
}
tensor.dispose();
return null;
} catch (error) {
log('ML analysis error:', error.message);
return null;
}
}
async function analyzeImageFeatures(frame, imageElement) {
return await frame.evaluate((img) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
let brightness = 0;
let edges = 0;
const colors = new Set();
let redDominance = 0;
let blueDominance = 0;
let greenDominance = 0;
for (let i = 0; i < imageData.data.length; i += 4) {
const r = imageData.data[i];
const g = imageData.data[i + 1];
const b = imageData.data[i + 2];
brightness += (r + g + b) / 3;
if (r > g && r > b) redDominance++;
if (b > r && b > g) blueDominance++;
if (g > r && g > b) greenDominance++;
const colorKey = `${Math.floor(r/50)*50},${Math.floor(g/50)*50},${Math.floor(b/50)*50}`;
colors.add(colorKey);
if (i + canvas.width * 4 < imageData.data.length) {
if (Math.abs(r - imageData.data[i + canvas.width * 4]) > 30) edges++;
}
}
const totalPixels = imageData.data.length / 4;
return {
brightness: brightness / totalPixels,
edges: edges,
colorDiversity: colors.size,
redDominance: redDominance / totalPixels,
blueDominance: blueDominance / totalPixels,
greenDominance: greenDominance / totalPixels
};
}, imageElement);
}
async function classifyImage(imageUrl, challengeText, features) {
const mlPredictions = await analyzeImageWithML(imageUrl);
let score = 0;
const challenge = challengeText.toLowerCase();
if (mlPredictions && mlPredictions.length > 0) {
const topClass = mlPredictions[0].class;
if (challenge.includes('vehicle') || challenge.includes('car') || challenge.includes('bus') || challenge.includes('truck')) {
if ((topClass >= 400 && topClass <= 900) || features.edges > 1000) {
score += 3;
}
} else if (challenge.includes('animal') || challenge.includes('cat') || challenge.includes('dog') || challenge.includes('bird')) {
if ((topClass >= 0 && topClass <= 400) || features.colorDiversity > 80) {
score += 3;
}
} else if (challenge.includes('plant') || challenge.includes('tree') || challenge.includes('flower')) {
if (features.greenDominance > 0.3 || (topClass >= 900 && topClass <= 1000)) {
score += 3;
}
} else if (challenge.includes('sky') || challenge.includes('cloud')) {
if (features.blueDominance > 0.4 || features.brightness > 150) {
score += 3;
}
} else if (challenge.includes('water') || challenge.includes('ocean') || challenge.includes('lake')) {
if (features.blueDominance > 0.3 || features.brightness > 100) {
score += 3;
}
}
}
if (challenge.includes('vehicle') || challenge.includes('car') || challenge.includes('bus')) {
if (features.edges > 1200) score += 2;
if (features.colorDiversity > 70) score += 1;
} else if (challenge.includes('animal') || challenge.includes('cat') || challenge.includes('dog')) {
if (features.colorDiversity > 90) score += 2;
if (features.edges > 800) score += 1;
} else if (challenge.includes('plant') || challenge.includes('tree')) {
if (features.greenDominance > 0.25) score += 2;
if (features.colorDiversity > 60) score += 1;
} else if (challenge.includes('sky') || challenge.includes('water')) {
if (features.brightness > 140) score += 2;
if (features.blueDominance > 0.3) score += 1;
} else if (challenge.includes('building') || challenge.includes('house')) {
if (features.edges > 1500) score += 2;
if (features.colorDiversity < 70) score += 1;
}
return score;
}
async function findPuzzleMatch(frame) {
log("Analyzing puzzle piece position with ML");
const puzzleBox = await frame.$eval('.challenge-image', el => {
const rect = el.getBoundingClientRect();
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
});
const pieceBox = await frame.$eval('.draggable-piece', el => {
const rect = el.getBoundingClientRect();
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
});
const gapX = await frame.evaluate(() => {
const canvas = document.querySelector('.challenge-image');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
let darkestX = 0;
let minBrightness = 999;
let maxEdgeCount = 0;
let edgeX = 0;
for (let x = 0; x < canvas.width; x += 3) {
let columnBrightness = 0;
let edgeCount = 0;
for (let y = 0; y < canvas.height; y++) {
const i = (y * canvas.width + x) * 4;
const brightness = (imageData.data[i] + imageData.data[i+1] + imageData.data[i+2]) / 3;
columnBrightness += brightness;
if (y > 0) {
const prevI = ((y-1) * canvas.width + x) * 4;
const diff = Math.abs(brightness - (imageData.data[prevI] + imageData.data[prevI+1] + imageData.data[prevI+2]) / 3);
if (diff > 40) edgeCount++;
}
}
columnBrightness /= canvas.height;
if (columnBrightness < minBrightness) {
minBrightness = columnBrightness;
darkestX = x;
}
if (edgeCount > maxEdgeCount) {
maxEdgeCount = edgeCount;
edgeX = x;
}
}
return (darkestX + edgeX) / 2;
});
return {
targetX: puzzleBox.x + gapX,
targetY: puzzleBox.y + puzzleBox.height / 2,
startX: pieceBox.x + pieceBox.width / 2,
startY: pieceBox.y + pieceBox.height / 2
};
}
async function humanMouseMove(page, startX, startY, endX, endY) {
await page.mouse.move(startX, startY);
await randomDelay(50, 150);
await page.mouse.down();
await randomDelay(100, 200);
const steps = 15 + Math.floor(Math.random() * 10);
const controlX = (startX + endX) / 2 + (Math.random() - 0.5) * 50;
const controlY = (startY + endY) / 2 + (Math.random() - 0.5) * 50;
for (let i = 1; i <= steps; i++) {
const t = i / steps;
const x = Math.pow(1 - t, 2) * startX + 2 * (1 - t) * t * controlX + Math.pow(t, 2) * endX;
const y = Math.pow(1 - t, 2) * startY + 2 * (1 - t) * t * controlY + Math.pow(t, 2) * endY;
await page.mouse.move(x + (Math.random() - 0.5) * 2, y + (Math.random() - 0.5) * 2);
await randomDelay(30, 80);
}
await randomDelay(150, 300);
await page.mouse.up();
await randomDelay(200, 400);
}
async function solveDragDropChallenge(page, challengeFrame) {
log("Solving drag & drop challenge");
const position = await findPuzzleMatch(challengeFrame);
await humanMouseMove(page, position.startX, position.startY, position.targetX, position.targetY);
await randomDelay(500, 1000);
return true;
}
async function solveImageChallenge(challengeFrame, challengeText) {
log("Solving image selection challenge with ML:", challengeText);
const images = await challengeFrame.$$('.task-image');
log(`Found ${images.length} images`);
const imageData = [];
for (let i = 0; i < images.length; i++) {
const img = images[i];
const features = await analyzeImageFeatures(challengeFrame, img);
const imageUrl = await img.evaluate(el => el.src);
const score = await classifyImage(imageUrl, challengeText, features);
imageData.push({ index: i, score, features });
await randomDelay(100, 200);
}
imageData.sort((a, b) => b.score - a.score);
const threshold = imageData[0].score * 0.7;
const selectedIndices = imageData
.filter(data => data.score >= threshold)
.map(data => data.index);
if (selectedIndices.length === 0) {
const randomCount = Math.min(3, Math.floor(Math.random() * 3) + 1);
for (let i = 0; i < randomCount; i++) {
selectedIndices.push(imageData[i].index);
}
}
log(`Selecting ${selectedIndices.length} images based on ML:`, selectedIndices);
for (const index of selectedIndices) {
await randomDelay(300, 700);
await images[index].click();
await randomDelay(200, 400);
}
await randomDelay(500, 1000);
const submitButton = await challengeFrame.$('.button-submit');
if (submitButton) {
await submitButton.click();
await randomDelay(1500, 2500);
}
return true;
}
async function solveCaptcha(url, options = {}) {
const {
waitTimeout = 30000,
maxRetries = 3
} = options;
await loadModel();
const browser = await chromium.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--disable-gpu'
]
});
const context = await browser.newContext({
viewport: { width: 1280, height: 720 },
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
});
const page = await context.newPage();
try {
log(`Navigating to: ${url}`);
await page.goto(url, { waitUntil: 'networkidle', timeout: waitTimeout });
await page.waitForSelector(HCAPTCHA_CHECKBOX, { state: "attached", timeout: waitTimeout });
const checkboxElement = await page.$(HCAPTCHA_CHECKBOX);
if (!checkboxElement) {
throw new Error("Could not find hCaptcha checkbox iframe");
}
const checkboxFrame = await checkboxElement.contentFrame();
if (!checkboxFrame) {
throw new Error("Could not find hCaptcha checkbox iframe content");
}
const checkboxRect = await checkboxElement.boundingBox();
if (!checkboxRect) {
throw new Error("Could not get checkbox position");
}
log("Clicking checkbox with mouse");
const checkboxX = checkboxRect.x + checkboxRect.width / 2;
const checkboxY = checkboxRect.y + checkboxRect.height / 2;
await page.mouse.move(checkboxX, checkboxY);
await randomDelay(100, 300);
await page.mouse.click(checkboxX, checkboxY);
await randomDelay(2000, 3000);
const challengeElement = await page.$(HCAPTCHA_FRAME);
if (!challengeElement) {
log("No challenge appeared - likely passed");
const cookies = await context.cookies();
await browser.close();
return {
success: true,
message: "No challenge required",
cookies: cookies
};
}
const challengeFrame = await challengeElement.contentFrame();
if (!challengeFrame) {
throw new Error("Could not find hCaptcha challenge iframe content");
}
await challengeFrame.waitForSelector(CHALLENGE_CONTAINER, { timeout: waitTimeout });
let attempts = 0;
let solved = false;
while (!solved && attempts < maxRetries) {
attempts++;
log(`Attempt ${attempts}/${maxRetries}`);
const challengeText = await challengeFrame.$eval('.prompt-text', el => el.textContent).catch(() => '');
log("Challenge prompt:", challengeText);
const isDragDrop = await challengeFrame.$('.draggable-piece') !== null;
try {
if (isDragDrop) {
await solveDragDropChallenge(page, challengeFrame);
} else {
await solveImageChallenge(challengeFrame, challengeText);
}
await randomDelay(2000, 3000);
const stillHasChallenge = await page.$(HCAPTCHA_FRAME) !== null;
if (!stillHasChallenge) {
solved = true;
log("Challenge solved successfully with ML!");
}
} catch (error) {
log(`Attempt ${attempts} failed:`, error.message);
await randomDelay(1000, 2000);
}
}
if (!solved) {
await browser.close();
return {
success: false,
message: "Could not solve hCaptcha after maximum retries"
};
}
const cookies = await context.cookies();
const token = await page.evaluate(() => {
const responseInput = document.querySelector('[name="h-captcha-response"]');
return responseInput ? responseInput.value : null;
});
await browser.close();
return {
success: true,
message: "hCaptcha solved successfully with ML",
token: token,
cookies: cookies
};
} catch (error) {
await browser.close();
throw error;
}
}
app.post('/solve', async (req, res) => {
try {
const { url, options } = req.body;
if (!url) {
return res.status(400).json({
success: false,
error: 'URL is required'
});
}
log(`Received solve request for: ${url}`);
const result = await solveCaptcha(url, options);
res.json(result);
} catch (error) {
log('Error:', error.message);
res.status(500).json({
success: false,
error: error.message
});
}
});
app.get('/health', (req, res) => {
res.json({ status: 'ok', model: model ? 'loaded' : 'fallback', timestamp: new Date().toISOString() });
});
const PORT = process.env.PORT || 7860;
app.listen(PORT, '0.0.0.0', () => {
log(`hCaptcha Solver API with ML running on port ${PORT}`);
});