Загрузка данных
/* Автоматический разгадыватель капчи из 4ёх искажённых картинок на форуме 2ch.su. Установи браузерное расширение по типу Tampermonkey ( https://chromewebstore.google.com/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=ru&pli=1 ) и скопируй скрипт туда. Для смартфона подойдёт Firefox, Kiwi Browser и др.
Код можно скормить нейронке и она подтвердит что он безопасен.
Если есть проблемы при установке или баги, пишите в Github или прямо в комментах к этому посту */
// ==UserScript==
// @name 2ch-autosolver
// @namespace http://tampermonkey.net/
// @version 20.06.2026
// @description См. выше
// @include https://2ch.su/*
// @include https://2ch.org/*
// @include https://2ch.life/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=2ch.su
// @require https://code.jquery.com/jquery-3.1.0.min.js
// @require https://gist.githubusercontent.com/JackOutsideTheBox/ca3d4aec3e1464d26c2f3c19f88d03de/raw/6447d74eccbba9378ccdcc066f73017265c5f1d2/opencvwithsift.js
// @grant none
// ==/UserScript==
/**
* Captcha Autosolver - JavaScript implementation using OpenCV.js
* Performs SIFT-based feature matching to identify candidate icons within captcha images
*/
(async () => {
cv = (cv instanceof Promise) ? await cv : cv;
class KpDesc {
constructor() {
this.keypoints;
this.descriptors;
}
}
class CandidateInfo {
constructor(image) {
this.image = image; // cv.Mat
this.keypoints = new cv.KeyPointVector();
this.descriptors = cv.Mat.zeros(this.image.rows, this.image.cols, cv.CV_32F);
}
}
class CandidateResult {
constructor(info, finalScore, combinedPoints, bestWindowPoints, approved) {
this.info = info; //CandidateInfo
this.finalScore = finalScore;
this.combinedPoints = combinedPoints; // array of { x, y } points
this.bestWindowPoints = bestWindowPoints; // array of { x, y } points
this.approved = approved;
}
}
class PointSet {
constructor() {
this.map = new Map();
}
add(point) {
this.map.set(`${point.x},${point.y}`, point);
}
has(point) {
return this.map.has(`${point.x},${point.y}`);
}
get size() {
return this.map.size;
}
values() {
return this.map.values();
}
[Symbol.iterator]() {
return this.map.values();
}
}
class CaptchaAutosolver {
constructor() {
this.candidateInfos = [];
this.results = new Map(); //img -> CandidateResult
this.originalCaptcha = null; // cv.Mat
this.captchaVariants = [];
// Algorithm parameters
// Array of SIFT detectors with progressively more aggressive settings
this.siftDetectors = [
new cv.SIFT(0, 3, 0.04, 10, 1.2), // default / conservative
new cv.SIFT(0, 5, 0.03, 20, 1.2), // slightly more keypoints
new cv.SIFT(0, 10, 0.02, 30, 1.2), // more aggressive
new cv.SIFT(0, 15, 0.01, 40, 1.2) // most aggressive
];
this.captchaDetector = new cv.SIFT(0, 3, 0.04, 10, 1.6);
this.MAX_SUBREGION_WIDTH = 60;
this.MAX_SUBREGION_HEIGHT = 60;
}
/**
* Clean up captcha variants and keypoint/descriptor data
* @public
*/
cleanup() {
this.results.clear();
for (const variant of this.captchaVariants) {
variant.delete();
}
for (const kpd of this.captchaKpDesc) {
kpd.keypoints.delete();
kpd.descriptors.delete();
}
this.captchaVariants = [];
this.captchaKpDesc = [];
}
/**
* Convert HTMLImageElement to cv.Mat
* @private
* @param {HTMLImageElement} imgElement - HTML image element
* @returns {cv.Mat} The image as cv.Mat
*/
_imgToMat(imgElement) {
const canvas = document.createElement('canvas');
canvas.width = imgElement.width;
canvas.height = imgElement.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(imgElement, 0, 0);
// Create cv.Mat from canvas (returns RGBA)
const mat = cv.imread(canvas);
this._opaqueToWhite(mat);
return mat;
}
/**
* Load captcha image from an HTML img element with data:image URI
* @param {HTMLImageElement|string} imageSource - HTML img element or image element ID containing data:image
*/
loadCaptcha(imageSource) {
let imgElement;
if (typeof imageSource === 'string') {
imgElement = document.getElementById(imageSource);
if (!imgElement) {
console.error(`Image element with ID '${imageSource}' not found`);
return;
}
} else {
imgElement = imageSource;
}
if (!(imgElement instanceof HTMLImageElement)) {
console.error("Source must be an HTMLImageElement or valid element ID");
return;
}
// Convert img element to canvas, then to cv.Mat
const captchaImage = this._imgToMat(imgElement);
if (captchaImage.empty()) {
console.error("Failed to convert image element to cv.Mat");
return;
}
this._preprocessCaptcha(captchaImage);
captchaImage.delete();
}
/**
* Preprocess captcha image and prepare variants for matching
* @private
* @param {cv.Mat} captchaImage - The captcha image to preprocess
*/
_preprocessCaptcha(captchaImage) {
// Store original captcha for later use
this.originalCaptcha = captchaImage.clone();
// Prepare captcha variants: Lab channels and their inversions
// Convert to Lab color space
const reschemedCaptcha = new cv.Mat();
cv.cvtColor(captchaImage, reschemedCaptcha, cv.COLOR_RGB2Lab);
// Split Lab channels
const channels = new cv.MatVector();
cv.split(reschemedCaptcha, channels);
// Apply CLAHE to each channel
const clahe = new cv.CLAHE(5, new cv.Size(4, 4));
for (let i = 0; i < 3; i++) {
const channel = channels.get(i);
const processed = new cv.Mat();
clahe.apply(channel, processed);
// Add channel and its inversion
const inverted = new cv.Mat();
cv.bitwise_not(processed, inverted);
this.captchaVariants.push(processed);
this.captchaVariants.push(inverted);
}
// Add grayscale variants
const greyscaleCaptcha = new cv.Mat();
cv.cvtColor(captchaImage, greyscaleCaptcha, cv.COLOR_BGR2GRAY);
const equalCaptcha = new cv.Mat();
cv.equalizeHist(greyscaleCaptcha, equalCaptcha);
const claheCaptcha = new cv.Mat();
clahe.apply(greyscaleCaptcha, claheCaptcha);
const invGreyscale = new cv.Mat();
cv.bitwise_not(greyscaleCaptcha, invGreyscale);
const invEqualCaptcha = new cv.Mat();
cv.bitwise_not(equalCaptcha, invEqualCaptcha);
const invClaheCaptcha = new cv.Mat();
cv.bitwise_not(claheCaptcha, invClaheCaptcha);
this.captchaVariants.push(greyscaleCaptcha);
this.captchaVariants.push(invGreyscale);
this.captchaVariants.push(equalCaptcha);
this.captchaVariants.push(invEqualCaptcha);
this.captchaVariants.push(claheCaptcha);
this.captchaVariants.push(invClaheCaptcha);
// Extract keypoints and descriptors for all captcha variants
const captchaKpDesc = [];
for (let i = 0; i < this.captchaVariants.length; i++) {
const kp = new KpDesc();
kp.keypoints = new cv.KeyPointVector();
kp.descriptors = cv.Mat.zeros(this.captchaVariants[i].rows, this.captchaVariants[i].cols, cv.CV_32F);
this.captchaDetector.detectAndCompute(this.captchaVariants[i], new cv.Mat(), kp.keypoints, kp.descriptors);
captchaKpDesc.push(kp);
}
this.captchaKpDesc = captchaKpDesc;
// Cleanup temporary mats
reschemedCaptcha.delete();
channels.delete();
}
/**
* Perform feature matching between candidate and captcha variants
* Uses cached results if candidate has been matched before
* @param {cv.Mat} img - The candidate icon
* @param {Array} excludedWindows - Windows to exclude from matching
* @returns {CandidateResult} Result containing match information and best window points
*/
matchCandidate(img, excludedWindows = []) {
const compensatePoorKpCount = (result) => {
const keypointCount = result.info.keypoints.length;
if (keypointCount <= -100) { //turn off
result.finalScore = Math.round((100.0 / keypointCount) * result.finalScore);
}
};
// Check if we've already calculated matches for this candidate
if (this.results.has(img.src)) {
const result = this.results.get(img.src);
const newBestWindow = this._findBestWindow(result.combinedPoints, excludedWindows);
result.bestWindowPoints = newBestWindow;
result.finalScore = newBestWindow.length;
compensatePoorKpCount(result);
return result;
}
let candidateImage = this._imgToMat(img);
// Resize 2x using INTER_CUBIC interpolation
let upscaledCandidateImage = new cv.Mat();
let size = new cv.Size(candidateImage.cols * 2, candidateImage.rows * 2);
cv.resize(candidateImage, upscaledCandidateImage, size, 0, 0, cv.INTER_CUBIC);
const candidateInfo = new CandidateInfo(upscaledCandidateImage);
// Try each SIFT detector with increasing aggressiveness
for (let i = 0; i < this.siftDetectors.length; i++) {
this.siftDetectors[i].detectAndCompute(candidateInfo.image, new cv.Mat(), candidateInfo.keypoints, candidateInfo.descriptors);
if (candidateInfo.keypoints.size() >= 100) {
console.log(`Used SIFT instance ${i} for candidate ${img.src} with ${candidateInfo.keypoints.size()} keypoints.`);
break;
}
}
if (candidateInfo.keypoints.size() < 100) {
console.warn(`Even with maximum precision candidate ${img.src} only got ${candidateInfo.keypoints.size()} keypoints.`);
}
this.candidateInfos.push(candidateInfo);
candidateImage.delete();
const candidateKpts = candidateInfo.keypoints;
const candidateDpts = candidateInfo.descriptors;
const combinedPointsSet = new PointSet();
for (let i = 0; i < this.captchaVariants.length; i++) {
const captchaKpts = this.captchaKpDesc[i].keypoints;
const captchaDpts = this.captchaKpDesc[i].descriptors;
if (!captchaDpts || captchaDpts.empty() || !candidateDpts || candidateDpts.empty()) {
continue;
}
// Perform KNN matching with k=2
const loweMatches = [];
const matcher = new cv.BFMatcher(cv.NORM_L2, false);
const knnMatches = new cv.DMatchVectorVector();
matcher.knnMatch(candidateDpts, captchaDpts, knnMatches, 2);
// Apply Lowe's ratio test
for (let j = 0; j < knnMatches.size(); j++) {
const matches = knnMatches.get(j);
if (matches.size() === 2 && matches.get(0).distance < 0.8 * matches.get(1).distance) {
loweMatches.push(matches.get(0));
}
}
const uniquePoints = new PointSet();
if (loweMatches.length >= 4) {
// Use RANSAC to estimate affine transformation
const candidatePts = [];
const captchaPts = [];
for (const m of loweMatches) {
candidatePts.push(candidateKpts.get(m.queryIdx).pt);
captchaPts.push(captchaKpts.get(m.trainIdx).pt);
}
// Convert to cv.Mat format for estimateAffineTransform
const srcMat = cv.matFromArray(candidatePts.length, 1, cv.CV_32FC2, candidatePts.flatMap(p => [p.x, p.y]));
const dstMat = cv.matFromArray(captchaPts.length, 1, cv.CV_32FC2, captchaPts.flatMap(p => [p.x, p.y]));
const inliersMask = new cv.Mat();
const affineMatrix = cv.estimateAffine2D(srcMat, dstMat, inliersMask, cv.RANSAC, 10.0);
// Collect inlier points
for (let j = 0; j < loweMatches.length; j++) {
if (inliersMask.ucharPtr(j)[0]) {
const point = captchaKpts.get(loweMatches[j].trainIdx).pt;
uniquePoints.add(point);
}
}
srcMat.delete();
dstMat.delete();
inliersMask.delete();
affineMatrix?.delete();
} else {
// If fewer than 4 matches, collect all points
for (const m of loweMatches) {
const point = captchaKpts.get(m.trainIdx).pt;
uniquePoints.add(point);
}
}
// Filter applicable subregion and add to combined points
if (uniquePoints.size > 0) {
const pointsArray = Array.from(uniquePoints);
const [isApplicable, filteredPoints] = this._isApplicableSubregion(pointsArray);
for (const pt of filteredPoints) {
combinedPointsSet.add(pt);
}
}
matcher.delete();
knnMatches.delete();
}
// Convert combined points to array
const combinedPoints = Array.from(combinedPointsSet);
// Find best window without exclusions
const bestWindowPoints = this._findBestWindow(combinedPoints, excludedWindows);
const result = new CandidateResult(
candidateInfo,
bestWindowPoints.length,
combinedPoints,
bestWindowPoints,
false
);
compensatePoorKpCount(result);
this.results.set(img.src, result);
return result;
}
/**
* Check if a subregion is applicable (has at least 2 points in best window)
* @private
*/
_isApplicableSubregion(regionPoints) {
if (regionPoints.length < 2) {
return [false, []];
}
const bestWindowPoints = this._findBestWindow(regionPoints);
if (bestWindowPoints.length >= 2) {
return [true, bestWindowPoints];
}
return [false, []];
}
/**
* Find best window for maximum point density
* @private
*/
_findBestWindow(combinedPoints, excludedWindows = []) {
let filteredCombinedPoints = [];
if (excludedWindows.length === 0) {
filteredCombinedPoints = [...combinedPoints];
} else {
// Filter out points inside excluded rectangles
for (const pt of combinedPoints) {
let excluded = false;
for (const r of excludedWindows) {
if (pt.x >= r.x && pt.x < r.x + r.width &&
pt.y >= r.y && pt.y < r.y + r.height) {
excluded = true;
break;
}
}
if (!excluded) {
filteredCombinedPoints.push(pt);
}
}
}
if (filteredCombinedPoints.length === 0) {
return [];
}
// Find bounding box of all points
const ptsMat = cv.matFromArray(filteredCombinedPoints.length, 1, cv.CV_32FC2, filteredCombinedPoints.flatMap(p => [p.x, p.y]));
const boundingBox = cv.boundingRect(ptsMat);
ptsMat.delete();
// Sliding window search
let bestScore = 0;
let bestWindow = new cv.Rect(0, 0, this.MAX_SUBREGION_WIDTH, this.MAX_SUBREGION_HEIGHT);
const stepX = Math.floor(this.MAX_SUBREGION_WIDTH / 3);
const stepY = Math.floor(this.MAX_SUBREGION_HEIGHT / 3);
// Search within and beyond bounding box
for (let x = boundingBox.x - this.MAX_SUBREGION_WIDTH; x < boundingBox.x + boundingBox.width; x += stepX) {
for (let y = boundingBox.y - this.MAX_SUBREGION_HEIGHT; y < boundingBox.y + boundingBox.height; y += stepY) {
const currentWindow = new cv.Rect(x, y, this.MAX_SUBREGION_WIDTH, this.MAX_SUBREGION_HEIGHT);
// Count points within this window
let pointsInWindow = 0;
for (const pt of filteredCombinedPoints) {
if (pt.x >= currentWindow.x && pt.x < currentWindow.x + currentWindow.width &&
pt.y >= currentWindow.y && pt.y < currentWindow.y + currentWindow.height) {
pointsInWindow++;
}
}
// Update best window if this covers more points
if (pointsInWindow > bestScore) {
bestScore = pointsInWindow;
bestWindow = currentWindow;
}
}
}
// Extract points covered by best window
const bestWindowPoints = [];
for (const pt of filteredCombinedPoints) {
if (pt.x >= bestWindow.x && pt.x < bestWindow.x + bestWindow.width &&
pt.y >= bestWindow.y && pt.y < bestWindow.y + bestWindow.height) {
bestWindowPoints.push(pt);
}
}
return bestWindowPoints;
}
/**
* Convert semi-transparent pixels to white
* @private
*/
_opaqueToWhite(img) {
const data = img.data;
const rows = img.rows;
const cols = img.cols;
const channels = img.channels();
if (channels === 4) {
// RGBA format
for (let i = 0; i < rows * cols * 4; i += 4) {
const alpha = data[i + 3];
if (alpha < 100) {
data[i] = 255; // Blue
data[i + 1] = 255; // Green
data[i + 2] = 255; // Red
data[i + 3] = 255; // Alpha
}
}
}
}
}
$('._captcha').prepend(`
<div class="captcha__autosolver-wrapper">
<div class="captcha__autosolver">
<div class="options__box">
<input type="checkbox" js-target-autosolve id="autosolve">
<label for="autosolve"><span title="Заработает со следующей капчи">Автоотгадывание капчи</span></label>
</div>
<div class="options__box" style="visibility: hidden">
<input type="checkbox" js-target-autoresend id="autoresend">
<label for="autoresend"><span title="Отгадывать, пока не отправится">Автопереотправка при неудаче</span></label>
</div>
<div class="options__box" style="visibility: hidden">
<input type="checkbox" js-target-hidecaptcha id="hidecaptcha">
<label for="hidecaptcha"><span title="">Скрыть капчу</span></label>
</div>
</div>
</div>`).first().prepend(`
<style id="autosolver-style">
.captcha__autosolver-wrapper {
margin-bottom: 20px;
}
.captcha__autosolver {
margin: 10px auto -10px auto;
width: fit-content;
scale: 1.2;
user-select: none;
}
.captcha__autosolver input:checked,
.captcha__autosolver input:checked ~ label {
font-weight: 700;
color: var(--theme_default_link);
}
.captcha__scanner {
width: 5px;
background-color: green;
opacity: 0.9;
position: absolute;
z-index: 2;
box-shadow: 0em 0em 1em 6px green;
}
.captcha__hidden {
display: none !important;
}
</style>`);
const captchaObserver = new MutationObserver(mutationsList => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => setTimeout(() => processNode(node), 100)); //workaround for firefox
}
}
}
);
const responseObserver = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('alert')) {
const text = node.textContent.toLowerCase();
if (text.includes('успешно')) {
successCount++;
pendingResend = false;
} else if (text.includes('невалидна')) {
failCount++;
if (autoresend) {
pendingResend = true;
console.log('Pending resend flagged due to failed operation.');
}
}
showStats();
saveState();
// Persist updated config
console.log(`Success: ${successCount}, total: ${successCount + failCount}`);
}
}
);
}
);
}
);
function turnSolverOn() {
captchaObserver.observe(document.querySelector('._captcha-content'), {
childList: true,
subtree: true
});
responseObserver.observe(document.getElementById('js-posts'), {
childList: true,
subtree: true
});
}
function turnSolverOff() {
captchaObserver.disconnect();
responseObserver.disconnect();
$('.captcha__scanner').remove();
}
function loadState() {
const state = localStorage.getItem('autosolverOptions');
if (state) {
try {
return JSON.parse(state);
} catch (e) {
console.warn('Failed to parse saved captcha options', e);
}
}
return {
autosolve: false,
autoresend: false,
hidecaptcha: false,
successCount: 0,
failCount: 0
};
}
let { autosolve, autoresend, hidecaptcha, successCount, failCount } = loadState();
successCount = parseInt(successCount);
failCount = parseInt(failCount);
let totalCount = () => successCount + failCount;
let percentage = () => totalCount() === 0 ? 0 : Math.floor(successCount / totalCount() * 100);
let pendingResend;
let prevCaptcha = '';
let solver;
let chosenCandidates = [];
// Refinement: iteratively exclude regions and recompute
let excludedWindows = [];
function statistics() {
return `Авто-отгадано капч: ${successCount}/${totalCount()} (${percentage()}%)`;
}
function showStats() {
$('#captcha-stats').text(statistics());
}
$('.header__opts').append(`<a id="captcha-stats-reset" class="header__menuitem" href="#" style="font-size: smaller; margin-left: auto;">[сброс]</a><div class="header__menuitem" id="captcha-stats">${statistics()}</div>`);
function saveState() {
localStorage.setItem('autosolverOptions', JSON.stringify({
autosolve,
autoresend,
hidecaptcha,
successCount,
failCount
}));
}
$('#captcha-stats-reset').on('click', function () {
successCount = failCount = 0;
showStats();
saveState();
});
$('[id=autosolve]').on('change', function () {
autosolve = $(this).prop('checked');
const $additionals = $('[id=autoresend], [id=hidecaptcha]');
if (autosolve) {
$additionals.closest('.options__box').css('visibility', 'visible');
turnSolverOn();
processNode(document.querySelector('._captcha-content'), true);
} else {
turnSolverOff();
$additionals.prop('checked', false).trigger('change'); //needs explicit call to react to checkbox state change
$additionals.closest('.options__box').css('visibility', 'hidden');
}
saveState();
});
$('[id=autoresend]').on('change', function () {
autoresend = $(this).prop('checked');
saveState();
});
$('[id=hidecaptcha]').on('change', function () {
hidecaptcha = $(this).prop('checked');
if (hidecaptcha) {
$('#autosolver-style').after('<style id="captcha-hider">._captcha-wrapper{display: none !important;}</style>');
} else {
$('[id=captcha-hider]').remove();
}
saveState();
});
if (autosolve)
$('[id=autosolve]').click();
if (autoresend)
$('[id=autoresend]').click();
if (hidecaptcha)
$('[id=hidecaptcha]').click();
// sync other instances
$('.captcha__autosolver input').click(event => {
const id = event.target.id;
const isChecked = $(event.target).prop('checked');
$(`input[id="${id}"]`).prop('checked', isChecked);
}
);
window.addEventListener('storage', (event) => {
if (event.key === 'autosolverOptions') {
({ autosolve, autoresend, hidecaptcha, successCount, failCount } = loadState());
showStats();
console.log('Solver options synced from another tab');
}
});
function yieldToEventLoop() {
return new Promise(resolve => setTimeout(resolve, 0));
}
//_captcha-keyboard-selected-icon
async function processNode(node, force = false) {
// Ensure it's an element
if (node.nodeType === 1) {
const captcha = node.querySelector('._captcha-image');
const candidates = Array.from(node.querySelectorAll('._captcha-keyboard-button'));
const success = node.querySelector('.captcha__msg-success-text');
const error = node.querySelector('.captcha__msg-error');
if (captcha && (captcha.src !== prevCaptcha || force)) {
excludedWindows = [];
$('.captcha__scanner').remove();
console.log('New captcha image arrived:', captcha.src);
chosenCandidates = Array.from(node.querySelectorAll('._captcha-keyboard-selected-icon')).map(chosen => chosen.src);
// Iterate over each outmost container
if (!hidecaptcha && candidates.length > 0) {
$('._captcha').each(async function () {
const $outmost = $(this);
const $img = $outmost.find('._captcha-image');
// Calculate the topOffset between captcha and outmost container
const captchaRect = $img[0].getBoundingClientRect();
const containerRect = $outmost[0].getBoundingClientRect();
const topOffset = Math.abs(captchaRect.top - containerRect.top);
const height = $img[0].height;
const scanner = document.createElement('div');
scanner.className = 'captcha__scanner';
scanner.style.top = `${topOffset}px`;
scanner.style.height = `${height}px`;
scanner.animate([{
transform: `translateX(${captchaRect.left - containerRect.left}px)`,
easing: "ease-in-out"
}, {
transform: `translateX(${captchaRect.right - containerRect.left}px)`,
easing: "ease-in-out"
}, {
transform: `translateX(${captchaRect.left - containerRect.left}px)`,
easing: "ease-in-out"
}], {
duration: 3000,
iterations: Infinity,
});
$outmost.prepend(scanner);
await yieldToEventLoop();
});
}
await yieldToEventLoop();
solver?.cleanup();
solver = new CaptchaAutosolver();
solver.loadCaptcha(captcha);
prevCaptcha = captcha.src;
}
await yieldToEventLoop();
if (candidates.length > 0) {
console.log('In process of solving captcha');
// Calculate match counts and pick the best candidate
let indexOfBest = -1;
let bestCandidate = null;
let bestResult = null;
let bestScore = -1;
for (let i = 0; i < candidates.length; i++) {
const img = candidates[i].querySelector('img');
const result = solver.matchCandidate(img, excludedWindows);
await yieldToEventLoop();
if (result.finalScore > bestScore) {
bestResult = result;
bestScore = result.finalScore;
bestCandidate = candidates[i];
indexOfBest = i;
}
}
bestResult.approved = true;
// Create exclusion rectangle from best window points
const ptsMat = cv.matFromArray(bestResult.bestWindowPoints.length, 1, cv.CV_32FC2, bestResult.bestWindowPoints.flatMap(p => [p.x, p.y]));
const bb = cv.boundingRect(ptsMat);
ptsMat.delete();
excludedWindows.push(new cv.Rect(bb.x, bb.y, bb.width, bb.height));
let bestCandidateData = bestCandidate.querySelector('img').src;
chosenCandidates.push(bestCandidateData);
console.log(`Clicking candidate ${indexOfBest} = ${bestCandidateData} with highest match count ${bestScore}`);
bestCandidate.click();
} else if (success && !force) {
turnSolverOff();
let chosenCandidatesIcons = `
<div class="_captcha-container">
<div class="_captcha-wrapper">
<div class="_captcha-keyboard-selected-list">
${chosenCandidates.map(src => `
<div class="_captcha-keyboard-selected-item" type="button">
<img class="_captcha-keyboard-selected-icon" src="${src}">
</div>`).join('')}
</div>
<div class="_captcha-image-container">
<img js-target-image="" class="_captcha-image" src="${prevCaptcha}">
</div>
</div>
</div>`;
$('._captcha-content').prepend(chosenCandidatesIcons);
console.log('Solving finished');
if (pendingResend) {
const submitId = document.getElementById('qr-postform').checkVisibility() ? 'qr-submit' : 'submit';
document.getElementById(submitId).click();
}
turnSolverOn();
} else if (error) {
$('.captcha__scanner').remove();
}
}
}
})()