Greasy Fork is available in English.
解放眼睛和双手,自动识别并填入数字,字母验证码。新版本支持识别滑动验证码。
使用最新代码即可: // ==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();
})();
http://www.ttshitu.com/login.html?spm=null
这个网站上的不会识别,定位也不行