// ==UserScript==
// @name 自动提取政和验证码
// @namespace http://tampermonkey.net/
// @version 2.0
// @description 接着奏乐接着舞 Find an img with src containing "api-uaa/v1/validate/code", get Base64, and send it for captcha code
// @match *://*/*
// @match *://*/*
// @require http://libs.baidu.com/jquery/2.0.0/jquery.min.js
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// ==/UserScript==
// 显示识别结果弹框,并在 3 秒后自动关闭,带进度条
function showCaptchaResultPopup(resultText) {
// 首先检查是否已有弹框存在,如果存在,则移除之前的弹框
const existingModal = document.querySelector('.captcha-result-modal');
if (existingModal) {
existingModal.remove(); // 移除之前的弹框
}
// 创建新的弹框容器
const modal = document.createElement('div');
modal.className = 'captcha-result-modal'; // 给弹框加一个类名,方便以后查找
modal.style.position = 'fixed';
modal.style.top = '10px';
modal.style.right = '20px';
modal.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
modal.style.color = 'white';
modal.style.padding = '10px 20px'; // 减小弹框的高度
modal.style.borderRadius = '10px';
modal.style.zIndex = '999999'; // 确保在页面最上层
modal.style.width = '250px'; // 设置弹框宽度
modal.style.boxSizing = 'border-box';
// 创建弹框标题
const title = document.createElement('h3');
title.innerText = '验证码识别结果';
title.style.margin = '0 0 5px';
title.style.fontSize = '16px'; // 减小字体大小
modal.appendChild(title);
// 创建弹框内容
const content = document.createElement('p');
content.innerText = resultText; // 展示识别结果
content.style.fontSize = '14px'; // 减小字体大小
modal.appendChild(content);
// 创建关闭按钮
const closeButton = document.createElement('button');
closeButton.innerText = '×';
closeButton.style.position = 'absolute';
closeButton.style.top = '5px';
closeButton.style.right = '5px';
closeButton.style.padding = '0px';
closeButton.style.backgroundColor = '#FF4D4F'; // 红色背景
closeButton.style.color = 'white';
closeButton.style.border = 'none';
closeButton.style.borderRadius = '50%'; // 圆形按钮
closeButton.style.cursor = 'pointer';
closeButton.style.fontSize = '20px'; // 增大字体
closeButton.style.width = '20px'; // 定义宽度
closeButton.style.height = '20px'; // 定义高度
closeButton.style.transition = 'background-color 0.3s'; // 添加平滑过渡效果
// 绑定关闭按钮事件
closeButton.addEventListener('click', function () {
modal.remove(); // 关闭弹框
});
// 将关闭按钮添加到弹框
modal.appendChild(closeButton);
// 创建进度条容器
const progressBarContainer = document.createElement('div');
progressBarContainer.style.height = '5px';
progressBarContainer.style.backgroundColor = '#444';
progressBarContainer.style.borderRadius = '5px';
progressBarContainer.style.overflow = 'hidden';
modal.appendChild(progressBarContainer);
// 创建进度条
const progressBar = document.createElement('div');
progressBar.style.height = '100%';
progressBar.style.backgroundColor = '#4caf50'; // 进度条颜色
progressBar.style.width = '100%'; // 初始进度为 100%
progressBarContainer.appendChild(progressBar);
// 将弹框添加到页面
document.body.appendChild(modal);
// 设置倒计时,3秒后自动关闭
let timeLeft = 5; // 5 秒倒计时
let interval;
// 更新进度条和倒计时
function updateProgressBar() {
timeLeft -= 0.05;
progressBar.style.width = (timeLeft / 5) * 100 + '%'; // 更新进度条
if (timeLeft <= 0) {
clearInterval(interval); // 清除定时器
modal.remove(); // 关闭弹框
}
}
// 启动进度条更新定时器
interval = setInterval(updateProgressBar, 50); // 每 50 毫秒更新一次进度条
// 鼠标悬浮时暂停进度条和倒计时
modal.addEventListener('mouseover', () => {
clearInterval(interval); // 暂停进度条更新
});
// 鼠标离开时重新开始进度条
modal.addEventListener('mouseleave', () => {
interval = setInterval(updateProgressBar, 50); // 恢复进度条更新
});
}
// 处理图片验证码
function processImageCaptcha(imgElement) {
if (!imgElement || !imgElement.src) return;
// 打印图片信息
console.log('识别图像验证码:', imgElement.src);
// 将图片转换为 Base64 编码
getBase64Code(imgElement).then(base64 => {
// 这里可以调用识别 API 来处理 Base64 编码的图片
console.log('图片Base64编码:', base64);
// 发送 Base64 编码给服务器进行识别
sendImageForOCR(imgElement, base64);
}).catch(error => {
console.error('图片转换失败:', error);
});
}
// 获取图片的 Base64 编码
function getBase64Code(element) {
return new Promise((resolve, reject) => {
// 确保图片加载完成
if (element.complete) {
// 如果图片已经加载完,直接获取宽高
resolve(getImageBase64(element));
} else {
element.onload = function () {
resolve(getImageBase64(element));
};
element.onerror = function () {
reject('图片加载失败');
};
}
});
}
// 获取图片的 Base64 编码
function getImageBase64(element) {
// 创建一个 canvas 元素
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
// 确保 canvas 的尺寸与图片相同
canvas.width = element.naturalWidth;
canvas.height = element.naturalHeight;
console.log("验证码宽高:", element.naturalWidth, element.naturalHeight);
// 将图片绘制到 canvas 上
context.drawImage(element, 0, 0, element.naturalWidth, element.naturalHeight);
try {
//尝试直接转换,如果失败,可能存在跨域
return canvas.toDataURL("image/png");
} catch (e) {
element.crossOrigin = 'Anonymous';
context.drawImage(element, 0, 0, element.naturalWidth, element.naturalHeight);
return canvas.toDataURL("image/png");
}
}
function getCapFoowwLocalStorage(key) {
var data = JSON.parse(sessionStorage.getItem(key));
if (data !== null) {
if (data.expirse != null && data.expirse < new Date().getTime()) {
sessionStorage.removeItem(key);
} else {
return data.value;
}
}
return null;
}
// 操作webStorage 增加缓存,减少对服务端的请求
function setCapFoowwLocalStorage(key, value, ttl_ms) {
var data = {value: value, expirse: new Date(ttl_ms).getTime()};
sessionStorage.setItem(key, JSON.stringify(data));
}
// 发送图片到服务器进行 OCR 识别
function sendImageForOCR(imgElement, base64) {
console.log("准备发送图片进行识别,图片 URL:", imgElement.src);
console.log("Base64 编码:", base64);
try {
const formData = new FormData();
formData.append("image", base64);
GM_xmlhttpRequest({
method: "POST", url: "https://ocr.xiaojingjing.top:8443/ocr", headers: {
"accept": "application/json", "User-Agent": navigator.userAgent
}, data: formData, onload: function (response) {
const jsonResponse = JSON.parse(response.responseText);
// 检查 code 是否为 200
if (jsonResponse.code === 200) {
const inputElement = findClosestInput(imgElement);
if (inputElement) {
// 关闭输入框的验证
disableInputValidation(inputElement);
// 获取识别结果
const captchaText = jsonResponse.data;
console.log('识别结果:', captchaText);
// 判断是否包含计算表达式
if (imgElement.src.includes("type=1")) {
// 如果包含数学运算,进行计算
const result = evaluateMathExpression(captchaText);
console.log('计算结果:', result);
inputElement.value = result; // 填充计算结果
// 在页面上展示识别结果的弹框
showCaptchaResultPopup(captchaText.replace('=', '') + '=' + result);
} else {
// 否则,直接填充识别结果
inputElement.value = captchaText;
// 在页面上展示识别结果的弹框
showCaptchaResultPopup(captchaText);
}
// 手动触发 input 事件,以便页面检测到输入的变化
const event = new Event('input', {bubbles: true});
inputElement.dispatchEvent(event);
// 如果还有其他事件检测输入,可以一并触发
const changeEvent = new Event('change', {bubbles: true});
inputElement.dispatchEvent(changeEvent);
console.log('验证码已填充至输入框:', inputElement.value);
} else {
console.log('未查询到输入框!');
}
} else {
console.error('识别错误!:', jsonResponse.message);
}
}, onerror: function (error) {
console.error('识别错误:', error);
}
});
} catch (error) {
console.error("识别异常:", error);
}
}
// 计算数学表达式
function evaluateMathExpression(expression) {
try {
// 使用 JavaScript 的 eval 函数计算表达式
// 移除等号并计算
return eval(expression.replace('=', '').replace('x', '*').replace('X', '*').replace('÷', '/').trim());
} catch (e) {
console.error('计算错误:', e);
return '';
}
}
// 禁用输入框的验证
function disableInputValidation(inputElement) {
inputElement.removeAttribute('required');
inputElement.removeAttribute('minlength');
inputElement.removeAttribute('maxlength');
inputElement.removeAttribute('pattern');
// 移除错误提示标题
inputElement.removeAttribute('title');
// 如果输入框是属于一个表单,禁用整个表单的验证
const form = inputElement.closest('form');
if (form) {
// 禁用表单验证
form.setAttribute('novalidate', 'true');
}
}
// 给图片添加左键点击事件
function addImageClickListener(imgElement) {
if (!imgElement) return;
// 给图片添加左键点击事件
imgElement.addEventListener('click', function (e) {
// 确保是左键点击
if (e.button === 0) {
// 左键点击
console.log('点击了图片:', imgElement.src);
processImageCaptcha(imgElement);
}
});
}
// 定义从 img 元素开始逐级向上查找最近的 input 元素的函数
function findClosestInput(element) {
while (element) {
element = element.parentElement;
if (!element) break;
const inputs = element.getElementsByTagName('input');
if (inputs.length > 0) {
// 返回找到的最后一个 input 元素
return inputs[inputs.length - 1];
}
}
return null;
}
// 检查页面上的验证码图像,并触发识别
function checkForCaptchaImages() {
// 选择所有图片验证码元素
$("canvas,img,input[type='image']").each(function () {
// 确保是可见的图片,并且没有处理过
const imgElement = this;
if ($(imgElement).is(":visible") && !$(imgElement).data('processed')) {
const imgSrc = imgElement.src;
// 只处理包含 'api-uaa/v1/validate/code' 的图片
if (imgSrc && imgSrc.includes('api-uaa/v1/validate/code')) {
// 标记为已处理
$(imgElement).data('processed', true);
// 给图片添加左键点击事件
addImageClickListener(imgElement);
// 进行识别
processImageCaptcha(imgElement);
}
}
});
}
// 监听图片的变化,确保图像更新后重新触发识别
function observeImageChanges() {
const observer = new MutationObserver(() => {
checkForCaptchaImages();
});
// 监听 img 和 canvas 元素的变化
observer.observe(document.body, {
childList: true, subtree: true,
});
}
// 开始监听图像变化
observeImageChanges();