网页通用验证码

解放眼睛和双手,自动识别并填入数字,字母验证码。新版本支持识别滑动验证码。

< Valutazione su 网页通用验证码

Domanda/commento

§
Pubblicato: 14/05/2022

http://www.ttshitu.com/login.html?spm=null
这个网站上的不会识别,定位也不行

husky666Autore
§
Pubblicato: 17/05/2022

使用最新代码即可: // ==UserScript== // @name 网页通用验证码识别 // @namespace http://tampermonkey.net/ // @version 3.0. // @description 解放眼睛和双手,自动识别并填入数字,字母验证码。新版本支持识别滑动验证码。 // @author 哈士奇

// @include http://* // @include https://* // @license MIT

// @grant unsafeWindow // @grant GMaddStyle // @grant GMlistValues // @grant GMaddValueChangeListener // @grant GMremoveValueChangeListener // @grant GMsetValue // @grant GMgetValue // @grant GMdeleteValue // @grant GMlog // @grant GMgetResourceText // @grant GMgetResourceURL // @grant GMregisterMenuCommand // @grant GMunregisterMenuCommand // @grant GMxmlhttpRequest // @grant GMdownload // @grant GMgetTab // @grant GMsaveTab // @grant GMgetTabs // @grant GMnotification // @grant GMsetClipboard // @grant GMinfo // @grant GM_xmlhttpRequest // @connect * // @require https://cdn.jsdelivr.net/npm/vue@2.6.12 // @require https://unpkg.com/element-ui/lib/index.js // @resource elementUIcss https://unpkg.com/element-ui/lib/theme-chalk/index.css

// @run-at document-end // ==/UserScript==

(function() { // GMsetValue('tipsConfig',"") var elementUIcss = GMgetResourceText("elementUIcss"); GM_addStyle(elementUIcss);

function getStyle(el) {
    // 获取元素样式
    if (window.getComputedStyle) {
        return window.getComputedStyle(el, null);
    } else {
        return el.currentStyle;
    }
}

function init() {
    //简化各种api和初始化全局变量
    CUR_URL = window.location.href;
    DOMAIN = CUR_URL.split("//")[1].split("/")[0];
    SLIDE_STORE_KEY = "husky_" + "slidePath" + location.host;
    NORMAL_STORE_KEY = "husky_" + "normalPath" + location.host;
    selector = document.querySelector.bind(document);
    selectorAll = document.querySelectorAll.bind(document);
    getItem = localStorage.getItem.bind(localStorage);
    setItem = localStorage.setItem.bind(localStorage);
}

function getNumber(str) {
    return Number(str.split(".")[0].replace(/[^0-9]/gi, ""));
}

function isNumber(value) {
    if (!value && value !== 0) {
        return false;
    }
    value = Number(value);
    return typeof value === "number" && !isNaN(value);
}

function getEleTransform(el) {
    const style = window.getComputedStyle(el, null);
    var transform =
        style.getPropertyValue("-webkit-transform") ||
        style.getPropertyValue("-moz-transform") ||
        style.getPropertyValue("-ms-transform") ||
        style.getPropertyValue("-o-transform") ||
        style.getPropertyValue("transform") ||
        "null";
    return transform && transform.split(",")[4];
}

class Captcha {
    // 识别网页中的验证码
    constructor() {
        this.imgCache = [];
        this.inputTags = [];
        this.recommendPath = {};
        this.checkTimer = null;
        this.listenLoadSuccess = false;

        window.addEventListener("load", async() => {
            this.listenLoadSuccess = true;
            this.init();
        });
        setTimeout(() => {
            if (!this.listenLoadSuccess) {
                this.listenLoadSuccess = true;
                this.init();
            }
        }, 5000);
    }

    doCheckTask() {
        this.findCaptcha();
        this.checkSlideCaptcha();
    }
    init() {
        if (blackListCheck()) {
            return;
        }
        this.manualLocateCaptcha();
        this.doCheckTask();

        const MutationObserver =
            window.MutationObserver ||
            window.WebKitMutationObserver ||
            window.MozMutationObserver;
        const body = document.body;

        const Observer = new MutationObserver((mutations, instance) => {
            if (blackListCheck()) {
                return;
            }
            for (let i = 0; i < mutations.length; i++) {
                const el = mutations[i].target;
                const tagName = mutations[i].target.tagName.toLowerCase();
                let checkList = [];
                checkList.push(el.getAttribute("id"));
                checkList.push(el.className);
                checkList.push(el.getAttribute("alt"));
                checkList.push(el.getAttribute("src"));
                checkList.push(el.getAttribute("name"));
                checkList = checkList.filter((item) => item);

                for (let x = 0; x < checkList.length; x++) {
                    if (
                        /.*(code|aptcha|验证码|login|点击|verify|yzm|yanzhengma|滑块|拖动|拼图|yidun|slide).*/im.test(
                            checkList[x].toString().toLowerCase()
                        ) ||
                        tagName === "img" ||
                        tagName === "iframe"
                    ) {
                        if (!this.checkTimer) {
                            this.checkTimer = setTimeout(() => {
                                this.doCheckTask();
                            }, 0);
                        } else {
                            window.clearTimeout(this.checkTimer);
                            this.checkTimer = setTimeout(() => {
                                this.doCheckTask();
                            }, 2000);
                        }
                        return;
                    }
                }
            }
        });
        Observer.observe(body, {
            childList: true,
            subtree: true,
            attributes: true,
        });
    }
    setCaptchaCode(el, pwd) {
        function trigger(element, eventName) {
            var event = document.createEvent("HTMLEvents");
            event.initEvent(eventName, true, true);
            element.dispatchEvent(event);
        }
        if (el.tagName.toLowerCase() === "input") {
            el.value = pwd;
            if (typeof InputEvent !== "undefined") {
                el.dispatchEvent(new InputEvent("input")); //模拟事件
                trigger(el, "change");
                trigger(el, "blur");
                trigger(el, "focus");
                trigger(el, "keypress");
                trigger(el, "keydown");
                trigger(el, "keyup");
                trigger(el, "select");
            } else {
                el.dispatchEvent(new KeyboardEvent("input"));
            }
        }
    }
    dataURLtoFile(dataURL, filename = "captcha.jpg") {
        //  base64转图片文件
        let arr = dataURL.split(",");
        let mime =
            (arr[0].match(/:(.*?);/) && arr[0].match(/:(.*?);/)[1]) || "image/png";
        let bstr = "";
        try {
            // 解码异常case: https://www.blyoo.com/4941.html
            bstr = atob(arr[1]);
        } catch (error) {
            bstr = atob(decodeURI(arr[1]));
        }
        let n = bstr.length;
        let u8arr = new Uint8Array(n);
        while (n--) {
            u8arr[n] = bstr.charCodeAt(n);
        }
        return new File([u8arr], filename, { type: mime });
    }
    async getRecommendPath() {
        let requestUrl =
            "http://101.43.206.185:7000/cssPath?href=" +
            location.href.split("?")[0];
        try {
            GM_xmlhttpRequest({
                method: "get",
                url: requestUrl,
                onload: async(res) => {
                    if (res.status === 200 && res.response) {
                        let data = (res.response && JSON.parse(res.response)) || {};
                        const { path, recommendTimes = 0 } = data;
                        if (path && recommendTimes) {
                            let inputSelector = path.split("$$")[0];
                            let imgSelector = path.split("$$")[1];
                            if (
                                selector(inputSelector) &&
                                selector(imgSelector) &&
                                selector(imgSelector).getAttribute("src") &&
                                selector(inputSelector).getAttribute("type") === "text"
                            ) {
                                let dataURL = await this.handleImg(selector(imgSelector));
                                try {
                                    if (!this.hasRequest(dataURL, { record: true })) {
                                        let code = await this.request(
                                            this.dataURLtoFile(dataURL),
                                            this.cssPath(selector(inputSelector)) +
                                            "$$" +
                                            this.cssPath(selector(imgSelector)),
                                            selector(imgSelector).getAttribute("src")
                                        );
                                        if (code) {
                                            this.setCaptchaCode(selector(inputSelector), code);

                                            if (typeof Vue !== "undefined") {
                                                new Vue().$message.success("获取验证码成功");
                                            }
                                            console.log("正在使用共享验证码功能获取验证码");
                                        } else {
                                            console.error("验证码为空,请检查图片是否正确");
                                        }
                                    }
                                } catch (error) {
                                    console.log(error);
                                    // if (typeof Vue !== "undefined") {
                                    //     new Vue().$message.error("获取验证码失败");
                                    // }
                                }
                            }
                        }
                    }
                },
                onerror: function(err) {
                    console.log("推荐路径请求失败:" + err);
                },
            });
        } catch (error) {
            console.log(error);
        }
    }
    getCaptchaFeature(el) {
        // 获取验证码特征
        let checkList = [];
        checkList.push(el.getAttribute("id"));
        checkList.push(el.className);
        checkList.push(el.getAttribute("alt"));
        checkList.push(el.getAttribute("src"));
        checkList.push(el.getAttribute("name"));

        return checkList;
    }
    cssPath = (el) => {
        // 获取元素css path
        if (!(el instanceof Element)) return;
        var path = [];
        while (el.nodeType === Node.ELEMENT_NODE) {
            var selector = el.nodeName.toLowerCase();
            if (el.id) {
                selector += "#" + el.id;
                path.unshift(selector);
                break;
            } else {
                var sib = el,
                    nth = 1;
                while ((sib = sib.previousElementSibling)) {
                    if (sib.nodeName.toLowerCase() == selector) nth++;
                }
                if (nth != 1) selector += ":nth-of-type(" + nth + ")";
            }
            path.unshift(selector);
            el = el.parentNode;
        }
        return path.join(" > ");
    };

    manualLocateCaptcha() {
        let imgs = [];
        let inputTags = [];
        let cssPathStore = {};
        let finish = false;
        this.vue = new Vue();
        this.isIframe = top !== self;
        var onTagClick = (e) => {
            let el = e.target;
            let tagName = el.tagName;
            if (tagName.toLowerCase() === "input") {
                let type = el.getAttribute("type");
                if (type && type !== "text") {
                    this.vue.$message.error(
                        "提醒:当前点击输入框type=" + type + ",请选择文本输入框"
                    );
                } else {
                    cssPathStore.input = this.cssPath(el);
                    this.vue.$message.success("您已成功选择输入框");
                }
            } else {
                cssPathStore.img = this.cssPath(el);
                this.vue.$message.success("您已成功选择验证码图片");
            }
            if (cssPathStore.input && cssPathStore.img) {
                GM_setValue(NORMAL_STORE_KEY, JSON.stringify(cssPathStore));
                imgs.forEach((img) => {
                    img && img.removeEventListener("click", onTagClick);
                }, false);
                inputTags.forEach((input) => {
                    input.removeEventListener("click", onTagClick);
                }, false);
                setTimeout(() => {
                    this.vue.$message.success("选择完毕,赶快试试吧");
                    captchaInstance.doCheckTask();
                }, 3000);
                finish = true;
            }
        };
        var onMenuClick = (e) => {
            if (this.isIframe) {
                alert("当前脚本处于iframe中,暂不支持该操作,快让作者优化吧");
                return;
            }
            finish = false;
            cssPathStore = {};
            GM_deleteValue(NORMAL_STORE_KEY);
            this.vue.$alert("接下来请点击验证码图片和输入框", "操作提示", {
                confirmButtonText: "确定",
                callback: () => {
                    // setTimeout(() => {
                    //     imgs.forEach((img) => {
                    //         img && img.removeEventListener("click", onTagClick);
                    //     }, false);
                    //     inputTags.forEach((input) => {
                    //         input.removeEventListener("click", onTagClick);
                    //     }, false);
                    //     if (!finish) {
                    //         this.vue.$notify.success({
                    //             title: "提示",
                    //             message: "已退出手动选择验证码模式。",
                    //             offset: 100,
                    //         });
                    //     }
                    // }, 20000);
                },
            });
            setTimeout(() => {
                imgs.forEach((img) => {
                    img && img.removeEventListener("click", onTagClick);
                }, false);
                inputTags.forEach((input) => {
                    input.removeEventListener("click", onTagClick);
                }, false);
                if (!finish) {
                    this.vue.$notify.success({
                        title: "提示",
                        message: "已退出手动选择验证码模式。",
                        offset: 100,
                    });
                }
            }, 20000);

            // alert("请点击验证码和输入框各一次。");
            imgs = [...selectorAll("img")];
            inputTags = [...selectorAll("input")];
            imgs.forEach((img) => {
                img.addEventListener("click", onTagClick);
            }, false);
            inputTags.forEach((input) => {
                input.addEventListener("click", onTagClick);
            }, false);
        };
        GM_registerMenuCommand("手动选择验证码和输入框", onMenuClick);
    }
    handleImg(img) {
        return new Promise((resolve, reject) => {
            try {
                // 图片没设置跨域,可采用图片转canvas转base64的方式
                let dataURL = null;

                const action = () => {
                    let canvas = document.createElement("canvas");
                    canvas.width = img.naturalWidth;
                    canvas.height = img.naturalHeight;
                    let ctx = canvas.getContext("2d");
                    ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight);
                    dataURL = canvas.toDataURL("image/png");
                    resolve(dataURL);
                };
                if (!img.src.includes(";base64,")) {
                    img.onload = function() {
                        action();
                    };
                    if (img.complete) {
                        action();
                    } else {
                        img.onload = function() {
                            action();
                        };
                    }
                } else {
                    dataURL = img.src;
                    resolve(dataURL);
                }
            } catch (error) {
                console.error("error:" + error);
                // 这块处理比较复杂,待优化
                // 图片设置跨域,重新请求图片内容后转base64,相当于替用户点击了“换一张图片”
                // if (this.times >= 1) {
                //   return;
                // }
                // if (typeof Vue !== "undefined") {
                //     new Vue().$notify.success({
                //         title: "温馨提示",
                //         message: "当前验证码结果可能和图片显示不一致,请放心提交。",
                //         offset: 100,
                //     });
                // }

                // this.times++;
                // GM_xmlhttpRequest({
                //     method: "get",
                //     url: img.src,
                //     responseType: "blob",
                //     onload: (res) => {
                //         if (res.status === 200) {
                //             let blob = res.response;
                //             let fileReader = new FileReader();
                //             fileReader.onloadend = (e) => {
                //                 let base64 = e.target.result;
                //                 resolve(base64);
                //             };
                //             fileReader.readAsDataURL(blob);
                //         } else {
                //             console.log("图片转换blob失败");
                //             console.log(res);
                //             reject();
                //         }
                //     },
                //     onerror: function(err) {
                //         console.log("图片请求失败:" + err);
                //         reject();
                //     },
                // });
            }
        });
    }
    hasRequest(dataURL, config = {}) {
        let startIndex = config.type === "url" ? 0 : dataURL.length - 100;
        let imgClips = dataURL.slice(startIndex, dataURL.length);
        if (this.imgCache.includes(imgClips)) {
            return true;
        }
        if (config.record) {
            this.imgCache.push(imgClips);
        }
        return false;
    }
    request(file, path, src) {
        try {
            if (!file) {
                console.error("缺少file参数");
                return Promise.reject();
            }

            return new Promise((resolve, reject) => {
                let host = location.href;
                let href = location.href.split("?")[0].split("#")[0];
                if (self === top) {
                    host = location.host;
                }
                let formData = new FormData();
                let detail = {
                    path,
                    src,
                    host,
                    href,
                };
                formData.append("img", file);
                formData.append("detail", JSON.stringify(detail));
                formData.append("whatever", GM_getValue('whatever'));
                // let requestUrl = "http://192.168.31.184:7000/captcha";
                let requestUrl = "http://101.43.206.185:7000/captcha";
                GM_xmlhttpRequest({
                    method: "post",
                    url: requestUrl,

                    data: formData,
                    onload: function(response) {
                        if (response.status === -1) {
                            console.error("获取验证码失败:" + response);
                            reject();
                        } else {
                            let data = response.response;
                            if (data.length < 50) {
                                data = JSON.parse(data);
                                if (data.code) {
                                    resolve(data.code);
                                } else {
                                    let date = new Date().getDate();
                                    let tipsConfig = {
                                        date,
                                        times: 1,
                                    };
                                    let cache =
                                        GM_getValue("tipsConfig") &&
                                        JSON.parse(GM_getValue("tipsConfig"));
                                    if (cache && cache.times > 3) {} else {
                                        if (!cache) {
                                            GM_setValue("tipsConfig", JSON.stringify(tipsConfig));
                                        } else {
                                            cache.times = cache.times + 1;
                                            GM_setValue("tipsConfig", JSON.stringify(cache));
                                        }
                                        if (typeof Vue !== "undefined") {
                                            new Vue().$message.error(data.msg);
                                        }
                                    }

                                    console.error("获取验证码失败:", data);
                                    reject();
                                }
                            } else {
                                console.error("获取验证码失败:", response);
                                console.dir(data);
                                reject();
                            }
                        }
                    },
                    onerror: function(err) {
                        console.error(err);
                        reject();
                    },
                });
            });
        } catch (error) {
            console.log(error);
        }
    }
    async findCaptcha() {
        // 先读取用户手动设置的验证码配置
        let cache = GM_getValue(NORMAL_STORE_KEY);
        let captchaPath = cache && JSON.parse(cache);
        if (
            captchaPath &&
            captchaPath.input &&
            captchaPath.img &&
            selector(captchaPath.input) &&
            selector(captchaPath.img)
        ) {
            let dataURL = await this.handleImg(selector(captchaPath.img));
            try {
                if (!this.hasRequest(dataURL, { record: true })) {
                    let code = await this.request(
                        this.dataURLtoFile(dataURL),
                        this.cssPath(selector(captchaPath.input)) +
                        "$$" +
                        this.cssPath(selector(captchaPath.img)),
                        selector(captchaPath.img).getAttribute("src")
                    );
                    if (code) {
                        this.setCaptchaCode(selector(captchaPath.input), code);
                        // selector(captchaPath.input).value = code.trim();
                        console.log("正在使用用户自定义验证码位置数据获取验证码");
                        return;
                    } else {
                        console.error("验证码为空,请检查图片是否正确");
                    }
                }
            } catch (error) {
                console.log(error);
            }
            return;
        }
        // 自动寻找验证码和输入框
        let captchaMap = [];
        let imgs = [...selectorAll("img")];
        imgs.forEach((img) => {
            let checkList = [
                ...this.getCaptchaFeature(img),
                ...this.getCaptchaFeature(img.parentNode),
            ];
            checkList = checkList.filter((item) => item);
            let isInvalid = ["#", "about:blank"].includes(img.getAttribute("src")) ||
                !img.getAttribute("src");

            for (let i = 0; i < checkList.length; i++) {
                if (
                    /.*(code|captcha|验证码|login|点击|verify|yzm|yanzhengma).*/im.test(
                        checkList[i].toLowerCase()
                    ) &&
                    img.width > 30 &&
                    img.width < 150 &&
                    img.height < 80 &&
                    !isInvalid
                ) {
                    captchaMap.push({ img: img, input: null });
                    break;
                }
            }
        });
        captchaMap.forEach((item) => {
            let imgEle = item.img;
            let parentNode = imgEle.parentNode;
            for (let i = 0; i < 4; i++) {
                // 以当前可能是验证码的图片为基点,向上遍历四层查找可能的Input输入框
                if (!parentNode) {
                    return;
                }
                let inputTags = [...parentNode.querySelectorAll("input")];
                if (inputTags.length) {
                    let input = inputTags.pop();
                    let type = input.getAttribute("type");
                    while (type !== "text" && inputTags.length) {
                        if (type === "password") {
                            break;
                        }
                        input = inputTags.pop();
                        type = input.getAttribute("type");
                    }
                    let inputWidth = getStyle(input).width.replace(/[^0-9]/gi, "");
                    // let inputHeight = getStyle(input).height.replace(/[^0-9]/gi, "");
                    if (!type || (type === "text" && inputWidth > 20)) {
                        // 兼容各种奇葩情况
                        item.input = input;
                        break;
                    }
                    if (type === "password") {
                        // 验证码一般在密码框后面,遍历到密码框了就大概率说明没有验证码
                        break;
                    }
                }
                parentNode = parentNode.parentNode;
            }
        });
        if (!captchaMap.length) {
            const { path, recommendTimes } = this.recommendPath;
            if (path) {
                let inputSelector = path.split("$$")[0];
                let imgSelector = path.split("$$")[1];
                if (selector(inputSelector) && selector(imgSelector)) {
                    let dataURL = await this.handleImg(selector(imgSelector));
                    try {
                        if (!this.hasRequest(dataURL, { record: true })) {
                            selector(inputSelector).value = await this.request(
                                this.dataURLtoFile(dataURL),
                                path,
                                item.img.getAttribute("src")
                            );
                            if (typeof Vue !== "undefined") {
                                new Vue().$message.success("获取验证码成功");
                            }
                        }
                    } catch (error) {
                        console.log(error);
                        // if (typeof Vue !== "undefined") {
                        //     new Vue().$message.error("获取验证码失败");
                        // }
                    }
                }
            }
        }
        captchaMap = captchaMap.filter((item) => item.input);
        captchaMap.forEach(async(item, index) => {
            let dataURL = await this.handleImg(item.img);
            try {
                if (!this.hasRequest(dataURL, { record: true })) {
                    let code = await this.request(
                        this.dataURLtoFile(dataURL),
                        this.cssPath(item.input) + "$$" + this.cssPath(item.img),
                        item.img.getAttribute("src")
                    );
                    if (code) {
                        this.setCaptchaCode(item.input, code);
                        // item.input.value = code;
                        if (typeof Vue !== "undefined") {
                            new Vue().$message.success("获取验证码成功");
                        }
                        console.log("正在使用自动寻找验证码功能获取验证码");
                    } else {
                        if (index === captchaMap.length - 1) {
                            this.getRecommendPath();
                        }
                        console.error("验证码为空,请检查图片是否正确");
                    }
                }
            } catch (error) {
                if (index === captchaMap.length - 1) {
                    this.getRecommendPath();
                }
                console.log(error);
                // if (typeof Vue !== "undefined") {
                //     new Vue().$message.error("获取验证码失败");
                // }
            }
        });
    }
    getImgViaBlob(url) {
        return new Promise((resolve, reject) => {
            try {
                GM_xmlhttpRequest({
                    method: "get",
                    url,
                    responseType: "blob",
                    onload: (res) => {
                        if (res.status === 200) {
                            let blob = res.response;
                            let fileReader = new FileReader();
                            fileReader.onloadend = (e) => {
                                let base64 = e.target.result;
                                if (base64.length > 20) {
                                    resolve(base64);
                                } else {
                                    alert(
                                        "验证码助手:当前网站验证码图片禁止跨域访问,待作者优化。"
                                    );
                                    handleClearMenuClick();
                                    reject("base64图片长度不够");
                                    throw "getImgViaBlob: base64图片长度不够";
                                }
                            };
                            fileReader.readAsDataURL(blob);
                        } else {
                            console.log("图片转换blob失败");
                            console.log(res);
                            reject();
                        }
                    },
                    onerror: function(err) {
                        console.log("图片请求失败:" + err);
                        reject();
                    },
                });
            } catch (error) {
                console.log(error);
                reject();
            }
        });
    }
    elDisplay(el) {
        if (!el) {
            return false;
        }

        while (el) {
            if (!(el instanceof Element)) {
                return true;
            }
            if (getStyle(el).display === "none") {
                return false;
            }
            el = el.parentNode;
        }
        return true;
    }
    checkSlideCaptcha() {
        const check = async() => {
            const slideCache =
                (GM_getValue(SLIDE_STORE_KEY) &&
                    JSON.parse(GM_getValue(SLIDE_STORE_KEY))) || {};
            const { bgImg, targetImg, moveItem } = slideCache;
            if (
                bgImg &&
                targetImg &&
                moveItem &&
                selector(targetImg) &&
                selector(bgImg) &&
                selector(moveItem) &&
                this.elDisplay(selector(targetImg)) &&
                this.elDisplay(selector(bgImg)) &&
                this.elDisplay(selector(moveItem))
            ) {
                const target_url =
                    selector(targetImg).getAttribute("src") ||
                    getStyle(selector(targetImg))["background-image"].split('"')[1];
                const bg_url =
                    selector(bgImg).getAttribute("src") ||
                    getStyle(selector(bgImg))["background-image"].split('"')[1];
                if (!this.hasRequest(target_url, { record: true, type: "url" })) {
                    const target_base64 = await this.getImgViaBlob(target_url);
                    const bg_base64 = await this.getImgViaBlob(bg_url);
                    return new Promise(async(resolve, reject) => {
                        let host = location.href;
                        let href = location.href.split("?")[0].split("#")[0];
                        if (self === top) {
                            host = location.host;
                        }
                        let detail = {
                            path: slideCache,
                            host,
                            href,
                        };
                        let formData = new FormData();
                        let requestUrl = "http://101.43.206.185:7000/slideCaptcha";
                        let targetWidth = getNumber(getStyle(selector(targetImg)).width);
                        let bgWidth = getNumber(getStyle(selector(bgImg)).width);
                        formData.append("target_img", this.dataURLtoFile(target_base64));
                        formData.append("bg_img", this.dataURLtoFile(bg_base64));
                        formData.append("targetWidth", targetWidth);
                        formData.append("bgWidth", bgWidth);
                        formData.append("detail", JSON.stringify(detail));
                        formData.append("whatever", GM_getValue('whatever'));
                        GM_xmlhttpRequest({
                            method: "post",
                            url: requestUrl,
                            data: formData,
                            onload: (response) => {
                                const data = JSON.parse(response.response);
                                this.moveSideCaptcha(
                                    selector(targetImg),
                                    selector(moveItem),
                                    data.result.target[0]
                                );
                                // resolve()
                            },
                            onerror: function(err) {
                                console.error(err);
                                reject();
                            },
                        });
                    });
                }
            }
        };
        check();
        // const interval = 3000;
        // simulateInterval(check, interval);
    }
    moveSideCaptcha(targetImg, moveItem, distance) {
        if (distance === 0) {
            console.log("distance", distance);
            return;
        }
        var btn = moveItem;
        let target = targetImg;

        let varible = null;
        let targetLeft = Number(getStyle(target).left.replace("px", "")) || 0;
        let targetParentLeft =
            Number(getStyle(target.parentNode).left.replace("px", "")) || 0;
        let targetTransform = Number(getEleTransform(target)) || 0;
        let targetParentTransform =
            Number(getEleTransform(target.parentNode)) || 0;

        var mousedown = document.createEvent("MouseEvents");
        var rect = btn.getBoundingClientRect();
        var x = rect.x;
        var y = rect.y;
        mousedown.initMouseEvent(
            "mousedown",
            true,
            true,
            document.defaultView,
            0,
            x,
            y,
            x,
            y,
            false,
            false,
            false,
            false,
            0,
            null
        );
        btn.dispatchEvent(mousedown);

        var dx = 0;
        var dy = 0;
        var interval = setInterval(function() {
            var mousemove = document.createEvent("MouseEvents");
            var _x = x + dx;
            var _y = y + dy;
            mousemove.initMouseEvent(
                "mousemove",
                true,
                true,
                document.defaultView,
                0,
                _x,
                _y,
                _x,
                _y,
                false,
                false,
                false,
                false,
                0,
                null
            );
            btn.dispatchEvent(mousemove);
            btn.dispatchEvent(mousemove);

            let newTargetLeft =
                Number(getStyle(target).left.replace("px", "")) || 0;
            let newTargetParentLeft =
                Number(getStyle(target.parentNode).left.replace("px", "")) || 0;
            let newTargetTransform = Number(getEleTransform(target)) || 0;
            let newTargetParentTransform =
                Number(getEleTransform(target.parentNode)) || 0;

            if (newTargetLeft !== targetLeft) {
                varible = newTargetLeft;
            } else if (newTargetParentLeft !== targetParentLeft) {
                varible = newTargetParentLeft;
            } else if (newTargetTransform !== targetTransform) {
                varible = newTargetTransform;
            } else if (newTargetParentTransform != targetParentTransform) {
                varible = newTargetParentTransform;
            }
            if (varible >= distance) {
                clearInterval(interval);
                var mouseup = document.createEvent("MouseEvents");
                mouseup.initMouseEvent(
                    "mouseup",
                    true,
                    true,
                    document.defaultView,
                    0,
                    _x,
                    _y,
                    _x,
                    _y,
                    false,
                    false,
                    false,
                    false,
                    0,
                    null
                );
                setTimeout(() => {
                    btn.dispatchEvent(mouseup);
                }, Math.ceil(Math.random() * 2000));
            } else {
                if (dx >= distance - 20) {
                    dx += Math.ceil(Math.random() * 2);
                } else {
                    dx += Math.ceil(Math.random() * 10);
                }
                let sign = Math.random() > 0.5 ? -1 : 1;
                dy += Math.ceil(Math.random() * 3 * sign);
            }
        }, 10);
        setTimeout(() => {
            clearInterval(interval);
        }, 10000);
    }
}

function getEleCssPath(el) {
    // 获取元素css path
    if (!(el instanceof Element)) return;
    var path = [];
    while (el.nodeType === Node.ELEMENT_NODE) {
        var selector = el.nodeName.toLowerCase();
        if (el.id) {
            selector += "#" + el.id;
            path.unshift(selector);
            break;
        } else {
            var sib = el,
                nth = 1;
            while ((sib = sib.previousElementSibling)) {
                if (sib.nodeName.toLowerCase() == selector) nth++;
            }
            if (nth != 1) selector += ":nth-of-type(" + nth + ")";
        }
        path.unshift(selector);
        el = el.parentNode;
    }
    return path.join(" > ");
}

function handleSlideMenuClick({ isPostmessage } = {}) {
    if (top === self) {
        alert("请点击滑动验证码的大图片,小图片,滑块。");
    }
    this.vue = new Vue();
    this.isIframe = top !== self;
    GM_deleteValue(SLIDE_STORE_KEY);

    let imgs = [...selectorAll("img")];
    let divTags = [...selectorAll("div")];
    imgs.forEach((img) => {
        img.addEventListener("click", onSlideTagClick);
    }, false);
    divTags.forEach((input) => {
        input.addEventListener("click", onSlideTagClick);
    }, false);

    setTimeout(() => {
        imgs.forEach((img) => {
            img && img.removeEventListener("click", onSlideTagClick);
        }, false);
        divTags.forEach((input) => {
            input.removeEventListener("click", onSlideTagClick);
        }, false);
    }, 30000);

    if (!isPostmessage) {
        if (self === top) {
            const iframes = [...selectorAll("iframe")];
            iframes.forEach((iframe) => {
                iframe.contentWindow.postMessage({
                        sign: "husky",
                        action: "handleSlideMenuClick",
                    },
                    "*"
                );
            });
        } else {
            window.postMessage({
                    sign: "husky",
                    action: "handleSlideMenuClick",
                },
                "*"
            );
        }
    }
}

let noticeTimer = 0;

function notice(msg) {
    if (noticeTimer) {
        clearTimeout(noticeTimer);
    } else {
        setTimeout(() => new Vue().$message.success(msg));
    }
    noticeTimer = setTimeout(() => new Vue().$message.success(msg), 1000);
}

var onSlideTagClick = (e) => {
    let el = e.target;
    let tagName = el.tagName.toLowerCase();
    let width = Number(getNumber(getStyle(el).width)) || 0;
    const vue = new Vue();
    let height = Number(getNumber(getStyle(el).height)) || 0;
    let position = getStyle(el).position;
    let pathCache =
        (GM_getValue(SLIDE_STORE_KEY) &&
            JSON.parse(GM_getValue(SLIDE_STORE_KEY))) || {};
    if (tagName === "img") {
        if (width >= height && width > 150) {
            let newValue = {...pathCache, bgImg: getEleCssPath(el) };
            GM_setValue(SLIDE_STORE_KEY, JSON.stringify(newValue));
            pathCache = newValue;
            notice("您已成功选择大图片");
        } else if (width < 100 && height >= width - 5) {
            let newValue = {...pathCache, targetImg: getEleCssPath(el) };
            GM_setValue(SLIDE_STORE_KEY, JSON.stringify(newValue));
            pathCache = newValue;
            notice("您已成功选择小图片");
        }
    } else {
        let curEl = el;
        for (let i = 0; i < 3; i++) {
            if (!curEl || curEl === Window) {
                break;
            }
            position = getStyle(curEl).position;
            let bgUrl = getStyle(curEl)["backgroundImage"];
            width = Number(getNumber(getStyle(curEl).width)) || 0;
            height = Number(getNumber(getStyle(curEl).height)) || 0;

            if (position === "absolute" && width < 100 && height < 100) {
                let newValue = {...pathCache, moveItem: getEleCssPath(curEl) };
                GM_setValue(SLIDE_STORE_KEY, JSON.stringify(newValue));
                pathCache = newValue;
                notice("您已成功选择滑块");
                break;
            }
            let reg = /url\("(.+)"\)/im;

            if (bgUrl && bgUrl.match(reg)) {
                if (width >= height && width > 150) {
                    let newValue = {...pathCache, bgImg: getEleCssPath(curEl) };
                    GM_setValue(SLIDE_STORE_KEY, JSON.stringify(newValue));
                    pathCache = newValue;
                    notice("您已成功选择大图片");
                    break;
                } else if (width < 100 && height >= width - 5) {
                    let newValue = {...pathCache, targetImg: getEleCssPath(curEl) };
                    GM_setValue(SLIDE_STORE_KEY, JSON.stringify(newValue));
                    pathCache = newValue;
                    notice("您已成功选择小图片");
                    break;
                }
            }
            curEl = curEl.parentNode;
        }

        curEl = el;
        const firstImg = curEl.querySelector("img");
        firstImg && onSlideTagClick({ target: firstImg });
    }

    const finish = Object.keys(pathCache).filter((item) => item).length == 3;
    if (finish) {
        let imgs = [...selectorAll("img")];
        let divTags = [...selectorAll("div")];
        imgs.forEach((img) => {
            img && img.removeEventListener("click", onSlideTagClick);
        }, false);
        divTags.forEach((div) => {
            div.removeEventListener("click", onSlideTagClick);
        }, false);
        setTimeout(() => {
            vue.$message.success("选择完毕,赶快试试吧");
            captchaInstance.doCheckTask();
        }, 3000);
    }
};

GM_registerMenuCommand("手动定位滑动验证码", handleSlideMenuClick);

function handleClearMenuClick() {
    GM_listValues().forEach((name) => {
        if (name.includes("husky")) {
            GM_deleteValue(name);
        }
    });
}

GM_registerMenuCommand("清空所有验证码配置", handleClearMenuClick);

function cleanCurrentPage() {
    GM_deleteValue(SLIDE_STORE_KEY);
    GM_deleteValue(NORMAL_STORE_KEY);
}
GM_registerMenuCommand("清空当前页面验证码配置", cleanCurrentPage);

let blackListMenuId = null;

function blackListCheck() {
    let key = location.host + location.pathname + "_black";
    let data = GM_getValue(key) && JSON.parse(GM_getValue(key));
    if (blackListMenuId) {
        GM_unregisterMenuCommand(blackListMenuId);
    }
    if (data) {
        blackListMenuId = GM_registerMenuCommand(
            "标记当前网站有验证码",
            labelWebsite
        );
    } else {
        blackListMenuId = GM_registerMenuCommand(
            "标记当前网站没有验证码",
            labelWebsite
        );
    }
    return data;
}

function labelWebsite() {
    let key = location.host + location.pathname + "_black";
    let data = GM_getValue(key) && JSON.parse(GM_getValue(key));
    if (data) {
        GM_setValue(key, "false");
    } else {
        GM_setValue(key, "true");
    }
    notice(
        "操作成功," + (data ? "已标记网站有验证码" : "已标记网站没有验证码")
    );
    if (data) {
        captchaInstance = captchaInstance || new Captcha();
        captchaInstance.init();
    }
    blackListCheck();
}
blackListCheck();

var captchaInstance = null;

function main() {
    window.addEventListener("DOMContentLoaded", function() {
        init();
        captchaInstance = new Captcha();
    });
}

const actions = {
    handleSlideMenuClick: handleSlideMenuClick,
};

window.addEventListener(
    "message",
    (event) => {
        const { data = {} } = event || {};
        const { sign, action } = data;
        if (sign === "husky") {
            if (action && actions[action]) {
                actions[action]({ isPostmessage: true });
            }
        }
    },
    false
);
main();

})();

Pubblica risposta

Accedi per pubblicare una risposta.