Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /* Автоматический разгадыватель капчи из 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);
- // Convert RGBA to BGR (OpenCV standard)
- /* const bgrMat = cv.Mat.zeros(mat.rows, mat.cols, cv.CV_8UC3);
- cv.cvtColor(mat, bgrMat, cv.COLOR_RGBA2BGR);
- //mat.delete();
- return bgrMat; */
- 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);
- totalCount = () => successCount + failCount;
- percentage = () => 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();
- }
- }
- }
- function normalizeThreadUrl(url) {
- url = url.trim();
- if (!url.startsWith(location.origin)) {
- url = location.origin + url;
- }
- return url.replace(/\/arch\/[^/]+\//, "/").replace(/#.*$/, "");
- }
- function arhivachLink(url = null) {
- return `https://arhivach.vc/ajax/?act=locate_thread&url=${normalizeThreadUrl(url ?? location.href)}`;
- }
- if (location.href.includes('/arch/') &&
- document.querySelector('.nf__nf')?.textContent.trim() === '404') {
- location.replace(arhivachLink());
- }
- function watchAndAlertForDeletedThread() {
- let deletionAlerted = false;
- const check = () => {
- if (deletionAlerted) return;
- const favicon = document.querySelector(
- 'link[rel*="icon"]'
- )?.href;
- if (favicon?.includes('favicon_deleted.ico')) {
- deletionAlerted = true;
- $alert(`<b>Тред удалён, но можно <a href="${arhivachLink()}">поискать в Архиваче</a>.</b>`, 'wait');
- }
- };
- check();
- new MutationObserver(check).observe(document.head, {
- subtree: true,
- childList: true,
- attributes: true,
- attributeFilter: ['href']
- });
- }
- watchAndAlertForDeletedThread();
- const favs = document.getElementById('favorites-table');
- const favThreadSelector = '.bb__row';
- function addArhivachLinkToFavoritedThread(row) {
- const $row = $(row);
- const href = $row.find('a.fav__title').first().attr('href');
- $('<a>', {
- href: arhivachLink(href),
- title: 'Найти в Архиваче',
- target: '_blank',
- rel: 'noopener noreferrer'
- })
- .append(`
- <svg xmlns="http://www.w3.org/2000/svg" class="icon fav__fa fav__fa_type_findimg">
- <use xlink:href="#icon__findimg"></use>
- </svg>`)
- .insertAfter($row.find('svg').last());
- }
- const favsObserver = new MutationObserver(mutations => {
- mutations.forEach(mutation => {
- mutation.addedNodes.forEach(function() {
- for (const node of mutation.addedNodes) {
- if (!(node instanceof Element)) continue;
- if (node.matches(favThreadSelector)) {
- addArhivachLinkToFavoritedThread(node);
- } else {
- node.querySelectorAll?.(favThreadSelector).forEach(addArhivachLinkToFavoritedThread);
- }
- }
- });
- });
- });
- favsObserver.observe(favs, {
- childList: true,
- subtree: true
- });
- // Optional: process existing rows
- $(favs).find(favThreadSelector).each(function () {
- addArhivachLinkToFavoritedThread(this);
- });
- })()
Advertisement
Comments
-
- https://tgach.top/b/res/367384.html
- тред
-
- На всякий случай, я -автор скрипта - к этой борде отношения не имею, рекламой её не занимаюсь, просто нужно было безопасное место для обсуждения сабжа
Add Comment
Please, Sign In to add comment