// ==UserScript== // @name 自动识别填充网页验证码-重制版 // @namespace http://tampermonkey.net/ // @version 1.1.0 // @description 自动识别填写大部分网站的验证码 (本地存储+云码) // @author lcymzzZ (Original), Lanxi (Modifier) // @license GPL Licence // @connect https://www.jfbym.com // @match http://*/* // @match https://*/* // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCI+PHBhdGggZD0iTTEyIDI0YzAtMTEuMDUgOC45NS0yMCAyMC0yMHMyMCA4Ljk1IDIwIDIwdjE2YzAgMTEuMDUtOC45NSAyMC0yMCAyMHMtMjAtOC45NS0yMC0yMFptMjAtMTJjLTYuNjI1IDAtMTIgNS4zNzUtMTIgMTJ2MTZjMCA2LjYyNSA1LjM3NSAxMiAxMiAxMnMxMi01LjM3NSAxMi0xMlYyNGMwLTYuNjI1LTUuMzc1LTEyLTEyLTEyIiBzdHlsZT0ic3Ryb2tlOm5vbmU7ZmlsbC1ydWxlOm5vbnplcm87ZmlsbDojMDAwO2ZpbGwtb3BhY2l0eToxIi8+PC9zdmc+ // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; var element, input, imgIndex, canvasIndex, inputIndex, captchaType; var localRules = {}; var exist = false; var iscors = false; var inBlack = false; var noRuleFound = false; var recognitionSuccessful = false; var focusListenerAttached = false; var initialCheckRun = false; var fisrtUse = GM_getValue("fisrtUse", true); if (fisrtUse) { var mzsm = prompt(`自动识别填充网页验证码 (本地存储+云码版) 首次使用,请阅读并同意以下免责条款。 1. 此脚本仅用于学习研究,您必须在下载后24小时内将所有内容从您的计算机或手机或任何存储设备中完全删除,若违反规定引起任何事件本人对此均不负责。 2. 请勿将此脚本用于任何商业或非法目的,若违反规定请自行对此负责。 3. 本人对此脚本引发的问题概不负责,包括但不限于由脚本错误引起的任何损失和损害。 4. 任何以任何方式查看此脚本的人或直接或间接使用此脚本的使用者都应仔细阅读此条款。 5. 本人保留随时更改或补充此条款的权利,一旦您使用或复制了此脚本,即视为您已接受此免责条款。 若您同意以上内容,请输入"我已阅读并同意以上内容" 然后开始使用。`, ""); if (mzsm == "我已阅读并同意以上内容") { GM_setValue("fisrtUse", false); } else { alert("免责条款未同意,脚本停止运行。\n若不想使用,请自行禁用脚本,以免每个页面都弹出该提示。"); return; } } function fetchAndDisplayBalance() { var token = GM_getValue("token"); if (!token) { alert("请先通过菜单设置云码 Token"); return; } const datas = { "token": token, "type": "score" }; alert("正在查询云码余额..."); GM_xmlhttpRequest({ method: "POST", url: "http://api.jfbym.com/api/YmServer/getUserInfoApi", data: JSON.stringify(datas), headers: { "Content-Type": "application/json" }, responseType: "json", timeout: 10000, onload: function(response) { try { if (response.status == 200 && response.response) { var respData = response.response; if (respData.data && respData.data.score !== undefined) { alert(`查询成功!\n云码余额: ${respData.data.score}`); } else if (respData.code === 10000) { alert(`查询成功,但未获取到余额信息。 API Msg: ${respData.msg || 'N/A'}`); console.warn("查询余额成功但缺少 score 字段:", respData); } else { let errorCode = respData.code; let errorMsg = yunmaErrorCodes[errorCode]; let displayMsg = `查询余额失败: ${errorMsg || respData.msg || '未知错误'}`; displayMsg += ` (Code: ${errorCode || 'N/A'})`; alert(displayMsg); console.error("查询余额 API 返回错误:", respData); } } else { alert(`查询余额请求失败,状态码: ${response.status}`); } } catch (e) { alert("处理余额响应时出错。"); console.error("处理余额响应时出错:", e, response.responseText); } }, onerror: function(response) { alert("查询余额网络错误,请检查网络连接或稍后再试。"); console.error("查询余额网络错误:", response); }, ontimeout: function() { alert("查询余额请求超时。"); console.error("查询余额请求超时"); } }); } //添加菜单 GM_registerMenuCommand('添加当前页面规则', addRule); GM_registerMenuCommand('清除当前页面规则', delRule); GM_registerMenuCommand('云码Token(验证码识别必需)', saveToken); GM_registerMenuCommand('修改当前规则识别类型', modifyCurrentRuleYmType); GM_registerMenuCommand('查询云码余额', fetchAndDisplayBalance); GM_registerMenuCommand('管理脚本设置', showSettingsPanel); GM_setValue("preCode", ""); function saveToken(){ var helpText = "云码Token是使用本脚本识别验证码的必需凭证。\n请前往 www.jfbym.com 注册并获取。\n文档:https://www.jfbym.com/page-doc/api-project-list.html"; var currentToken = GM_getValue("token", ""); var token = prompt(helpText + "\n\n当前Token: " + (currentToken ? currentToken.substring(0, 5) + "..." : "未设置"), currentToken); if (token === null) { return; } if (token === "") { GM_setValue("token", ""); alert("Token已清除"); return; } if (token.length < 10) { alert("Token格式似乎不正确,请重新输入。"); return; } alert("Token保存成功"); GM_setValue("token", token); } const yunmaErrorCodes = { 10001: "参数错误", 10002: "余额不足", 10003: "无此访问权限 (请检查 Token 或重置)", 10004: "无此验证类型", 10005: "网络拥塞", 10006: "数据包过载", 10007: "服务繁忙", 10008: "网络错误,请稍后重试", 10009: "结果准备中,请稍后再试", 10010: "请求结束 (但未成功?)" }; const captchaTypes = [ { code: '10110', description: '通用数英1-4位(2积分一次,最便宜)' }, { code: '10111', description: '通用数英5-8位(3积分一次)' }, { code: '10112', description: '通用数英9~11位(5积分一次)' }, { code: '10113', description: '通用数英12位及以上(10积分一次)' }, { code: '10103', description: '通用数英1~6位plus (12积分一次,别用)' }, { code: '9001', description: '定制-数英5位~qcs' }, { code: '193', description: '定制-纯数字4位' }, { code: '10114', description: '通用中文字符1~2位' }, { code: '10115', description: '通用中文字符 3~5位' }, { code: '10116', description: '通用中文字符6~8位' }, { code: '10117', description: '通用中文字符9位及以上' }, { code: '10107', description: '定制-XX西游苦行中文字符' }, { code: '50100', description: '通用数字计算题(15积分一次...)' }, { code: '50101', description: '通用中文计算题' }, { code: '452', description: '定制-计算题 cni' } ]; function modifyCurrentRuleYmType() { var currentUrl = window.location.href.split("?")[0]; let allRules = GM_getValue("localRules", {}); let originalRule = allRules[currentUrl]; if (!originalRule) { topNotice("当前页面不存在规则,请先添加规则后再修改类型。"); return; } let ruleToModify = { ...originalRule }; let availableTypesForPrompt = captchaTypes; let promptTitle = "修改数英/中文字符识别类型:"; let defaultPromptValue = ruleToModify.ymType || '10103'; if (ruleToModify.captchaType === 'math') { alert(`当前规则为算术验证码,固定使用类型 ${ruleToModify.ymType || '50100'}。如需更改为字符类型,请删除规则后重新添加。`); return; } let currentYmType = ruleToModify.ymType || '未设置 (将使用 10103)'; let promptText = `${promptTitle}\n当前页面 (${currentUrl})\n当前类型: ${currentYmType}\n\n请输入新的类型代码 (留空使用默认 10103):\n\n`; let defaultType = '10103'; let validCodes = availableTypesForPrompt.map(t => t.code); availableTypesForPrompt.forEach(type => { promptText += `${type.code}: ${type.description}\n`; }); let selectedYmType = prompt(promptText, defaultPromptValue); if (selectedYmType === null) { topNotice("修改已取消"); return; } let finalYmType = defaultType; if (selectedYmType === "") { } else if (validCodes.includes(selectedYmType)) { finalYmType = selectedYmType; } else { alert(`无效的类型代码 '${selectedYmType}'。未作修改。将继续使用 ${currentYmType} 或默认类型。`); return; } ruleToModify.ymType = finalYmType; ruleToModify.captchaType = 'general'; try { allRules[currentUrl] = ruleToModify; GM_setValue("localRules", allRules); topNotice(`规则识别类型已更新为: ${ruleToModify.ymType}。请刷新页面使更改生效。`); } catch (error) { console.error("保存更新后的规则失败:", error); topNotice("错误:保存更新后的规则失败!"); } } //判断是否为验证码(预设规则) function isCode(){ if (element.height >= 100 || element.height == element.width) return false; var attrList = ["id", "title", "alt", "name", "className", "src"]; var strList = ["code", "Code", "CODE", "captcha", "Captcha", "CAPTCHA", "yzm", "Yzm", "YZM", "check", "Check", "CHECK", "random", "Random", "RANDOM", "veri", "Veri", "VERI", "验证码", "看不清", "换一张"]; for (var i = 0; i < attrList.length; i++) { for (var j = 0; j < strList.length; j++) { var attr = element[attrList[i]]; if (attr.indexOf(strList[j]) != -1) { return true; } } } return false; } //判断是否为验证码输入框(预设规则) function isInput(){ var attrList = ["placeholder", "alt", "title", "id", "className", "name"]; var strList = ["code", "Code", "CODE", "captcha", "Captcha", "CAPTCHA", "yzm", "Yzm", "YZM", "check", "Check", "CHECK", "random", "Random", "RANDOM", "veri", "Veri", "VERI", "验证码", "看不清", "换一张"]; for (var i = 0; i < attrList.length; i++) { for (var j = 0; j < strList.length; j++) { var attr = input[attrList[i]]; if (attr.indexOf(strList[j]) != -1) { // return true; } } } return false; } //手动添加规则(操作) function addRule(){ var ruleData = {"url": window.location.href.split("?")[0], "img": "", "input": "", "inputType": "", "type": "", "captchaType": "", "ymType": null }; //检测鼠标右键点击事件 topNotice('请在验证码图片上点击鼠标 "右"👉 键'); document.oncontextmenu = function(e){ e = e || window.event; e.preventDefault(); if (e.target.tagName == "IMG" || e.target.tagName == "GIF") { var imgList = document.getElementsByTagName('img'); for (var i = 0; i < imgList.length; i++) { if (imgList[i] == e.target) { var k = i; ruleData.type = "img"; } } } else if (e.target.tagName == "CANVAS") { var imgList = document.getElementsByTagName('canvas'); for (var i = 0; i < imgList.length; i++) { if (imgList[i] == e.target) { var k = i; ruleData.type = "canvas"; } } } if (k == null) { topNotice("选择有误,请重新点击验证码图片"); return; } ruleData.img = k; topNotice('请在验证码输入框上点击鼠标 "左"👈 键'); document.onclick = function(e){ e = e || window.event; e.preventDefault(); var inputList = document.getElementsByTagName('input'); var textareaList = document.getElementsByTagName('textarea'); // if (e.target.tagName == "INPUT") { ruleData.inputType = "input"; for (var i = 0; i < inputList.length; i++) { if (inputList[i] == e.target) { if (inputList[0] && (inputList[0].id == "_w_simile" || inputList[0].id == "black_node")) { var k = i - 1; } else { var k = i; } } } } else if (e.target.tagName == "TEXTAREA") { ruleData.inputType = "textarea"; for (var i = 0; i < textareaList.length; i++) { if (textareaList[i] == e.target) { var k = i; } } } if (k == null) { topNotice("选择有误,请重新点击验证码输入框"); return; } ruleData.input = k; var r = confirm('选择验证码类型\n\n数/英验证码请点击"确定",算术验证码请点击"取消"'); if (r == true) { ruleData.captchaType = "general"; let promptText = "请为当前规则选择一个具体的识别类型(输入代码):\n默认使用 10103 (通用数英1~6位plus)\n\n"; let defaultType = '10103'; let validCodes = captchaTypes.map(t => t.code); captchaTypes.forEach(type => { promptText += `${type.code}: ${type.description}\n`; }); let selectedYmType = prompt(promptText, defaultType); if (selectedYmType === null) { topNotice("添加规则已取消"); document.oncontextmenu = null; document.onclick = null; return; } if (validCodes.includes(selectedYmType)) { ruleData.ymType = selectedYmType; } else { alert(`无效的选择 '${selectedYmType}',将使用默认类型 ${defaultType}。`); ruleData.ymType = defaultType; } } else { ruleData.captchaType = "math"; ruleData.ymType = '50100'; } addR(ruleData).then((res)=>{ if (res.status == 200){ topNotice("添加规则成功"); document.oncontextmenu = null; document.onclick = null; start(); } else { topNotice("Error,添加规则失败"); document.oncontextmenu = null; document.onclick = null; } }); } } } //手动添加规则(请求) -> 改为本地存储 function addR(ruleData){ return new Promise((resolve, reject) => { try { let allRules = GM_getValue("localRules", {}); allRules[ruleData.url] = ruleData; GM_setValue("localRules", allRules); resolve({ status: 200 }); } catch (error) { console.error("保存规则到本地存储失败:", error); reject({ status: 500, error: error }); } }); } //删除当前页面规则 -> 改为本地存储 function delRule(){ var currentUrl = window.location.href.split("?")[0]; var ruleData = {"url": currentUrl}; delR(ruleData).then((res)=>{ if (res.status == 200) topNotice("删除规则成功"); else topNotice("Error,删除规则失败"); }); } //删除规则(请求) -> 改为本地存储 function delR(ruleData){ return new Promise((resolve, reject) => { try { let allRules = GM_getValue("localRules", {}); if (allRules[ruleData.url]) { delete allRules[ruleData.url]; GM_setValue("localRules", allRules); resolve({ status: 200 }); } else { resolve({ status: 404 }); } } catch (error) { console.error("从本地存储删除规则失败:", error); reject({ status: 500, error: error }); } }); } //按已存规则填充 function codeByRule(){ if (!element || !(element instanceof HTMLImageElement)) { console.error("【自动识别填充验证码】错误:规则指定的验证码图片元素 (element) 无效或未找到。请检查规则或重新添加。 Index:", imgIndex, "Element found:", element); return; } if (!input || !(input instanceof HTMLInputElement || input instanceof HTMLTextAreaElement)) { console.error("【自动识别填充验证码】错误:规则指定的验证码输入框 (input) 无效或未找到。请检查规则或重新添加。 Index:", inputIndex, "Element found:", input); return; } var code = ""; var src = element.src; if (src.indexOf('data:image') != -1) { // code = src.split("base64,")[1]; GM_setValue("tempCode", code); if (GM_getValue("tempCode") != GM_getValue("preCode")) { recognitionSuccessful = false; console.log('[调试] codeByRule (data URI): Image changed, ready to call p1.'); GM_setValue("preCode", GM_getValue("tempCode")); p1(code, localRules.ymType).then((ans) => { if (ans) { writeIn1(ans); } }); } } else if (src.indexOf('blob') != -1) { const image = new Image() image.src = src; image.onload = () => { const canvas = document.createElement('canvas') canvas.width = image.width canvas.height = image.height const context = canvas.getContext('2d') context.drawImage(image, 0, 0, image.width, image.height); code = canvas.toDataURL().split("base64,")[1]; GM_setValue("tempCode", code); if (GM_getValue("tempCode") != GM_getValue("preCode")) { recognitionSuccessful = false; console.log('[调试] codeByRule (blob): Image changed, ready to call p1.'); GM_setValue("preCode", GM_getValue("tempCode")); p1(code, localRules.ymType).then((ans) => { if (ans) { writeIn1(ans); } }); } } } else { var img = element; if (img.complete && img.naturalWidth !== 0 && img.src) { try { var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0, img.width, img.height); code = canvas.toDataURL("image/png").split("base64,")[1]; GM_setValue("tempCode", code); if (GM_getValue("tempCode") != GM_getValue("preCode")) { recognitionSuccessful = false; console.log('[调试] codeByRule (img element immediate): Image changed, ready to call p1.'); GM_setValue("preCode", GM_getValue("tempCode")); p1(code, localRules.ymType).then((ans) => { if (ans) { writeIn1(ans); } }); } } catch(err) { console.error("Error processing completed image in codeByRule:", err); return; } } else if (img.src) { img.onload = function() { try { var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0, img.width, img.height); code = canvas.toDataURL("image/png").split("base64,")[1]; GM_setValue("tempCode", code); if (GM_getValue("tempCode") != GM_getValue("preCode")) { recognitionSuccessful = false; console.log('[调试] codeByRule (img element onload): Image changed, ready to call p1.'); GM_setValue("preCode", GM_getValue("tempCode")); p1(code, localRules.ymType).then((ans) => { if (ans) { writeIn1(ans); } }); } } catch (err) { console.error("Error processing image onload in codeByRule:", err); } } img.onerror = function() { console.error("Error loading image source in codeByRule:", img.src); } } else { console.warn("codeByRule: Image source is missing or invalid."); } } } function canvasRule(){ if (!element || !(element instanceof HTMLCanvasElement)) { console.error("【自动识别填充验证码】错误:规则指定的验证码 Canvas 元素 (element) 无效或未找到。请检查规则或重新添加。 Index:", canvasIndex, "Element found:", element); return; } if (!input || !(input instanceof HTMLInputElement || input instanceof HTMLTextAreaElement)) { console.error("【自动识别填充验证码】错误:规则指定的验证码输入框 (input) 无效或未找到。请检查规则或重新添加。 Index:", inputIndex, "Element found:", input); return; } setTimeout(function(){ try { let t0 = performance.now(); var code = element.toDataURL("image/png").split("base64,")[1]; let t1 = performance.now(); GM_setValue("tempCode", code); if (GM_getValue("tempCode") != GM_getValue("preCode")) { recognitionSuccessful = false; console.log('[调试] canvasRule: Canvas changed, ready to call p1.'); GM_setValue("preCode", GM_getValue("tempCode")); p1(code, localRules.ymType).then((ans) => { if (ans) { writeIn1(ans); } }); } } catch(err){ canvasRule(); } }, 100); } //寻找网页中的验证码 function findCode(k){ var code = ''; var codeList = document.getElementsByTagName('img'); // for (var i = k; i < codeList.length; i++) { var src = codeList[i].src; element = codeList[i]; if (src.indexOf('data:image') != -1) { if (isCode()) { firstin = false; code = src.split("base64,")[1]; // GM_setValue("tempCode", code); if (GM_getValue("tempCode") != GM_getValue("preCode")) { GM_setValue("preCode", GM_getValue("tempCode")); p(code, i); } break; } } else { if (isCode()) { if (firstin){ firstin = false; var img = element; if (img.src && img.width != 0 && img.height != 0) { var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0, img.width, img.height); code = canvas.toDataURL("image/png").split("base64,")[1]; try{ code = canvas.toDataURL("image/png").split("base64,")[1]; } catch(err){ // findCode(i + 1); return; } // GM_setValue("tempCode", code); if (GM_getValue("tempCode") != GM_getValue("preCode")) { iscors = isCORS(); GM_setValue("preCode", GM_getValue("tempCode")); p(code, i); return; } } else{ findCode(i); return; } } else { var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); element.onload = function(){ canvas.width = element.width; canvas.height = element.height; ctx.drawImage(element, 0, 0, element.width, element.height); try{ code = canvas.toDataURL("image/png").split("base64,")[1]; } catch(err){ // findCode(i + 1); return; } // GM_setValue("tempCode", code); if (GM_getValue("tempCode") != GM_getValue("preCode")) { iscors = isCORS(); GM_setValue("preCode", GM_getValue("tempCode")); p(code, i); return; } } break; } } } } } //寻找网页中的验证码输入框 function findInput(){ var inputList = document.getElementsByTagName('input'); // for (var i = 0; i < inputList.length; i++) { input = inputList[i]; if (isInput()) { return true; } } } //将识别结果写入验证码输入框(预设规则) function writeIn(ans){ if (findInput()) { ans = ans.replace(/\s+/g,""); input.value = ans; if (typeof(InputEvent)!=="undefined"){ input.value = ans; input.dispatchEvent(new InputEvent('input')); var eventList = ['input', 'change', 'focus', 'keypress', 'keyup', 'keydown', 'select']; for (var i = 0; i < eventList.length; i++) { fire(input, eventList[i]); } input.value = ans; } else if(KeyboardEvent) { input.dispatchEvent(new KeyboardEvent("input")); } } } //识别验证码(预设规则) -> 改为调用 p1 function p(code, i){ return p1(code).then(result => { if (result === null || result === undefined) { findCode(i + 1); return ""; } else { writeIn(result); return result; } }).catch(error => { console.error("调用 p1 时出错 (来自 p):", error); findCode(i + 1); return ""; }); } //识别验证码(自定义规则) -> 改为调用云码 API function p1(code, ruleYmType = null) { var token = GM_getValue("token"); if (!token) { topNotice("请先通过菜单设置云码 Token"); return Promise.resolve(null); } if (!ruleYmType) { topNotice("错误:当前规则未指定识别类型 (ymType),无法识别。请通过菜单\"修改当前规则识别类型\"进行设置。"); console.warn("[识别中止] 当前规则缺少 ymType:", localRules); return Promise.resolve(null); } let ymTypeToUse = ruleYmType; const datas = { "image": String(code), "type": ymTypeToUse, "token": token, }; return new Promise((resolve, reject) => { let api_t0 = performance.now(); GM_xmlhttpRequest({ method: "POST", url: "http://api.jfbym.com/api/YmServer/customApi", data: JSON.stringify(datas), headers: { "Content-Type": "application/json", }, responseType: "json", timeout: 20000, onload: function(response) { let api_t1 = performance.now(); try { if (response.status == 200 && response.response) { var respData = response.response; if (respData.data && respData.data.time) { } if (respData.code === 10000 && respData.data && respData.data.code === 0) { var result = respData.data.data; return resolve(result); } else { let errorCode = respData.code; let errorMsg = yunmaErrorCodes[errorCode]; if (!errorMsg && respData.data && respData.data.code !== 0) { errorCode = respData.data.code; errorMsg = yunmaErrorCodes[errorCode]; } let displayMsg = `云码识别失败: ${errorMsg || respData.msg || '未知错误'}`; displayMsg += ` (Code: ${errorCode || 'N/A'})`; if (respData.data && respData.data.msg && respData.data.msg !== respData.msg) { displayMsg += ` (详情: ${respData.data.msg})`; } topNotice(displayMsg); console.error("云码识别失败:", respData); } } else { topNotice("云码 API 请求失败,状态码: " + response.status); console.error("云码 API 请求失败:", response); } } catch (e) { topNotice("处理云码响应时出错"); console.error("处理云码响应时出错:", e, response.responseText); } return resolve(null); }, onerror: function(response) { let api_t1 = performance.now(); topNotice("云码 API 请求网络错误"); console.error("云码 API 请求网络错误:", response); return resolve(null); }, ontimeout: function() { let api_t1 = performance.now(); topNotice("云码 API 请求超时"); console.error("云码 API 请求超时"); return resolve(null); } }); }); } //判断是否跨域 function isCORS(){ try { if (element && element.tagName === 'IMG' && element.src) { if (element.src.startsWith('http:') || element.src.startsWith('https:')) { try { const imgOrigin = new URL(element.src).origin; if (imgOrigin !== window.location.origin) { return true; } } catch (urlError) { console.warn("isCORS check failed to parse element.src:", element.src, urlError); return false; } } } // return false; } catch(err){ console.warn("isCORS check failed unexpectedly:", err); return false; } } //将url转换为base64(解决跨域问题) function p2(){ let p2_t0 = performance.now(); return new Promise((resolve, reject) =>{ GM_xmlhttpRequest({ url: element.src, method: "GET", headers: {'Content-Type': 'application/json; charset=utf-8','path' : window.location.href}, responseType: "blob", timeout: 15000, onload: function(response) { let blob = response.response; let reader = new FileReader(); reader.onloadend = (e) => { let data = e.target.result; element.src = data; let p2_t1 = performance.now(); return resolve(data); } reader.readAsDataURL(blob); }, onerror: function(response) { let p2_t1 = performance.now(); reject(new Error("Cross-origin fetch failed")); }, ontimeout: function() { let p2_t1 = performance.now(); reject(new Error("Cross-origin fetch timed out")); } }); }); } //此段逻辑借鉴Crab大佬的代码,十分感谢 function fire(element,eventName){ var event = document.createEvent("HTMLEvents"); event.initEvent(eventName, true, true); element.dispatchEvent(event); } function FireForReact(element, eventName) { try { let env = new Event(eventName); element.dispatchEvent(env); var funName = Object.keys(element).find(p => Object.keys(element[p]).find(f => f.toLowerCase().endsWith(eventName))); if (funName != undefined) { element[funName].onChange(env) } } catch (e) {} } //将识别结果写入验证码输入框(自定义规则) function writeIn1(ans){ ans = ans.replace(/\s+/g,""); if (input.tagName == "TEXTAREA") { input.innerHTML = ans; } else { input.value = ans; if (typeof(InputEvent)!=="undefined"){ input.value = ans; input.dispatchEvent(new InputEvent('input')); var eventList = ['input', 'change', 'focus', 'keypress', 'keyup', 'keydown', 'select']; for (var i = 0; i < eventList.length; i++) { fire(input, eventList[i]); } FireForReact(input, 'change'); input.value = ans; } else if(KeyboardEvent) { input.dispatchEvent(new KeyboardEvent("input")); } } } //判断当前页面是否存在规则,返回布尔值 -> 改为本地存储 function compareUrl(){ return new Promise((resolve, reject) => { try { var currentUrl = window.location.href.split("?")[0]; let allRules = GM_getValue("localRules", {}); localRules = allRules[currentUrl]; if (localRules) { resolve(true); } else { localRules = {}; resolve(false); } } catch (error) { console.error("compareUrl: 读取本地规则失败:", error); localRules = {}; reject(error); } }); } //开始识别 function start(){ compareUrl().then((isExist) => { if (isExist) { exist = true; noRuleFound = false; /* if (localRules["type"] == "img") { } else if (localRules["type"] == "canvas") { } */ } else { noRuleFound = true; } }).catch(error => { console.error("start: compareUrl rejected:", error); noRuleFound = true; console.warn("【自动识别填充验证码】加载本地规则失败,脚本停止运行此页面。"); }); } //页面变化执行函数 - Now primarily finds elements and attaches focus listener function pageChange(){ if (focusListenerAttached) { return; } if (exist && localRules && localRules.type) { let foundElement, foundInput; let currentImgIndex, currentInputIndex; if (localRules["type"] == "img") { currentImgIndex = localRules["img"]; currentInputIndex = localRules["input"]; foundElement = document.getElementsByTagName('img')[currentImgIndex]; if (localRules["inputType"] == "textarea") { foundInput = document.getElementsByTagName('textarea')[currentInputIndex]; } else { let inputs = document.getElementsByTagName('input'); if (inputs[0] && (inputs[0].id == "_w_simile" || inputs[0].id == "black_node")) { let adjustedIndex = parseInt(currentInputIndex) + 1; foundInput = inputs[adjustedIndex]; } else { foundInput = inputs[currentInputIndex]; } } if (foundElement instanceof HTMLImageElement && (foundInput instanceof HTMLInputElement || foundInput instanceof HTMLTextAreaElement)) { element = foundElement; input = foundInput; if (!focusListenerAttached) { console.log('[调试] pageChange (img rule): Attaching focus listener to input:', input); input.addEventListener('focus', handleInputFocus); focusListenerAttached = true; } } else { } } else if (localRules["type"] == "canvas") { currentImgIndex = localRules["img"]; currentInputIndex = localRules["input"]; foundElement = document.getElementsByTagName('canvas')[currentImgIndex]; if (localRules["inputType"] == "textarea") { foundInput = document.getElementsByTagName('textarea')[currentInputIndex]; } else { let inputs = document.getElementsByTagName('input'); if (inputs[0] && (inputs[0].id == "_w_simile" || inputs[0].id == "black_node")) { let adjustedIndex = parseInt(currentInputIndex) + 1; foundInput = inputs[adjustedIndex]; } else { foundInput = inputs[currentInputIndex]; } } if (foundElement instanceof HTMLCanvasElement && (foundInput instanceof HTMLInputElement || foundInput instanceof HTMLTextAreaElement)) { element = foundElement; input = foundInput; if (!focusListenerAttached) { console.log('[调试] pageChange (canvas rule): Attaching focus listener to input:', input); input.addEventListener('focus', handleInputFocus); focusListenerAttached = true; } } else { } } } else if (!exist) { } } function handleInputFocus() { console.log('[调试] Input focused, triggering recognition check. Input:', input); if (!localRules || !localRules.type) { console.warn('[调试] handleInputFocus: No local rule or rule type found, cannot proceed.'); return; } if (localRules.type === 'img') { if(isCORS()) { console.log('[调试] handleInputFocus (img): CORS detected, calling p2 first.'); p2().then(() => { codeByRule(); }).catch(err => console.error("p2 failed in handleInputFocus (img):", err)); } else { codeByRule(); } } else if (localRules.type === 'canvas') { canvasRule(); } } function topNotice(msg){ var div = document.createElement('div'); div.id = 'topNotice'; div.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 5%; z-index: 9999999999; background: rgba(117,140,148,1); display: flex; justify-content: center; align-items: center; color: #fff; font-family: "Microsoft YaHei"; text-align: center;'; div.innerHTML = msg; div.style.fontSize = 'medium'; document.body.appendChild(div); setTimeout(function(){ document.body.removeChild(document.getElementById('topNotice')); }, 3500); } GM_addStyle(` #captchaHelperSettingsPanel { position: fixed; top: 50%; /* Center vertically */ left: 50%; transform: translate(-50%, -50%); /* Center horizontally */ width: 750px; /* Increased width */ max-width: 90%; /* Max width for smaller screens */ max-height: 80vh; /* Limit height */ background-color: #f8f9fa; /* Lighter background */ border: none; /* Remove border */ border-radius: 12px; /* More rounded corners */ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); /* Softer shadow */ z-index: 9999999999; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; /* System fonts */ color: #343a40; /* Darker text */ display: flex; /* Use flexbox for layout */ flex-direction: column; /* Stack header, content, footer */ overflow: hidden; /* Prevent content overflow */ } .settingsPanelHeader { padding: 16px 24px; /* More padding */ background-color: #ffffff; /* White header */ border-bottom: 1px solid #dee2e6; /* Lighter border */ border-radius: 12px 12px 0 0; display: flex; justify-content: space-between; align-items: center; font-size: 1.1rem; /* Slightly larger font */ font-weight: 600; /* Bolder */ color: #495057; /* Header text color */ } .settingsPanelCloseButton { background: none; border: none; font-size: 24px; font-weight: normal; /* Less bold */ cursor: pointer; color: #adb5bd; /* Lighter close button */ padding: 5px; line-height: 1; } .settingsPanelCloseButton:hover { color: #495057; /* Darken on hover */ } .settingsPanelContent { padding: 24px; /* More padding */ background-color: #f8f9fa; /* Match panel background */ overflow-y: auto; /* Enable scrolling for content */ flex-grow: 1; /* Allow content to take available space */ } .settingsSection { margin-bottom: 24px; padding-bottom: 16px; /* Space below section */ border-bottom: 1px solid #e9ecef; /* Separator line */ } .settingsSection:last-child { margin-bottom: 0; /* No margin for last section */ border-bottom: none; /* No line for last section */ } .settingsSection h3 { margin-bottom: 12px; font-size: 1rem; /* Standard font size */ font-weight: 600; color: #495057; } .settingsSection p { /* Style paragraph text */ margin-bottom: 12px; line-height: 1.6; color: #6c757d; } .settingsButton { padding: 10px 18px; /* Larger button */ border: none; /* Remove border */ background-color: #007bff; color: #ffffff; border-radius: 6px; /* Slightly rounded */ cursor: pointer; font-size: 0.9rem; font-weight: 500; transition: background-color 0.2s ease; /* Smooth transition */ display: inline-flex; /* Use inline-flex for button layout */ align-items: center; /* Vertically center content */ justify-content: center; /* Horizontally center content */ } .settingsButton:hover { background-color: #0056b3; } /* Add a subtle style for disabled buttons if needed */ .settingsButton:disabled { background-color: #ced4da; cursor: not-allowed; } .settingsInput { /* Style input fields if you add them later */ padding: 10px 12px; border: 1px solid #ced4da; border-radius: 6px; width: 100%; /* Full width */ box-sizing: border-box; /* Include padding in width */ margin-bottom: 12px; font-size: 0.9rem; } .settingsInput:focus { border-color: #80bdff; outline: none; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } `); function showSettingsPanel() { if (document.getElementById('captchaHelperSettingsPanel')) { return; } const panel = document.createElement('div'); panel.id = 'captchaHelperSettingsPanel'; const header = document.createElement('div'); header.className = 'settingsPanelHeader'; header.innerHTML = '自动识别填充验证码 - 设置'; const closeButton = document.createElement('button'); closeButton.className = 'settingsPanelCloseButton'; closeButton.innerHTML = '×'; closeButton.onclick = () => { panel.remove(); }; header.appendChild(closeButton); const content = document.createElement('div'); content.className = 'settingsPanelContent'; content.innerHTML = '
正在加载设置...
'; panel.appendChild(header); panel.appendChild(content); document.body.appendChild(panel); renderSettingsContent(content); } function renderSettingsContent(contentElement) { contentElement.innerHTML = ''; const tokenSection = document.createElement('div'); tokenSection.className = 'settingsSection'; tokenSection.innerHTML = '规则列表将显示在此处。
'; contentElement.appendChild(rulesSection); renderRulesTable(rulesSection); const blacklistSection = document.createElement('div'); blacklistSection.className = 'settingsSection'; blacklistSection.innerHTML = '黑名单列表将显示在此处。
'; contentElement.appendChild(blacklistSection); renderBlacklistTable(blacklistSection); } function exportAllData(defaultFilename) { const rulesData = GM_getValue('localRules', {}); const blacklistData = GM_getValue('blackList', []); const hasRules = rulesData && Object.keys(rulesData).length > 0; const hasBlacklist = blacklistData && blacklistData.length > 0; if (!hasRules && !hasBlacklist) { alert('没有可导出的配置(规则和黑名单均为空)。'); return; } const combinedData = { rules: rulesData, blacklist: blacklistData }; try { const jsonData = JSON.stringify(combinedData, null, 2); const blob = new Blob([jsonData], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = defaultFilename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); topNotice('配置已导出为 ' + defaultFilename); } catch (error) { alert('导出配置时发生错误。'); console.error('导出配置失败:', error); } } function renderRulesTable(containerElement) { containerElement.innerHTML = '暂无本地规则。
'; return; } const table = document.createElement('table'); table.style.width = '100%'; table.style.borderCollapse = 'collapse'; table.style.marginTop = '10px'; table.style.tableLayout = 'fixed'; const thead = table.createTHead(); const headerRow = thead.insertRow(); const headers = ['URL (部分)', '元素类型', '验证码类型', '云码类型', '操作']; headers.forEach((text, index) => { const th = document.createElement('th'); th.textContent = text; th.style.border = '1px solid #ddd'; th.style.padding = '10px 12px'; th.style.textAlign = 'left'; th.style.backgroundColor = '#f2f2f2'; th.style.verticalAlign = 'middle'; if (index === 0) th.style.width = '40%'; else if (index >= 1 && index <= 3) th.style.width = '15%'; else if (index === 4) { th.style.textAlign = 'center'; th.style.width = '15%'; } //if (index === 4) th.style.textAlign = 'center'; headerRow.appendChild(th); }); const tbody = table.createTBody(); urls.forEach(url => { const rule = allRules[url]; const row = tbody.insertRow(); const urlCell = row.insertCell(); urlCell.textContent = url.length > 50 ? url.substring(0, 47) + '...' : url; urlCell.title = url; urlCell.style.border = '1px solid #ddd'; urlCell.style.padding = '10px 12px'; urlCell.style.wordBreak = 'break-all'; urlCell.style.verticalAlign = 'middle'; const typeCell = row.insertCell(); typeCell.textContent = rule.type || 'N/A'; typeCell.style.border = '1px solid #ddd'; typeCell.style.padding = '10px 12px'; typeCell.style.verticalAlign = 'middle'; const captchaTypeCell = row.insertCell(); captchaTypeCell.textContent = rule.captchaType === 'math' ? '算术' : '字符'; captchaTypeCell.style.border = '1px solid #ddd'; captchaTypeCell.style.padding = '10px 12px'; captchaTypeCell.style.verticalAlign = 'middle'; const ymTypeCell = row.insertCell(); ymTypeCell.textContent = rule.ymType || (rule.captchaType === 'math' ? '50100' : '10103 (默认)'); ymTypeCell.style.border = '1px solid #ddd'; ymTypeCell.style.padding = '10px 12px'; ymTypeCell.style.verticalAlign = 'middle'; const actionCell = row.insertCell(); actionCell.style.border = '1px solid #ddd'; actionCell.style.padding = '10px 12px'; actionCell.style.textAlign = 'center'; actionCell.style.verticalAlign = 'middle'; const deleteButton = document.createElement('button'); deleteButton.textContent = '删除'; deleteButton.className = 'settingsButton'; deleteButton.style.backgroundColor = '#dc3545'; deleteButton.onclick = () => { if (confirm(`确定要删除规则 ${url} 吗?`)) { delR({ url: url }).then(res => { if (res.status === 200) { topNotice('规则已删除'); renderRulesTable(containerElement); } else { topNotice('删除规则失败'); } }).catch(err => { topNotice('删除规则时出错'); console.error("删除规则时出错:", err); }); } }; actionCell.appendChild(deleteButton); }); containerElement.appendChild(table); } function renderBlacklistTable(containerElement) { containerElement.innerHTML = '