// ==UserScript==
// @name 数英通用验证码
// @namespace http://tampermonkey.net/
// @version 2.1
// @description 数字和字母通用验证码,识别率可达95%以上。
// @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://cdn.jsdelivr.net/npm/[email protected]
// @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];
selector = document.querySelector.bind(document);
selectorAll = document.querySelectorAll.bind(document);
getItem = localStorage.getItem.bind(localStorage);
setItem = localStorage.setItem.bind(localStorage);
}
class BaiDuCloud {
// 百度网盘自动填写验证码
constructor() {
this.findBaiduCloudLink();
this.getCode();
// this.autoDownLoad() // 根据需要自主选择是否开启自动点击下载网盘文件
}
findBaiduCloudLink() {
let text = document.body.innerText.replace(/\x0A/gm, "");
// let panLink = text.match(/(pan.baidu.com[^\u4e00-\u9fa5]+)(提取码|密码).?\s?(\w{4})/gm);
let panLink = text.match(
/pan.baidu.com[^\u4e00-\u9fa5]+\s*\n?[\u4e00-\u9fa5]+.*?([a-z\d]{4})/gm
);
if (!panLink) {
return;
}
panLink.forEach((el) => {
let link = el.match(/(pan.baidu.com[^\u4e00-\u9fa5]+)/gm)[0];
let code = el.match(/([a-z\d]{4}$)/gm)[0];
let key = "https://" + link.trim();
GM_gstValue(key, code);
});
}
getCode() {
if (!CUR_URL.includes("pan.baidu.com") || !CUR_URL.includes("share")) {
return;
}
let sourceId = CUR_URL.split("surl=")[1];
let url = "https://pan.baidu.com/s/1" + sourceId;
let code = GM_getValue(url);
if (code && selector("#accessCode") && selector("#accessCode")) {
selector("#accessCode").value = code;
selector("#submitBtn .submit-a").click();
setTimeout(() => {
if (selector("#mpz1nz1")) {
GM_log("哈士奇小助手:检测到提取码错误,当前密码:" + code);
}
}, 1000);
} else {
GM_log("哈士奇小助手:没有找到可使用的网盘验证码");
}
}
autoDownLoad() {
// 到网盘下载界面后是否自动点击下载
if (!CUR_URL.includes("pan.baidu.com") || !CUR_URL.includes("/s")) {
return;
}
let open = GM_getValue("autoDownload");
let filterUnuseLink = GM_getValue("filterUnuseLink");
if (!open || filterUnuseLink) {
return;
}
let downBtn = selectorAll("a.g-button")[1];
let hasFileList = selector("#shareqr");
let text = document.body.innerText;
if (hasFileList) {
// 有文件列表,需要先勾选
selector(".zbyDdwb").click();
downBtn.click();
} else if (
document.body &&
(text.includes("不存在") ||
text.includes("删除") ||
text.includes("无法"))
) {
setTimeout(() => {
window.close();
}, 3000);
} else if (downBtn) {
//单个文件直接下载
downBtn.click();
}
let timer = setInterval(() => {
if (
selector(".module-yun-tip") &&
selector(".module-yun-tip").innerText.includes("启动网盘")
) {
clearInterval(timer);
}
}, 1000);
}
}
class Captcha {
// 识别网页中的验证码
constructor() {
this.imgCache = [];
this.inputTags = [];
this.recommendPath = {};
window.addEventListener("load", async () => {
this.manualLocateCaptcha();
try {
this.recommendPath = await this.getRecommendPath();
} catch (error) {}
this.findCaptcha();
});
}
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 });
}
getRecommendPath() {
let requestUrl =
"http://101.43.206.185:7000/cssPath?href=" +
location.href.split("?")[0];
try {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "get",
url: requestUrl,
onload: (res) => {
if (res.status === 200 && res.response) {
let data = JSON.parse(res.response);
if (data.path && data.recommendTimes) {
resolve(data);
}
}
resolve({});
},
onerror: function (err) {
console.log("推荐路径请求失败:" + err);
resolve({});
},
});
});
} catch (error) {
console.log(error);
Promise.resolve({});
}
}
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("cssPath" + location.href, 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("选择完毕,赶快试试吧");
}, 3000);
finish = true;
}
};
var onMenuClick = (e) => {
if (this.isIframe) {
alert("当前脚本处于iframe中,暂不支持该操作,快让作者优化吧");
return;
}
finish = false;
cssPathStore = {};
GM_deleteValue("cssPath" + location.href);
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;
if (!img.src.includes(";base64,")) {
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");
} 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 imgClips = dataURL.slice(dataURL.length - 100, 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];
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.msg);
reject();
}
} else {
console.error("获取验证码失败:" + data);
reject();
}
}
},
onerror: function (err) {
console.error(err);
reject();
},
});
});
} catch (error) {
console.log(error);
}
}
async findCaptcha() {
let timer = setInterval(async () => {
// 先读取用户手动设置的验证码配置
let cache = GM_getValue("cssPath" + location.href);
let captchaPath = cache && JSON.parse(cache);
if (
captchaPath &&
captchaPath.input &&
captchaPath.img &&
selector(captchaPath.input) &&
selector(captchaPath.img) &&
selector(captchaPath.img).getAttribute("src")
) {
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);
}
}
// 自动寻找验证码和输入框
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);
const { path, recommendTimes = 0 } = this.recommendPath;
// 获取网友共享验证码位置数据
if (!captchaMap.length || recommendTimes > 0) {
if (path) {
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("正在使用共享验证码功能获取验证码");
return;
} else {
console.error("验证码为空,请检查图片是否正确");
}
}
} catch (error) {
console.log(error);
// if (typeof Vue !== "undefined") {
// new Vue().$message.error("获取验证码失败");
// }
}
}
}
}
captchaMap = captchaMap.filter((item) => item.input);
captchaMap.forEach(async (item) => {
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 {
console.error("验证码为空,请检查图片是否正确");
}
}
} catch (error) {
console.log(error);
// if (typeof Vue !== "undefined") {
// new Vue().$message.error("获取验证码失败");
// }
}
});
}, 1000);
window.addEventListener("beforeunload", () => {
window.clearInterval(timer);
});
}
}
function main() {
window.addEventListener("DOMContentLoaded", function () {
init();
new BaiDuCloud();
new Captcha();
});
}
main();
})();