Automatically solve ReCAPTCHA, adapted from Wikidepia's rektCaptcha
// ==UserScript== // @name ReCAPTCHA Solver // @namespace http://tampermonkey.net/ // @version 2026-02-26.1 // @description Automatically solve ReCAPTCHA, adapted from Wikidepia's rektCaptcha // @author tigeryu8900 // @match *://*/* // @icon https://upload.wikimedia.org/wikipedia/commons/a/ad/RecaptchaLogo.svg // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/ort.min.js // @grant GM_getResourceURL // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_xmlhttpRequest // @connect cdn.jsdelivr.net // ==/UserScript== /* global ort, Jimp */ 'use strict'; if (self === top) { function registerBooleanSetting(key, description, defaultValue) { const onText = `${description}: on`; const offText = `${description}: off`; let id; function enable() { GM_setValue(key, true); id = GM_registerMenuCommand(onText, disable); } function disable() { GM_setValue(key, false); id = GM_registerMenuCommand(offText, enable); } id = GM_getValue(key, defaultValue) ? GM_registerMenuCommand(onText, disable) : GM_registerMenuCommand(offText, enable); } function registerIntSetting(key, description, defaultValue) { let id; function set() { const value = parseInt(prompt(description), 10); if (!isNaN(value)) { GM_setValue(key, value); id = GM_registerMenuCommand(`${description}: ${value}`, set); } } id = GM_registerMenuCommand(`${description}: ${GM_getValue(key, defaultValue)}`, set); } registerBooleanSetting('recaptcha_auto_open', 'Auto Open', false); registerBooleanSetting('recaptcha_auto_solve', 'Auto Solve', true); registerIntSetting('recaptcha_click_delay_time', 'Click Delay Time (ms)', 0); registerIntSetting('recaptcha_solve_delay_time', 'Solve Delay Time (ms)', 0); } window.addEventListener('message', e => { if (e.data === '__RECAPTCHA_SOLVER__') { const port = e.ports && e.ports[0]; if (!port) return; port.onmessage = e => { switch (e.data.q) { case 'recaptcha_image_visible': port.postMessage({ id: e.data.id, value: Array.prototype.some.call( document.querySelectorAll( 'iframe[src*="/recaptcha/api2/bframe"], iframe[src*="/recaptcha/enterprise/bframe"]' ), div => getComputedStyle(div).visibility === 'visible' ), }); break; case 'recaptcha_widget_visible': port.postMessage({ id: e.data.id, value: Array.prototype.some.call( document.querySelectorAll( 'iframe[src*="/recaptcha/api2/anchor"], iframe[src*="/recaptcha/enterprise/anchor"]' ), div => getComputedStyle(div).visibility === 'visible' ), }); break; } }; port.postMessage('__RECAPTCHA_SOLVER__'); } }); if (/^\w+:\/\/\w+\.(google\.com|recaptcha\.net)\/recaptcha\/(api2|enterprise)\//.test(location.href)) { const queryQueue = {}; const portPromise = new Promise(resolve => { const { port1, port2 } = new MessageChannel(); port1.onmessage = e => { if (e.data === '__RECAPTCHA_SOLVER__') { port1.onmessage = e => { if (queryQueue[e.data.id]) { queryQueue[e.data.id](e.data.value); } }; resolve(port1); } }; window.parent.postMessage('__RECAPTCHA_SOLVER__', '*', [port2]); }); async function query(q) { const port = await portPromise; const id = Math.random().toString().substring(2); return new Promise(resolve => { queryQueue[id] = resolve; port.postMessage({ id, q }); }); } class Time { static time() { if (!Date.now) { Date.now = () => new Date().getTime(); } return Date.now(); } static sleep(i = 1000) { return new Promise(resolve => setTimeout(resolve, i)); } static async random_sleep(min, max) { const duration = Math.floor(Math.random() * (max - min) + min); return await Time.sleep(duration); } } function SimulateMouseClick(element, clientX = null, clientY = null) { if (clientX === null || clientY === null) { const box = element.getBoundingClientRect(); clientX = box.left + box.width / 2; clientY = box.top + box.height / 2; } if (isNaN(clientX) || isNaN(clientY)) { return; } // Send mouseover, mousedown, mouseup, click, mouseout [ 'mouseover', 'mouseenter', 'mousedown', 'mouseup', 'click', 'mouseout', ].forEach(eventName => { const event = new MouseEvent(eventName, { detail: 1 - (eventName === 'mouseover'), bubbles: true, cancelable: true, clientX: clientX, clientY: clientY, }); element.dispatchEvent(event); }); } function imageDataToTensor(image, dims, normalize = true) { // 1. Get buffer data from image and extract R, G, and B arrays. var imageBufferData = image.bitmap.data; const [redArray, greenArray, blueArray] = [[], [], []]; // 2. Loop through the image buffer and extract the R, G, and B channels for (let i = 0; i < imageBufferData.length; i += 4) { redArray.push(imageBufferData[i]); greenArray.push(imageBufferData[i + 1]); blueArray.push(imageBufferData[i + 2]); } // 3. Concatenate RGB to transpose [224, 224, 3] -> [3, 224, 224] to a number array const transposedData = redArray.concat(greenArray, blueArray); // 4. Convert to float32 and normalize to 1 const float32Data = new Float32Array(transposedData.map(x => x / 255.0)); // 5. Normalize the data mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] if (normalize) { const mean = [0.485, 0.456, 0.406]; const std = [0.229, 0.224, 0.225]; for (let i = 0; i < float32Data.length; i++) { float32Data[i] = (float32Data[i] - mean[i % 3]) / std[i % 3]; } } // 6. Create a tensor from the float32 data const inputTensor = new ort.Tensor('float32', float32Data, dims); return inputTensor; } function overflowBoxes(box, maxSize) { return [ Math.max(box[0], 0), Math.max(box[1], 0), Math.min(box[2], maxSize - Math.max(box[0], 0)), Math.min(box[3], maxSize - Math.max(box[1], 0)), ]; }; function is_widget_frame() { return document.querySelector('.recaptcha-checkbox'); } function is_image_frame() { return document.querySelector('#rc-imageselect'); } function open_image_frame() { SimulateMouseClick(document.querySelector('#recaptcha-anchor')); } function is_invalid_config() { return document.querySelector('.rc-anchor-error-message'); } function is_rate_limited() { return document.querySelector('.rc-doscaptcha-header'); } function is_solved() { // Note: verify button is disabled after clicking and during transition to the next image task return document.querySelector('.recaptcha-checkbox[aria-checked="true"], #recaptcha-verify-button[disabled]'); } function on_images_ready(timeout = 15000) { return new Promise(async resolve => { const start = Time.time(); while (true) { const $tiles = document.querySelectorAll('.rc-imageselect-tile'); const $loading = document.querySelectorAll('.rc-imageselect-dynamic-selected'); const is_loaded = $tiles.length && !$loading.length; if (is_loaded) { return resolve(true); } if (Time.time() - start > timeout) { return resolve(false); } await Time.sleep(100); } }); } function get_image_url($e) { return $e?.src?.trim(); } async function get_task(task_lines) { let task = null; if (task_lines.length > 1) { task = task_lines.slice(0, 2).join(' '); task = task.replace(/\s+/g, ' ')?.trim(); } else { task = task_lines.join('\n'); } if (!task) { return null; } return task; } let last_urls_hash = null; function on_task_ready(interval = 500) { return new Promise(resolve => { let checking = false; const check_interval = setInterval(async () => { if (checking) { return; } checking = true; const task_lines = document .querySelector('.rc-imageselect-instructions') ?.innerText?.split('\n'); let task = await get_task(task_lines); if (!task) { checking = false; return; } const is_hard = task_lines.length === 3 ? true : false; const $cells = document.querySelectorAll('table tr td'); if ($cells.length !== 9 && $cells.length !== 16) { checking = false; return; } const cells = []; const urls = Array($cells.length).fill(null); let background_url = null; let has_secondary_images = false; let i = 0; for (const $e of $cells) { const $img = $e?.querySelector('img'); if (!$img) { checking = false; return; } const url = get_image_url($img); if (!url || url === '') { checking = false; return; } if ($img.naturalWidth >= 300) { background_url = url; } else if ($img.naturalWidth === 100) { urls[i] = url; has_secondary_images = true; } cells.push($e); i++; } if (has_secondary_images) { background_url = null; } const urls_hash = JSON.stringify([background_url, urls]); if (last_urls_hash === urls_hash) { checking = false; return; } last_urls_hash = urls_hash; clearInterval(check_interval); checking = false; return resolve({ task, is_hard, cells, background_url, urls }); }, i); }); } function submit() { SimulateMouseClick(document.querySelector('#recaptcha-verify-button')); } function reload() { SimulateMouseClick(document.querySelector('#recaptcha-reload-button')); } function got_solve_incorrect() { const errors = [ '.rc-imageselect-incorrect-response', // try again ]; for (const e of errors) { if (document.querySelector(e)?.style.display === '') { return true; } } return false; } function got_solve_error() { // <div aria-live="polite"> // <div class="rc-imageselect-error-select-more" style="" tabindex="0">Please select all matching images.</div> // <div class="rc-imageselect-error-dynamic-more" style="display:none">Please also check the new images.</div> // <div class="rc-imageselect-error-select-something" style="display:none">Please select around the object, or reload if there are none.</div> // </div> const errors = [ '.rc-imageselect-error-select-more', // select all matching images '.rc-imageselect-error-dynamic-more', // please also check the new images '.rc-imageselect-error-select-something', // select around the object or reload ]; for (const e of errors) { const $e = document.querySelector(e); if (!$e?.style.display || !$e?.tabIndex) { return true; } } return false; } function is_cell_selected($cell) { try { return $cell.classList.contains('rc-imageselect-tileselected'); } catch {} return false; } function softmax(x) { const e_x = x.map(Math.exp); const sum_e_x = e_x.reduce((a, b) => a + b, 0); return e_x.map(v => v / sum_e_x); } let was_solved = false; let was_incorrect = false; let solved_urls = []; async function on_widget_frame() { // Check if parent frame marked this frame as visible on screen if (!await query('recaptcha_widget_visible')) { return; } // Wait if already solved if (is_solved()) { if (!was_solved) { was_solved = true; } return; } was_solved = false; await Time.sleep(500); open_image_frame(); } async function on_image_frame() { // Check if parent frame marked this frame as visible on screen if (!await query('recaptcha_image_visible')) { return; } // Wait if verify button or rate limited or invalid config if (is_solved() || is_rate_limited() || is_invalid_config()) { return; } // Incorrect solution if (!was_incorrect && got_solve_incorrect()) { solved_urls = []; was_incorrect = true; } else { was_incorrect = false; } // Select more images error if (got_solve_error()) { solved_urls = []; return reload(); } // Wait for images to load const is_ready = await on_images_ready(); if (!is_ready) { return; } // Wait for task to be available const { task, is_hard, cells, background_url, urls } = await on_task_ready(); const image_urls = []; const n = 4 - (cells.length === 9); let clickable_cells = []; if (background_url === null) { urls.forEach((url, i) => { if (url && !solved_urls.includes(url)) { image_urls.push(url); clickable_cells.push(cells[i]); } }); } else { image_urls.push(background_url); clickable_cells = cells; } const normalizedLabel = { bicycles: 'bicycle', bridges: 'bridge', buses: 'bus', cars: 'car', chimneys: 'chimney', crosswalks: 'crosswalk', fire_hydrants: 'fire_hydrant', motorcycles: 'motorcycle', mountains: 'mountain_or_hill', palm_trees: 'palm_tree', taxis: 'taxi', stairs: 'stair', traffic_lights: 'traffic_light', tractors: 'tractor', vehicles: 'car', }; const modelLabel = [ 'bicycle', 'bridge', 'bus', 'car', 'chimney', 'crosswalk', 'fire_hydrant', 'motorcycle', 'mountain_or_hill', 'palm_tree', 'parking_meter', 'stair', 'taxi', 'tractor', 'traffic_light', ]; const data = Array(16).fill(false); let label = task .match(/(?<=Select all (?:square|image)s with\s+(?:an?\s+)?)(?!an?\s+).*[^ ]/)[0] .replaceAll(' ', '_') .toLowerCase(); label = normalizedLabel[label] || label; const subImages = []; if (!background_url) { for (const url of image_urls) { subImages.push(await Jimp.read(url).then(img => img.opaque())); } } else { const image = await Jimp.read(background_url).then(img => img.opaque()); if (n === 4) { subImages.push(image); } else { const cropSize = image.bitmap.width / n; for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { subImages.push( image.clone().crop({ x: j * cropSize, y: i * cropSize, w: cropSize, h: cropSize, }) ); } } } } if (!subImages.length) return; if (n === 3) { const url = GM_getResourceURL(`${label}.ort`); if (!url) { return reload(); } // Initialize recaptcha detection model const classifierSession = await ort.InferenceSession.create(url); const outputs = {}; for (let i = 0; i < subImages.length; i++) { const subImage = subImages[i]; // Resize image to 224x224 with bilinear interpolation subImage.resize({ w: 224, h: 224, mode: Jimp.RESIZE_BILINEAR }); // Convert image data to tensor const input = imageDataToTensor(subImage, [1, 3, 224, 224]); // Feed feats to classifier const classifierOutputs = await classifierSession.run({ input }); const output = classifierOutputs[classifierSession.outputNames[0]].data; // Find confidence score of output const confidence = softmax(output); outputs[i] = confidence[1]; } // Sort outputs by confidence const sortedOutputs = Object.keys(outputs).sort( (a, b) => outputs[b] - outputs[a] ); let possibleTrue = sortedOutputs.filter(idx => outputs[idx] > 0.7); if (![3, 4].includes(possibleTrue.length) && subImages.length === 9) { // if confidence between 3rd and 4th is smaller than 0.025, then include 4th possibleTrue = sortedOutputs.slice(0, 3 + ( sortedOutputs.length > 3 && outputs[sortedOutputs[2]] - outputs[sortedOutputs[3]] < 0.025 )); } else if ( // Logic for 2nd try at hard recaptcha [3, 4].includes(subImages.length) && !possibleTrue.length ) { possibleTrue = sortedOutputs.filter(idx => outputs[idx] > 0.6); } possibleTrue.forEach(idx => (data[idx] = true)); } else if (n === 4) { const imageSize = 320; // Initialize recaptcha detection model const nmsConfig = new ort.Tensor( 'float32', new Float32Array([10, 0.25, 0.1]) ); const [segmentation, mask, nms] = await Promise.all([ ort.InferenceSession.create(GM_getResourceURL('yolov5-seg.ort')), ort.InferenceSession.create(GM_getResourceURL('mask-yolov5-seg.ort')), ort.InferenceSession.create(GM_getResourceURL('nms-yolov5-det.ort')), ]); const inputImage = subImages[0].resize({w: imageSize, h: imageSize}); const inputTensor = imageDataToTensor( inputImage, [1, 3, imageSize, imageSize], false ); const { output0, output1 } = await segmentation.run({ images: inputTensor, }); const nmsOutput = await nms.run({ detection: output0, config: nmsConfig, }); const selectedIdx = nmsOutput[nms.outputNames[0]]; function hexToRgba(hex, alpha) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? [ parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16), alpha, ] : null; } // looping through output const gridWidth = imageSize / 4; const gridPixel = gridWidth ** 2; for (let i = 0; i < selectedIdx.data.length; i++) { const idx = selectedIdx.data[i]; const numClass = modelLabel.length; const selectedData = output0.data.slice( idx * output0.dims[2], (idx + 1) * output0.dims[2] ); const scores = selectedData.slice(5, 5 + numClass); let score = Math.max(...scores); const labelName = modelLabel[scores.indexOf(score)]; if (labelName !== label) continue; const color = '#FF37C7'; let box = overflowBoxes( [selectedData[0] - 0.5 * selectedData[2], selectedData[1] - 0.5 * selectedData[3], selectedData[2], selectedData[3]], imageSize, ); // Create mask overlay const detectionTensor = new ort.Tensor( 'float32', new Float32Array([...box, ...selectedData.slice(5 + numClass)]) ); const maskConfig = new ort.Tensor( 'float32', new Float32Array([ imageSize, ...box, ...hexToRgba(color, 120), ]) ); const maskOutput = await mask.run({ detection: detectionTensor, mask: output1, config: maskConfig, }); const maskFilter = maskOutput[mask.outputNames[0]]; // Create mask in JIMP const maskImage = new Jimp({ width: imageSize, height: imageSize }); maskImage.bitmap.data = maskFilter.data; // Get how much percentage of mask in each grid const gridMask = Array(16).fill(0); maskImage.scan(0, 0, imageSize, imageSize, function (x, y, idx) { const gridX = Math.floor(x / gridWidth); const gridY = Math.floor(y / gridWidth); if (this.bitmap.data[idx + 3]) { gridMask[(gridY << 2) + gridX] += 1; } }); // Convert to percentage if higher than 0.15 set to true for (let i = 0; i < 16; i++) { const maskPercentage = gridMask[i] / gridPixel; if (maskPercentage > 0.1) { data[i] = true; } } } } let clicks = 0; for (let i = 0; i < data.length; i++) { if (data[i]) { clicks++; // Click if not already selected if (!is_cell_selected(clickable_cells[i])) { SimulateMouseClick(clickable_cells[i]); await Time.sleep(GM_getValue('recaptcha_click_delay_time', 0)); } } } for (const url of urls) { solved_urls.push(url); if (solved_urls.length > 9) { solved_urls.shift(); } } await Time.sleep(GM_getValue('recaptcha_solve_delay_time', 0)); if ( (n === 3 && is_hard && !clicks && (await on_images_ready())) || (n === 3 && !is_hard) || (n === 4 && clicks) ) { await Time.sleep(200); return submit(); } else if (n === 4 && !clicks) { return reload(); } } (async () => { // Modify ort wasm path ort.env.wasm.wasmPaths = Object.fromEntries(await Promise.all(Object.entries({ mjs: 'https://cdn.jsdelivr.net/npm/[email protected]/dist/ort-wasm-simd-threaded.jsep.mjs', wasm: 'https://cdn.jsdelivr.net/npm/[email protected]/dist/ort-wasm-simd-threaded.jsep.wasm', }).map(([key, url]) => new Promise((resolve, reject) => GM_xmlhttpRequest({ method: 'GET', url, responseType: 'blob', onload({ response }) { resolve([key, URL.createObjectURL(response)]); }, onabort: reject, onerror: reject, ontimeout: reject, }))))); while (true) { await Time.sleep(1000); if (is_widget_frame() && GM_getValue('recaptcha_auto_open', false)) { await on_widget_frame(); } else if (is_image_frame() && GM_getValue('recaptcha_auto_solve', true)) { await on_image_frame(); } } })(); }