// ==UserScript== // @name 网页通用验证码 // @namespace http://tampermonkey.net/ // @version 3.1.2 // @description 解放眼睛和双手,自动识别并填入数字,字母验证码。新版本支持识别滑动验证码。 // @author 哈士奇 // @include http://* // @include https://* // @license MIT // @grant unsafeWindow // @grant GM_addStyle // @grant GM_listValues // @grant GM_addValueChangeListener // @grant GM_removeValueChangeListener // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_log // @grant GM_getResourceText // @grant GM_getResourceURL // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_xmlhttpRequest // @grant GM_download // @grant GM_getTab // @grant GM_saveTab // @grant GM_getTabs // @grant GM_notification // @grant GM_setClipboard // @grant GM_info // @grant GM_xmlhttpRequest // @connect * // @require https://unpkg.com/vue@2.6.12/dist/vue.js // @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 () { // GM_setValue('tipsConfig',"") var elementUIcss = GM_getResourceText("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|captcha|验证码|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, }); } dataURLtoFile(dataURL, filename = "captcha.jpg") { // base64转图片文件 var arr = dataURL.split(","), mime = (arr[0].match(/:(.*?);/) && arr[0].match(/:(.*?);/)[1]) || "image/png", bstr = atob(arr[1]), n = bstr.length, 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) { selector(inputSelector).value = 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); }, }); // 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)); // 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) { 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 > 50)) { // 兼容各种奇葩情况 item.input = input; break; } if (type === "password") { // 验证码一般在密码框后面,遍历到密码框了就大概率说明没有验证码 break; } } parentNode = parentNode.parentNode; } }); // console.log(captchaMap); 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) { 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)); 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(); })();