// ==UserScript== // @name DDDD OCR WEB - 验证码自动识别 // @namespace https://github.com/MakotoArai-CN/ddddocr-webjs // @version 1.0.2-beta-build1764699797247 // @author MakotoArai-CN // @description 自动检测并识别页面验证码,自动填充到输入框。首次使用需设置白名单,会自动下载约50MB模型文件以及20MB左右的ONNX推理运行时文件,但是不推荐自动下载,可能很慢,建议手动下载并上传(详见项目文档)。如果弹出窗口提示授权请授权给脚本。 // @license MIT // @icon data:image/svg+xml,🔤 // @match *://*/* // @require https://cdnjs.cloudflare.com/ajax/libs/onnxruntime-web/1.17.0/ort.min.js // @connect cdn.jsdelivr.net // @connect unpkg.com // @connect cdnjs.cloudflare.com // @connect fastly.jsdelivr.net // @connect registry.npmmirror.com // @connect raw.githubusercontent.com // @connect ghproxy.com // @connect ghfast.top // @connect mirror.ghproxy.com // @connect raw.kkgithub.com // @connect github.moeyy.xyz // @connect ghps.cc // @connect cors.isteed.cc // @connect raw.githubusercontents.com // @grant GM_getValue // @grant GM_notification // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_xmlhttpRequest // ==/UserScript== (function () { 'use strict'; const t={MODEL_VERSION:"1.0.2",MODEL_REPO:"MakotoArai-CN/ddddocr-webjs",MODEL_BRANCH:"main",MODEL_PATH:"public/common.onnx",CHARSETS_PATH:"public/charsets.json",WASM_VERSION:"1.17.0",CACHE_DURATION:2592e6,CAPTCHA_KEYWORDS:["captcha","verify","code","vcode","authcode","验证码","checkcode","yzm","capimg","signCaptcha"],MIN_CAPTCHA_WIDTH:40,MIN_CAPTCHA_HEIGHT:20,MAX_CAPTCHA_WIDTH:500,MAX_CAPTCHA_HEIGHT:200,AUTO_DETECT_INTERVAL:2e3,GITHUB_MIRRORS:["https://raw.githubusercontent.com","https://ghproxy.com/https://raw.githubusercontent.com","https://ghfast.top/https://raw.githubusercontent.com","https://mirror.ghproxy.com/https://raw.githubusercontent.com","https://raw.kkgithub.com","https://github.moeyy.xyz/https://raw.githubusercontent.com","https://ghps.cc/https://raw.githubusercontent.com","https://cors.isteed.cc/github.com/J3n5en/ddddocr-js/raw/main","https://raw.githubusercontents.com"],CDN_SOURCES:["https://cdn.jsdelivr.net","https://unpkg.com","https://cdnjs.cloudflare.com","https://fastly.jsdelivr.net","https://registry.npmmirror.com"]},e={autoDetect:false,captchaSelector:"",inputSelector:"",useLocalModel:false,localModelPath:"",localCharsetsPath:"",autoDownload:true,enableWhitelist:true,whitelist:[],useUploadedModel:false,enableSlideCaptcha:false,enableRotateCaptcha:false,enableClickCaptcha:false,slideDebugMode:false},n="ddddocr_config";function getConfig(){const t=GM_getValue(n);return t?{...e,...t}:e}function saveConfig(t){const e=getConfig();GM_setValue(n,{...e,...t});}function isWhitelisted(){const t=getConfig();if(!t.enableWhitelist)return true;if(!t.whitelist||0===t.whitelist.length)return false;const e=window.location.hostname;return t.whitelist.some(t=>new RegExp("^"+t.replace(/\*/g,".*")+"$","i").test(e))}function shouldExecuteScript(){const t=getConfig();if(t.enableWhitelist){if(!t.whitelist||0===t.whitelist.length)return false;if(!isWhitelisted())return false}return true}const i="ddddocr_model_cache",o="ddddocr_uploaded_model";class ModelCache{constructor(){this.dbName="DdddOCRDB",this.storeName="modelStore",this.db=null;}async init(){return new Promise((t,e)=>{const n=indexedDB.open(this.dbName,1);n.onerror=()=>e(n.error),n.onsuccess=()=>{this.db=n.result,t();},n.onupgradeneeded=t=>{const e=t.target.result;e.objectStoreNames.contains(this.storeName)||e.createObjectStore(this.storeName);};})}async get(){return this.db||await this.init(),new Promise((e,n)=>{const o=this.db.transaction([this.storeName],"readonly").objectStore(this.storeName).get(i);o.onerror=()=>n(o.error),o.onsuccess=()=>{const n=o.result;if(n)return Date.now()-n.timestamp>t.CACHE_DURATION||n.version!==t.MODEL_VERSION?(this.delete(),void e(null)):void e(n);e(null);};})}async set(e,n){this.db||await this.init();const o={model:e,charsets:n,timestamp:Date.now(),version:t.MODEL_VERSION};return new Promise((t,e)=>{const n=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName).put(o,i);n.onerror=()=>e(n.error),n.onsuccess=()=>t();})}async delete(){return this.db||await this.init(),new Promise((t,e)=>{const n=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName).delete(i);n.onerror=()=>e(n.error),n.onsuccess=()=>t();})}async getUploadedModel(){return this.db||await this.init(),new Promise((t,e)=>{const n=this.db.transaction([this.storeName],"readonly").objectStore(this.storeName).get(o);n.onerror=()=>e(n.error),n.onsuccess=()=>{t(n.result||null);};})}async setUploadedModel(t,e){this.db||await this.init();const n={model:t,charsets:e,timestamp:Date.now(),version:"uploaded"};return new Promise((t,e)=>{const i=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName).put(n,o);i.onerror=()=>e(i.error),i.onsuccess=()=>t();})}async deleteUploadedModel(){return this.db||await this.init(),new Promise((t,e)=>{const n=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName).delete(o);n.onerror=()=>e(n.error),n.onsuccess=()=>t();})}}function downloadFile(t,e=3e4){return new Promise((n,i)=>{GM_xmlhttpRequest({method:"GET",url:t,responseType:"arraybuffer",timeout:e,headers:{"Cache-Control":"max-age=2592000"},onload:t=>{200===t.status?n(t.response):i(new Error(`HTTP ${t.status}`));},onerror:t=>i(t),ontimeout:()=>i(new Error("下载超时"))});})}function downloadJSON(t,e=3e4){return new Promise((n,i)=>{GM_xmlhttpRequest({method:"GET",url:t,responseType:"json",timeout:e,headers:{"Cache-Control":"max-age=2592000"},onload:t=>{200===t.status?n(t.response):i(new Error(`HTTP ${t.status}`));},onerror:t=>i(t),ontimeout:()=>i(new Error("下载超时"))});})}function buildURL(e,n){return e.includes("jsdelivr")?`${e}/${t.MODEL_REPO}@${t.MODEL_BRANCH}/${n}`:`${e}/${t.MODEL_REPO}/${t.MODEL_BRANCH}/${n}`}class ImageProcessor{static extractImageFromElement(t){const e=document.createElement("canvas");e.width=t.naturalWidth||t.width,e.height=t.naturalHeight||t.height;const n=e.getContext("2d");n.fillStyle="#FFFFFF",n.fillRect(0,0,e.width,e.height),n.drawImage(t,0,0);const i=n.getImageData(0,0,e.width,e.height);return {data:this.toGrayscale(i.data),width:e.width,height:e.height}}static async loadImage(t){if(t instanceof HTMLImageElement)return this.extractImageFromElement(t);const e=new Image;if(e.crossOrigin="anonymous","string"==typeof t)try{const n=await this.fetchImageAsBlob(t);e.src=URL.createObjectURL(n);}catch(r){e.src=t;}else e.src=URL.createObjectURL(t);await new Promise((t,n)=>{e.onload=t,e.onerror=()=>n(new Error("图片加载失败")),setTimeout(()=>n(new Error("图片加载超时")),1e4);});const n=document.createElement("canvas");n.width=e.width,n.height=e.height;const i=n.getContext("2d");i.fillStyle="#FFFFFF",i.fillRect(0,0,e.width,e.height),i.drawImage(e,0,0);const o=i.getImageData(0,0,e.width,e.height),s=this.toGrayscale(o.data);return "string"!=typeof t&&URL.revokeObjectURL(e.src),{data:s,width:e.width,height:e.height}}static fetchImageAsBlob(t){return new Promise((e,n)=>{GM_xmlhttpRequest({method:"GET",url:t,responseType:"blob",headers:{Referer:window.location.href},timeout:1e4,onload:t=>{200===t.status?e(t.response):n(new Error(`HTTP ${t.status}`));},onerror:n,ontimeout:()=>n(new Error("获取图片超时"))});})}static toGrayscale(t){const e=new Uint8ClampedArray(t.length/4);for(let n=0;ne.includes("jsdelivr")?`${e}/npm/onnxruntime-web@${t.WASM_VERSION}/dist/`:e.includes("unpkg")?`${e}/onnxruntime-web@${t.WASM_VERSION}/dist/`:e.includes("cdnjs")?`${e}/ajax/libs/onnxruntime-web/${t.WASM_VERSION}/`:e.includes("npmmirror")?`${e}/onnxruntime-web/${t.WASM_VERSION}/files/dist/`:`${e}/onnxruntime-web@${t.WASM_VERSION}/dist/`),r=["ort-wasm.wasm","ort-wasm-simd.wasm","ort-wasm-threaded.wasm","ort-wasm-simd-threaded.wasm"],a=new class{constructor(){this.dbName="WASMCacheDB",this.storeName="wasmStore",this.db=null,this.memoryCache=new Map;}async init(){return new Promise((t,e)=>{const n=indexedDB.open(this.dbName,2);n.onerror=()=>e(n.error),n.onsuccess=()=>{this.db=n.result,t();},n.onupgradeneeded=t=>{const e=t.target.result;e.objectStoreNames.contains(this.storeName)||e.createObjectStore(this.storeName);};})}async get(e){return this.memoryCache.has(e)?this.memoryCache.get(e):(this.db||await this.init(),new Promise((n,i)=>{const o=this.db.transaction([this.storeName],"readonly").objectStore(this.storeName).get(e);o.onerror=()=>i(o.error),o.onsuccess=()=>{const i=o.result;if(i){if(Date.now()-i.timestamp>t.CACHE_DURATION||i.version!==t.WASM_VERSION)return this.delete(e),void n(null);this.memoryCache.set(e,i.data),n(i.data);}else n(null);};}))}async set(e,n){this.db||await this.init(),this.memoryCache.set(e,n);const i={data:n,timestamp:Date.now(),version:t.WASM_VERSION};return new Promise((t,n)=>{const o=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName).put(i,e);o.onerror=()=>n(o.error),o.onsuccess=()=>{t();};})}async delete(t){return this.memoryCache.delete(t),this.db||await this.init(),new Promise((e,n)=>{const i=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName).delete(t);i.onerror=()=>n(i.error),i.onsuccess=()=>e();})}async clear(){return this.memoryCache.clear(),this.db||await this.init(),new Promise((t,e)=>{const n=this.db.transaction([this.storeName],"readwrite").objectStore(this.storeName).clear();n.onerror=()=>e(n.error),n.onsuccess=()=>{t();};})}};async function downloadWASM(t){for(let n=0;n{GM_xmlhttpRequest({method:"GET",url:i,responseType:"arraybuffer",timeout:3e4,headers:{Accept:"application/wasm","Cache-Control":"max-age=2592000"},onload:n=>{200===n.status?t(n.response):e(new Error(`HTTP ${n.status}`));},onerror:e,ontimeout:()=>e(new Error("下载超时"))});})}catch(e){if(n===s.length-1)throw new Error(`所有 WASM CDN 均下载失败: ${t}`)}}throw new Error(`下载 WASM 失败: ${t}`)}class DdddOCR{constructor(){this.session=null,this.charsets=[],this.initialized=false,this.ort=null;}async init(){if(!this.initialized)try{if(await async function(){await a.init(),async function(){(await Promise.allSettled(r.map(async t=>{if(await a.get(t))return {filename:t,cached:!0};try{const e=await downloadWASM(t);return await a.set(t,e),{filename:t,cached:!1}}catch(e){return {filename:t,error:e}}}))).filter(t=>"fulfilled"===t.status).length;}().catch(t=>{});const t=window.fetch;window.fetch=async function(e,n){const i="string"==typeof e?e:e instanceof URL?e.href:e.url,o=r.find(t=>i.includes(t));if(!o)return t.call(this,e,n);try{let t=await a.get(o);return t||(t=await downloadWASM(o),a.set(o,t).catch(t=>{})),new Response(t,{status:200,headers:{"Content-Type":"application/wasm","Content-Length":String(t.byteLength)}})}catch(s){return t.call(this,e,n)}};}(),this.ort=await async function(){if("undefined"!=typeof ort)return ort;for(let e=0;e<100;e++){await new Promise(t=>setTimeout(t,100));try{if("undefined"!=typeof ort)return ort}catch(t){}}throw new Error("等待 ort 超时")}(),!this.ort)throw new Error("ONNX Runtime 未找到");this.ort.env.wasm.wasmPaths="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.17.0/dist/",this.ort.env.wasm.numThreads=1,this.ort.env.wasm.simd=!0,this.ort.env.logLevel="warning";const{model:e,charsets:n}=await async function(){const e=getConfig(),n=new ModelCache;if(e.useUploadedModel){const t=await n.getUploadedModel();if(t)return {model:t.model,charsets:t.charsets}}if(!e.autoDownload)throw new Error("自动下载已禁用,请上传模型文件或启用自动下载");const i=await n.get();if(i)return {model:i.model,charsets:i.charsets};let o=null,s=null,r="";for(let d=0;d=this.charsets.length)continue;const t=this.charsets[o];t&&t!==i&&(n.push(t),i=t);}return n.join("")}async destroy(){this.session&&(await this.session.release(),this.session=null),this.initialized=false;}}class AutoDetector{constructor(t,e){this.observer=null,this.processedElements=new WeakMap,this.enabled=false,this.checkInterval=null,this.eventEmitter=null,this.ocr=t,this.eventEmitter=e||null;}start(){this.enabled||(this.enabled=true,this.detectExistingCaptchas(),this.observer=new MutationObserver(t=>{for(const e of t){if(e.addedNodes.forEach(t=>{t instanceof HTMLElement&&this.checkElement(t);}),"attributes"===e.type){const t=e.target;t instanceof HTMLElement&&(t instanceof HTMLImageElement&&"src"===e.attributeName?this.recheckImage(t):t instanceof HTMLCanvasElement?this.recheckCanvas(t):"style"===e.attributeName&&t.style.backgroundImage&&this.recheckDiv(t));}"childList"===e.type&&e.target instanceof SVGElement&&this.recheckSVG(e.target);}}),this.observer.observe(document.body,{childList:true,subtree:true,attributes:true,attributeFilter:["src","style","data-src","href"],characterData:true}),this.startIntervalCheck());}stop(){this.enabled&&(this.enabled=false,this.observer?.disconnect(),this.observer=null,this.checkInterval&&(clearInterval(this.checkInterval),this.checkInterval=null));}startIntervalCheck(){this.checkInterval=window.setInterval(()=>{document.querySelectorAll("canvas").forEach(t=>{this.isCaptchaCanvas(t)&&this.recheckCanvas(t);});},t.AUTO_DETECT_INTERVAL);}getElementHash(t){if(t instanceof HTMLImageElement)return t.src+"_"+t.naturalWidth+"_"+t.naturalHeight;if(t instanceof HTMLCanvasElement)try{return t.toDataURL()}catch(e){return "canvas_"+Date.now()}else {if(t instanceof SVGElement)return t.outerHTML;if(t instanceof HTMLElement&&t.style.backgroundImage)return t.style.backgroundImage}return ""}hasElementChanged(t){const e=this.getElementHash(t),n=this.processedElements.get(t);return !n||e!==n}markElementProcessed(t){const e=this.getElementHash(t);this.processedElements.set(t,e);}detectExistingCaptchas(){document.querySelectorAll("img").forEach(t=>this.checkImage(t)),document.querySelectorAll("canvas").forEach(t=>this.checkCanvas(t)),document.querySelectorAll("svg").forEach(t=>this.checkSVG(t)),document.querySelectorAll('div[style*="background"]').forEach(t=>this.checkDiv(t));}checkElement(t){t instanceof HTMLImageElement&&this.checkImage(t),t instanceof HTMLCanvasElement&&this.checkCanvas(t),t instanceof SVGElement&&this.checkSVG(t),t.style.backgroundImage&&this.checkDiv(t),t.querySelectorAll("img").forEach(t=>this.checkImage(t)),t.querySelectorAll("canvas").forEach(t=>this.checkCanvas(t)),t.querySelectorAll("svg").forEach(t=>this.checkSVG(t)),t.querySelectorAll('div[style*="background"]').forEach(t=>this.checkDiv(t));}async waitForImageLoad(t,e=5e3){return !!(t.complete&&t.naturalWidth>0)||new Promise(n=>{const i=setTimeout(()=>{cleanup(),n(false);},e),onLoad=()=>{cleanup(),n(true);},onError=()=>{cleanup(),n(false);},cleanup=()=>{clearTimeout(i),t.removeEventListener("load",onLoad),t.removeEventListener("error",onError);};t.addEventListener("load",onLoad),t.addEventListener("error",onError),t.complete&&t.naturalWidth>0&&(cleanup(),n(true));})}async recheckImage(t){await this.waitForImageLoad(t)&&t.complete&&t.naturalWidth&&t.naturalHeight&&await this.checkImage(t);}async recheckCanvas(t){await new Promise(t=>requestAnimationFrame(t)),await this.checkCanvas(t);}async recheckSVG(t){await new Promise(t=>requestAnimationFrame(t)),await this.checkSVG(t);}async recheckDiv(t){await new Promise(t=>requestAnimationFrame(t)),await this.checkDiv(t);}async checkImage(t){const e=getConfig();if(e.captchaSelector){if(!t.matches(e.captchaSelector))return}else if(!this.isCaptchaImage(t))return;if(!this.hasElementChanged(t))return;this.eventEmitter?.emit("detect:found",{element:t,type:"img"});const n=this.findNearbyInput(t);if(n&&n.value.trim()&&(n.value="",n.dispatchEvent(new Event("input",{bubbles:true})),n.dispatchEvent(new Event("change",{bubbles:true}))),await this.waitForImageLoad(t)&&t.naturalWidth&&t.naturalHeight)try{this.eventEmitter?.emit("recognize:start",{element:t});const e=await this.ocr.recognize(t);this.markElementProcessed(t),this.eventEmitter?.emit("recognize:complete",{element:t,result:e}),this.fillCaptcha(t,e.text);}catch(i){this.eventEmitter?.emit("recognize:error",{element:t,error:i});}}async checkCanvas(t){const e=getConfig();if(e.captchaSelector){if(!t.matches(e.captchaSelector))return}else if(!this.isCaptchaCanvas(t))return;if(!this.hasElementChanged(t))return;this.eventEmitter?.emit("detect:found",{element:t,type:"canvas"});const n=this.findNearbyInput(t);n&&n.value.trim()&&(n.value="",n.dispatchEvent(new Event("input",{bubbles:true})),n.dispatchEvent(new Event("change",{bubbles:true}))),await new Promise(t=>requestAnimationFrame(t));try{this.eventEmitter?.emit("recognize:start",{element:t});const e=await new Promise((e,n)=>{t.toBlob(t=>{t?e(t):n(new Error("Canvas转换失败"));},"image/png");}),n=await this.ocr.recognize(e);this.markElementProcessed(t),this.eventEmitter?.emit("recognize:complete",{element:t,result:n}),this.fillCaptcha(t,n.text);}catch(i){this.eventEmitter?.emit("recognize:error",{element:t,error:i});}}async checkSVG(t){const e=getConfig();if(e.captchaSelector){if(!t.matches(e.captchaSelector))return}else if(!this.isCaptchaSVG(t))return;if(!this.hasElementChanged(t))return;this.eventEmitter?.emit("detect:found",{element:t,type:"svg"});const n=this.findNearbyInput(t);n&&n.value.trim()&&(n.value="",n.dispatchEvent(new Event("input",{bubbles:true})),n.dispatchEvent(new Event("change",{bubbles:true})));try{this.eventEmitter?.emit("recognize:start",{element:t});const e=(new XMLSerializer).serializeToString(t),n=new Blob([e],{type:"image/svg+xml;charset=utf-8"}),i=URL.createObjectURL(n),o=new Image;o.src=i,await new Promise((t,e)=>{o.onload=t,o.onerror=e,setTimeout(e,5e3);});const s=document.createElement("canvas");s.width=t.clientWidth||150,s.height=t.clientHeight||50,s.getContext("2d").drawImage(o,0,0),URL.revokeObjectURL(i);const r=await new Promise((t,e)=>{s.toBlob(n=>{n?t(n):e(new Error("SVG转换失败"));},"image/png");}),a=await this.ocr.recognize(r);this.markElementProcessed(t),this.eventEmitter?.emit("recognize:complete",{element:t,result:a}),this.fillCaptcha(t,a.text);}catch(i){this.eventEmitter?.emit("recognize:error",{element:t,error:i});}}async checkDiv(t){const e=getConfig();if(e.captchaSelector){if(!t.matches(e.captchaSelector))return}else if(!this.isCaptchaDiv(t))return;const n=t.style.backgroundImage;if(!n)return;if(!this.hasElementChanged(t))return;this.eventEmitter?.emit("detect:found",{element:t,type:"div"});const i=this.findNearbyInput(t);i&&i.value.trim()&&(i.value="",i.dispatchEvent(new Event("input",{bubbles:true})),i.dispatchEvent(new Event("change",{bubbles:true})));try{this.eventEmitter?.emit("recognize:start",{element:t});const e=n.match(/url\(['"]?(.+?)['"]?\)/);if(!e)return;const i=e[1];if(i.startsWith("data:")){const e=i.split(",")[1],n=atob(e),o=new Uint8Array(n.length);for(let t=0;tt.MAX_CAPTCHA_WIDTH||i>t.MAX_CAPTCHA_HEIGHT)return false;const o=(e.src+e.className+e.id+e.alt+(e.getAttribute("data-src")||"")).toLowerCase();return t.CAPTCHA_KEYWORDS.some(t=>o.includes(t))}isCaptchaCanvas(e){const n=e.width,i=e.height;if(nt.MAX_CAPTCHA_WIDTH||i>t.MAX_CAPTCHA_HEIGHT)return false;const o=(e.className+e.id+(e.getAttribute("data-type")||"")).toLowerCase();return t.CAPTCHA_KEYWORDS.some(t=>o.includes(t))}isCaptchaSVG(e){const n=e.clientWidth||parseInt(e.getAttribute("width")||"0"),i=e.clientHeight||parseInt(e.getAttribute("height")||"0");if(nt.MAX_CAPTCHA_WIDTH||i>t.MAX_CAPTCHA_HEIGHT)return false;const o=(e.className.baseVal+e.id).toLowerCase();return t.CAPTCHA_KEYWORDS.some(t=>o.includes(t))}isCaptchaDiv(e){const n=e.clientWidth,i=e.clientHeight;if(nt.MAX_CAPTCHA_WIDTH||i>t.MAX_CAPTCHA_HEIGHT)return false;const o=(e.className+e.id).toLowerCase();return t.CAPTCHA_KEYWORDS.some(t=>o.includes(t))}fillCaptcha(t,e){const n=getConfig();let i=null;n.inputSelector&&(i=document.querySelector(n.inputSelector)),i||(i=this.findNearbyInput(t)),i&&(i.value=e,i.dispatchEvent(new Event("input",{bubbles:true})),i.dispatchEvent(new Event("change",{bubbles:true})),this.highlightInput(i),"undefined"!=typeof GM_notification&&GM_notification({title:"验证码已自动填充",text:`识别结果: ${e}`,timeout:3e3}));}findNearbyInput(t){const e=t.closest("form");if(e){const t=e.querySelectorAll('input[type="text"], input[type="password"], input:not([type])');for(const e of t)if(this.isLikelyCaptchaInput(e))return e}let n=t.parentElement;for(let a=0;a<5&&n;a++){const t=n.querySelectorAll('input[type="text"], input[type="password"], input:not([type])');for(const e of t)if(this.isLikelyCaptchaInput(e))return e;n=n.parentElement;}const i=document.querySelectorAll('input[type="text"], input[type="password"], input:not([type])');let o=null,s=Infinity;const r=t.getBoundingClientRect();for(const a of i){if(!this.isLikelyCaptchaInput(a))continue;const t=a.getBoundingClientRect(),e=Math.sqrt(Math.pow(t.left-r.left,2)+Math.pow(t.top-r.top,2));en.includes(t))}highlightInput(t){const e=t.style.cssText;t.style.cssText+="\n transition: all 0.3s ease;\n box-shadow: 0 0 10px rgba(255, 105, 180, 0.8);\n border-color: #FF69B4 !important;\n ",setTimeout(()=>{t.style.cssText=e;},2e3);}}const d=class{static injectStyles(){if(this.stylesInjected)return;const t=document.createElement("style");t.textContent="\n .ddddocr-dialog-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n z-index: 2147483647;\n display: flex;\n align-items: center;\n justify-content: center;\n animation: ddddocr-fade-in 0.3s ease;\n }\n\n .ddddocr-dialog {\n background: white;\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n max-width: 500px;\n width: 90%;\n max-height: 80vh;\n overflow: hidden;\n animation: ddddocr-scale-in 0.3s ease;\n }\n\n .ddddocr-dialog-header {\n background: #4A90E2;\n color: white;\n padding: 20px 24px;\n font-size: 18px;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .ddddocr-dialog-body {\n padding: 24px;\n max-height: calc(80vh - 140px);\n overflow-y: auto;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n\n .ddddocr-dialog-content {\n color: #333;\n font-size: 14px;\n line-height: 1.6;\n white-space: pre-wrap;\n }\n\n .ddddocr-dialog-footer {\n padding: 16px 24px;\n border-top: 1px solid #E8F0FE;\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n }\n\n .ddddocr-dialog-button {\n padding: 10px 24px;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.3s;\n }\n\n .ddddocr-dialog-button.primary {\n background: #4A90E2;\n color: white;\n }\n\n .ddddocr-dialog-button.primary:hover {\n background: #357ABD;\n }\n\n .ddddocr-dialog-button.secondary {\n background: #E8F0FE;\n color: #4A90E2;\n }\n\n .ddddocr-dialog-button.secondary:hover {\n background: #D0E2F5;\n }\n\n @keyframes ddddocr-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n @keyframes ddddocr-scale-in {\n from {\n opacity: 0;\n transform: scale(0.9);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n }\n ",document.head.appendChild(t),this.stylesInjected=true;}static show(t){this.injectStyles(),this.close();const e=document.createElement("div");e.className="ddddocr-dialog-overlay";const n=document.createElement("div");n.className="ddddocr-dialog",n.innerHTML=`\n
\n ${t.icon||"ℹ️"} ${t.title}\n
\n
\n
${t.content}
\n
\n \n `,e.appendChild(n),document.body.appendChild(e),this.container=e;const i=n.querySelector(".ddddocr-dialog-button");i?.addEventListener("click",()=>{t.onConfirm?.(),this.close();}),e.addEventListener("click",t=>{t.target===e&&this.close();});}static confirm(t){this.injectStyles(),this.close();const e=document.createElement("div");e.className="ddddocr-dialog-overlay";const n=document.createElement("div");n.className="ddddocr-dialog",n.innerHTML=`\n
\n ${t.icon||"❓"} ${t.title}\n
\n
\n
${t.content}
\n
\n \n `,e.appendChild(n),document.body.appendChild(e),this.container=e;const i=n.querySelector(".confirm-btn"),o=n.querySelector(".cancel-btn");i?.addEventListener("click",()=>{t.onConfirm?.(),this.close();}),o?.addEventListener("click",()=>{t.onCancel?.(),this.close();}),e.addEventListener("click",n=>{n.target===e&&(t.onCancel?.(),this.close());});}static close(){this.container?.remove(),this.container=null;}};d.container=null,d.stylesInjected=false;let c=d;class SettingsUI{constructor(){this.container=null,this.isVisible=false,this.onConfigChange=()=>{},this.createStyles();}createStyles(){const t=document.createElement("style");t.textContent="\n .ddddocr-settings-container {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 480px;\n max-height: 80vh;\n background: #FFFFFF;\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);\n z-index: 2147483647;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n overflow: hidden;\n display: none;\n }\n .ddddocr-settings-header {\n background: #4A90E2;\n color: white;\n padding: 20px 24px;\n font-size: 18px;\n font-weight: 600;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n .ddddocr-settings-close {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.2);\n border: none;\n color: white;\n font-size: 20px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.3s;\n }\n .ddddocr-settings-close:hover {\n background: rgba(255, 255, 255, 0.3);\n }\n .ddddocr-settings-body {\n padding: 24px;\n max-height: calc(80vh - 80px);\n overflow-y: auto;\n }\n .ddddocr-setting-group {\n margin-bottom: 24px;\n padding: 16px;\n background: #F8FBFF;\n border-radius: 12px;\n border: 1px solid #E8F0FE;\n }\n .ddddocr-setting-group-title {\n font-size: 14px;\n font-weight: 600;\n color: #4A90E2;\n margin-bottom: 12px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n .ddddocr-setting-item {\n margin-bottom: 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n .ddddocr-setting-item:last-child {\n margin-bottom: 0;\n }\n .ddddocr-setting-label {\n font-size: 14px;\n color: #333;\n flex: 1;\n }\n .ddddocr-setting-desc {\n font-size: 12px;\n color: #666;\n margin-top: 4px;\n }\n .ddddocr-switch {\n position: relative;\n width: 48px;\n height: 24px;\n background: #CBD5E0;\n border-radius: 12px;\n cursor: pointer;\n transition: background 0.3s;\n }\n .ddddocr-switch.active {\n background: #FF69B4;\n }\n .ddddocr-switch-slider {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 20px;\n height: 20px;\n background: white;\n border-radius: 50%;\n transition: transform 0.3s;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n .ddddocr-switch.active .ddddocr-switch-slider {\n transform: translateX(24px);\n }\n .ddddocr-input {\n padding: 8px 12px;\n border: 2px solid #E8F0FE;\n border-radius: 8px;\n font-size: 14px;\n width: 100%;\n margin-top: 8px;\n transition: border-color 0.3s;\n }\n .ddddocr-input:focus {\n outline: none;\n border-color: #4A90E2;\n }\n .ddddocr-file-input {\n display: none;\n }\n .ddddocr-file-label {\n display: inline-block;\n padding: 8px 16px;\n background: #E8F0FE;\n color: #4A90E2;\n border-radius: 8px;\n font-size: 13px;\n cursor: pointer;\n transition: background 0.3s;\n margin-top: 8px;\n }\n .ddddocr-file-label:hover {\n background: #D0E2F5;\n }\n .ddddocr-file-name {\n font-size: 12px;\n color: #666;\n margin-top: 6px;\n }\n .ddddocr-textarea {\n padding: 8px 12px;\n border: 2px solid #E8F0FE;\n border-radius: 8px;\n font-size: 14px;\n width: 100%;\n margin-top: 8px;\n min-height: 80px;\n resize: vertical;\n font-family: 'Courier New', monospace;\n transition: border-color 0.3s;\n }\n .ddddocr-textarea:focus {\n outline: none;\n border-color: #4A90E2;\n }\n .ddddocr-button {\n padding: 10px 20px;\n background: #4A90E2;\n color: white;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.3s;\n }\n .ddddocr-button:hover {\n background: #357ABD;\n }\n .ddddocr-button.secondary {\n background: #FF69B4;\n }\n .ddddocr-button.secondary:hover {\n background: #FF1493;\n }\n .ddddocr-button.danger {\n background: #E74C3C;\n }\n .ddddocr-button.danger:hover {\n background: #C0392B;\n }\n .ddddocr-button-group {\n display: flex;\n gap: 12px;\n margin-top: 16px;\n }\n .ddddocr-info {\n padding: 12px;\n background: #FFF0F5;\n border: 1px solid #FFB6C1;\n border-radius: 8px;\n font-size: 12px;\n color: #666;\n margin-top: 12px;\n }\n .ddddocr-settings-visible {\n display: block !important;\n animation: ddddocr-fade-in 0.3s ease;\n }\n @keyframes ddddocr-fade-in {\n from {\n opacity: 0;\n transform: translate(-50%, -50%) scale(0.9);\n }\n to {\n opacity: 1;\n transform: translate(-50%, -50%) scale(1);\n }\n }\n ",document.head.appendChild(t);}async createContainer(){this.container=document.createElement("div"),this.container.className="ddddocr-settings-container";const t=getConfig(),e=new ModelCache,n=await e.getUploadedModel(),i=!!n;this.container.innerHTML=`\n
\n 🔧 DDDD OCR 设置\n \n
\n
\n
\n
模型设置
\n \n ${i?`\n
\n ✅ 已上传模型: ${(n.model.byteLength/1024/1024).toFixed(2)} MB\n
\n `:""}\n \n
\n
\n
使用上传的模型
\n
上传 common.onnx 和 charsets.json
\n
\n
\n
\n
\n
\n
\n \n
未选择文件
\n \n
未选择文件
\n
\n \n \n
\n
\n
\n
\n
自动下载模型
\n
首次使用时自动下载模型文件
\n
\n
\n
\n
\n
\n
\n
\n
功能设置
\n
\n
\n
自动检测并填充验证码
\n
自动识别页面中的验证码并填充
\n
\n
\n
\n
\n
\n
\n
\n
验证码选择器
\n
CSS选择器,留空则自动检测
\n
\n
\n \n
\n
\n
输入框选择器
\n
CSS选择器,留空则自动查找
\n
\n
\n \n
\n
\n
站点白名单
\n
\n
\n
启用白名单
\n
仅在指定站点启用脚本
\n
\n
\n
\n
\n
\n
\n \n
\n 支持通配符 * 匹配子域名。当前站点:${window.location.hostname}\n
\n
\n
\n
\n \n \n
\n
\n `,document.body.appendChild(this.container),this.bindEvents();}bindEvents(){this.container&&(this.container.querySelector(".ddddocr-settings-close")?.addEventListener("click",()=>{this.hide();}),this.container.querySelectorAll(".ddddocr-switch").forEach(t=>{t.addEventListener("click",t=>{const e=t.currentTarget,n=e.dataset.setting,i=e.classList.toggle("active");if("useUploadedModel"===n){const t=this.container.querySelector("#uploadModelArea"),e=this.container.querySelector("#autoDownloadItem");t.style.display=i?"block":"none",e.style.display=i?"none":"flex";}"enableWhitelist"===n&&(this.container.querySelector("#whitelistSettings").style.display=i?"block":"none");});}),this.container.querySelector("#modelFileInput")?.addEventListener("change",t=>{const e=t.target.files?.[0],n=this.container.querySelector("#modelFileName");e&&(n.textContent=`✅ ${e.name} (${(e.size/1024/1024).toFixed(2)} MB)`);}),this.container.querySelector("#charsetsFileInput")?.addEventListener("change",t=>{const e=t.target.files?.[0],n=this.container.querySelector("#charsetsFileName");e&&(n.textContent=`✅ ${e.name}`);}),this.container.querySelector("#uploadModelBtn")?.addEventListener("click",async()=>{const t=this.container.querySelector("#modelFileInput").files?.[0],e=this.container.querySelector("#charsetsFileInput").files?.[0];if(t&&e)try{await async function(t,e){const n=await t.arrayBuffer(),i=await e.text(),o=JSON.parse(i),s=new ModelCache;await s.setUploadedModel(n,o);}(t,e),saveConfig({useUploadedModel:!0}),c.show({title:"上传成功",content:"模型文件已保存,请刷新页面以应用",icon:"✅"});}catch(n){c.show({title:"上传失败",content:String(n),icon:"❌"});}else c.show({title:"缺少文件",content:"请选择模型文件和字符集文件",icon:"⚠️"});}),this.container.querySelector("#deleteUploadedBtn")?.addEventListener("click",()=>{c.confirm({title:"删除模型",content:"确定要删除已上传的模型吗?",icon:"🗑️",confirmText:"确定删除",cancelText:"取消",onConfirm:async()=>{try{await async function(){const t=new ModelCache;await t.deleteUploadedModel();}(),saveConfig({useUploadedModel:!1}),c.show({title:"删除成功",content:"已删除上传的模型",icon:"✅"}),this.hide();}catch(t){c.show({title:"删除失败",content:String(t),icon:"❌"});}}});}),this.container.querySelector("#saveSettingsBtn")?.addEventListener("click",()=>{this.saveSettings();}),this.container.querySelector("#resetSettingsBtn")?.addEventListener("click",()=>{c.confirm({title:"重置设置",content:"确定要重置所有设置吗?页面将自动刷新。",icon:"⚠️",confirmText:"确定重置",cancelText:"取消",onConfirm:()=>{this.resetSettings();}});}));}saveSettings(){if(!this.container)return;const t={};this.container.querySelectorAll(".ddddocr-switch").forEach(e=>{const n=e.dataset.setting;n&&(t[n]=e.classList.contains("active"));}),this.container.querySelectorAll("input[data-setting]").forEach(e=>{const n=e.dataset.setting;n&&(t[n]=e.value);}),this.container.querySelectorAll("textarea[data-setting]").forEach(e=>{if("whitelist"===e.dataset.setting){const n=e.value;t.whitelist=n.split("\n").filter(t=>t.trim());}}),saveConfig(t),this.onConfigChange(getConfig()),"undefined"!=typeof GM_notification&&GM_notification({title:"设置已保存",text:"配置已成功保存",timeout:2e3}),this.hide();}resetSettings(){saveConfig({autoDetect:false,captchaSelector:"",inputSelector:"",useUploadedModel:false,autoDownload:true,enableWhitelist:false,whitelist:[]}),this.hide(),window.location.reload();}async show(){this.container||await this.createContainer(),this.isVisible=true,this.container.classList.add("ddddocr-settings-visible");}hide(){this.container&&(this.isVisible=false,this.container.classList.remove("ddddocr-settings-visible"));}toggle(){this.isVisible?this.hide():this.show();}setOnConfigChange(t){this.onConfigChange=t;}}class LoadingIndicator{constructor(){this.container=null,this.isVisible=false,this.create();}create(){const t=document.createElement("style");t.textContent="\n .ddddocr-loading-indicator {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 4px;\n z-index: 2147483647;\n overflow: hidden;\n background: linear-gradient(90deg, \n #4A90E2 0%, \n #FF69B4 25%, \n #87CEEB 50%, \n #FF69B4 75%, \n #4A90E2 100%);\n background-size: 200% 100%;\n animation: ddddocr-wave 3s linear infinite;\n opacity: 0;\n transition: opacity 0.3s;\n }\n\n .ddddocr-loading-indicator.visible {\n opacity: 1;\n }\n\n @keyframes ddddocr-wave {\n 0% {\n background-position: 0% 50%;\n }\n 100% {\n background-position: 200% 50%;\n }\n }\n\n .ddddocr-loading-text {\n position: fixed;\n top: 8px;\n left: 50%;\n transform: translateX(-50%);\n background: #4A90E2;\n color: white;\n padding: 6px 16px;\n border-radius: 20px;\n font-size: 12px;\n font-weight: 500;\n z-index: 2147483647;\n opacity: 0;\n transition: opacity 0.3s;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n }\n\n .ddddocr-loading-text.visible {\n opacity: 1;\n }\n ",document.head.appendChild(t),this.container=document.createElement("div"),this.container.className="ddddocr-loading-indicator",document.body.appendChild(this.container);const e=document.createElement("div");e.className="ddddocr-loading-text",e.id="ddddocr-loading-text",e.textContent="正在初始化 DDDD OCR...",document.body.appendChild(e);}show(t){if(!this.container)return;this.isVisible=true,this.container.classList.add("visible");const e=document.getElementById("ddddocr-loading-text");e&&(t&&(e.textContent=t),e.classList.add("visible"));}updateText(t){const e=document.getElementById("ddddocr-loading-text");e&&(e.textContent=t);}hide(){if(!this.container)return;this.isVisible=false,this.container.classList.remove("visible");const t=document.getElementById("ddddocr-loading-text");t&&t.classList.remove("visible"),setTimeout(()=>{this.container?.remove(),t?.remove(),this.container=null;},300);}}class EventEmitter{constructor(){this.listeners=new Map;}on(t,e){this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e);}off(t,e){this.listeners.get(t)?.delete(e);}emit(t,e){this.listeners.get(t)?.forEach(t=>{try{t(e);}catch(n){}});}clear(){this.listeners.clear();}}class OCRApp{constructor(){this.loadingIndicator=null,this.initialized=false,this.eventEmitter=new EventEmitter,this.ocr=new DdddOCR,this.detector=new AutoDetector(this.ocr,this.eventEmitter),this.settingsUI=new SettingsUI,this.registerMenuCommands(),this.settingsUI.setOnConfigChange(t=>{this.handleConfigChange(t);});}async init(){if(!shouldExecuteScript())return;if(this.initialized)return;const t=getConfig();this.initialized=true,this.loadingIndicator=new LoadingIndicator;try{this.loadingIndicator.show("正在初始化 DDDD OCR"),this.loadingIndicator.updateText("正在加载模型文件"),await this.ocr.init(),this.loadingIndicator.updateText("DDDD OCR 已就绪"),t.autoDetect&&this.detector.start(),setTimeout(()=>{this.loadingIndicator?.hide();},2e3),this.showNotification("DDDD OCR 已就绪",t.autoDetect?"自动检测已启用":"点击菜单启用自动检测");}catch(e){this.loadingIndicator&&(this.loadingIndicator.updateText("初始化失败: "+String(e)),setTimeout(()=>{this.loadingIndicator?.hide();},3e3)),this.showNotification("初始化失败",String(e),true);}}registerMenuCommands(){GM_registerMenuCommand("⚙️ 打开设置",()=>this.settingsUI.show(),"s"),GM_registerMenuCommand("🤖 切换自动检测",()=>this.toggleAutoDetect(),"a"),GM_registerMenuCommand("🗑️ 清除所有缓存",async()=>{c.confirm({title:"清除缓存",content:"确定要清除所有缓存吗(包括模型和 WASM)?下次启动将重新下载。",icon:"🗑️",confirmText:"确定清除",cancelText:"取消",onConfirm:async()=>{await async function(){const t=new ModelCache;await t.delete();}(),await async function(){await a.clear();}(),this.showNotification("缓存已清除","请刷新页面");}});},"d"),GM_registerMenuCommand("ℹ️ 查看状态",()=>this.showStatus(),"i");}showStatus(){const t=getConfig(),e=isWhitelisted();let n=`\n脚本状态: ${this.initialized?"✅ 已初始化":"❌ 未初始化"}\n当前站点: ${window.location.hostname}\n白名单状态: ${t.enableWhitelist?"✅ 已启用":"❌ 已禁用"}\n白名单数量: ${t.whitelist?.length||0} 个站点\n当前站点匹配: ${e?"✅ 在白名单中":"❌ 不在白名单中"}\n自动检测: ${t.autoDetect?"✅ 已启用":"❌ 已禁用"}\n上传模型: ${t.useUploadedModel?"✅ 已启用":"❌ 未启用"}\n自动下载: ${t.autoDownload?"✅ 已启用":"❌ 已禁用"}`;this.initialized||(n+='\n\n⚠️ 脚本未初始化原因:\n'+this.getInitFailureReason()),c.show({title:"当前状态",content:n,icon:"ℹ️"});}getInitFailureReason(){const t=getConfig();if(t.enableWhitelist){if(!t.whitelist||0===t.whitelist.length)return "• 白名单为空\n• 请在设置中添加站点到白名单";if(!isWhitelisted())return `• 当前站点 ${window.location.hostname} 不在白名单中\n• 请将当前站点添加到白名单`}return "• 未知原因,请查看控制台日志"}toggleAutoDetect(){const t=!getConfig().autoDetect;t?this.initialized?(this.detector.start(),this.showNotification("自动检测已启用","将自动识别并填充验证码")):(c.show({title:"需要初始化",content:"启用自动检测需要先初始化 OCR 引擎,请稍候",icon:"⏳"}),this.init().then(()=>{this.detector.start(),this.showNotification("自动检测已启用","将自动识别并填充验证码");}).catch(t=>{this.showNotification("启用失败",String(t),true);})):(this.detector.stop(),this.showNotification("自动检测已关闭","不再自动处理验证码")),saveConfig({autoDetect:t});}handleConfigChange(t){t.autoDetect&&!this.detector.enabled?(this.initialized||this.init(),this.detector.start()):!t.autoDetect&&this.detector.enabled&&this.detector.stop(),this.checkNeedsRefresh(t)&&c.show({title:"配置已更改",content:"部分配置需要刷新页面才能生效,是否现在刷新?",icon:"🔄",confirmText:"刷新页面",onConfirm:()=>{window.location.reload();}});}checkNeedsRefresh(t){const e=getConfig();return t.useUploadedModel!==e.useUploadedModel||t.enableWhitelist!==e.enableWhitelist}showNotification(t,e,n=false){"undefined"!=typeof GM_notification&&GM_notification({title:t,text:e,timeout:n?5e3:3e3});}}function bootstrap(){const t=new OCRApp;shouldExecuteScript()&&setTimeout(()=>{t.init();},500);}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",bootstrap):bootstrap(); })();