// ==UserScript== // @name 网盘智能识别助手 (优化版) // @namespace https://scriptcat.org/zh-CN/users/202800 // @version 2026.9.8 // @author 辻弌20 // @description 智能识别选中文字中的🔗网盘链接和🔑提取码,识别成功打开网盘链接并自动填写提取码,省去手动复制提取码在输入的烦恼。支持识别:✅百度网盘 ✅阿里云盘 ✅夸克网盘 ✅腾讯微云 ✅蓝奏云 ✅蓝奏云·优享版 ✅123云盘新域名 ✅4275极速文件中转站 ✅三大通信运营商网盘:中国移动(和彩云)、中国联通(沃盘)、中国电信(天翼云盘) ✅迅雷云盘 ✅360云盘(屏蔽) ✅115网盘 ✅奶牛快传 ✅城通网盘 ✅FlowUs息流 ✅商店类:Chrome 扩展商店、Edge 扩展商店、Firefox 扩展商店、Windows 应用商店 ✅开源社区类:Github // @license AGPL-3.0-or-later // @homepage https://scriptcat.org/zh-CN/users/202800 // @supportURL https://scriptcat.org/zh-CN/users/202800 // @match *://*/* // @require https://unpkg.com/sweetalert2@10.16.6/dist/sweetalert2.min.js // @require https://unpkg.com/hotkeys-js@3.13.3/dist/hotkeys.min.js // @resource swalStyle https://unpkg.com/sweetalert2@10.16.6/dist/sweetalert2.min.css // @run-at document-idle // @grant GM_openInTab // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_getResourceText // @grant GM_info // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNMTAzLjYgMTA3LjRjMy41LTIuMiA4LjktNi4xIDEzLjgtMTIuNXM3LjMtMTIuNSA4LjUtMTYuNWMuNS0xLjcgMi4yLTcuNSAyLjItMTQuNyAwLTEwLjEtMy4zLTI1LjEtMTUuNC0zNi44LTE0LjUtMTQtMzIuMS0xNC4zLTM1LjctMTQuMy04IDAtMTUuNyAxLjktMjIuNiA1LjJDNDQgMjMgMzUuNyAzMS40IDMwLjggNDEuN2MtMS4zIDIuOC00IDQuNy03LjEgNS00IC4zLTcuNSA0LjQtOC45IDkuNi0uNSAxLjktMS42IDMuNS0zLjEgNC43QzQuNCA2Ni44IDAgNzUuNyAwIDg1YzAgNi44IDIuMyAxMy4xIDYuMSAxOC4yIDUuNSA3LjQgMTQuMiAxMi4yIDI0IDEyLjJoNDcuMWM0LjQgMCAxMS0uNSAxOC4zLTMuNSAzLjItMS40IDUuOS0zIDguMS00LjV6IiBmaWxsPSIjNDQ0Ii8+PHBhdGggZD0iTTExOS44IDY0LjNjLjEtMTcuMS0xMC40LTI4LTEyLjUtMzAuMUM5NSAyMi4xIDc5LjkgMjEuOCA3Ni45IDIxLjhjLTE3LjYgMC0zMy4zIDEwLjUtMzkuOSAyNi43LS42IDEuMy0xLjggMi4zLTMuNCAyLjNoLS40Yy01LjggMC0xMC42IDQuOC0xMC42IDEwLjd2LjVjMCAxLjQtLjggMi42LTEuOSAzLjNDMTMuNCA2OSA4LjggNzYuOCA4LjggODVjMCAxMi4yIDkuOSAyMi4zIDIyLjIgMjIuM2g0NS4yYzMuNi0uMSAxNy42LS45IDI5LjYtMTIgMi45LTIuOCAxMy45LTEzLjcgMTQtMzF6IiBmaWxsPSIjZGI4NDEyIi8+PHBhdGggZD0iTTExMC44IDU3LjRsLjIgMy4zYzAgMS4zLTEuMSAyLjQtMi4zIDIuNC0xLjMgMC0yLjMtMS4xLTIuMy0yLjRsLS4xLTIuOHYtLjNjMC0xLjIuOS0yLjIgMi4xLTIuM2guM2MuNyAwIDEuMy4zIDEuNy43LS4yLjEuMy41LjQgMS40em0tMy4zLTEwLjNjMCAxLjItMSAyLjMtMi4yIDIuM2gtLjFjLS44IDAtMS42LS41LTItMS4yLTQuNi04LjMtMTMuMy0xMy41LTIyLjgtMTMuNS0xLjIgMC0yLjMtMS0yLjMtMi4ydi0uMWMwLTEuMiAxLTIuMyAyLjItMi4zaC4xYTMwLjM3IDMwLjM3IDAgMCAxIDE1LjggNC40YzQuNiAyLjggOC40IDYuOCAxMS4xIDExLjUuMS4zLjIuNy4yIDEuMXpNNjkuMiA0OWwxOS40IDE0LjhjMS45IDEuNSAzLjEgMy41IDMuNSA1Ljd2LjJjLjEuNC4xLjguMSAxLjIgMCAuNi0uMSAxLjEtLjIgMS42LS40IDIuMi0xLjcgNC4yLTMuNSA1LjdMNjkuMyA5M2MtMi42IDItNS40IDIuNS03LjcgMS40LS4xLS4xLS4yLS4xLS4yLS4yLTItMS4yLTMuMi0zLjUtMy4yLTYuNHYtNi42aC01LjdjLTYuOCAwLTEyLTQuNy0xMi0xMC45IDAtNC44IDIuNi04LjUgNy4yLTEwLjMgMS4zLS41IDIuNy4yIDMuMiAxLjVzLS4xIDIuOC0xLjQgMy4zYy0yLjcgMS4xLTQgMi45LTQgNS41IDAgMy41IDMgNiA3IDZoOC4xYy41IDAgMSAuMiAxLjQuNi43LjYgMS4xIDEuNyAxLjEgMi42djguNGMwIDEuMy40IDIgLjcgMi4xLjQuMiAxLjMgMCAyLjQtLjlsMTkuMi0xNC45YzEuMi0uOSAxLjgtMi4xIDEuOC0zLjNzLS42LTIuMy0xLjctMy4xTDY2LjIgNTNjLTEuMS0uOS0yLTEuMS0yLjQtLjktLjMuMi0uNy45LS43IDIuMXY3LjZjMCAuOS0uNSAxLjctMS4yIDIuMS0uNC4zLS44LjQtMS4zLjQtMS40IDAtMi41LTEuMS0yLjUtMi41di03LjZjMC0zLjEgMS4zLTUuNSAzLjUtNi42bC43LS4zYzIuMS0uNyA0LjYtLjEgNi45IDEuN3oiIGZpbGw9IiM0NDQiLz48L3N2Zz4= // ==/UserScript== /* * 本脚本基于 syhyz1990 的开源项目(https://github.com/syhyz1990/panAI)优化改进。 * 原作者:MagicLyan(FuM), YouXiaoHou, 52fisher * 原始仓库:https://github.com/syhyz1990/panAI * 本次优化版本由 辻弌20 维护,主页:https://scriptcat.org/zh-CN/users/202800 */ (function() { 'use strict'; // ---------- 常量 ---------- const DEBOUNCE_DELAY = 300; const TOAST_DURATION = 3500; const MIN_TIMER = 2000; const MAX_TIMER = 10000; const STEP_TIMER = 500; const OBSERVER_TIMEOUT = 10000; const FILL_WAIT_BEFORE_CLICK = 800; const PWD_REGEX = /wss:[a-zA-Z0-9]+|(?<=\s*(?:密|提取|访问|解压|訪問|key|password|pwd|#|\?p=)\s*[码碼]?\s*[::=]?\s*)[a-zA-Z0-9]{3,8}/i; // ---------- 样式 ---------- const customClass = { container: 'panai-container', popup: 'panai-popup', }; // ★ 优化后的样式:毛玻璃 + 配色提升 ★ const CUSTOM_STYLE = ` .panai-container { z-index: 99999!important } /* ---- 识别弹窗:毛玻璃 + 新配色 ---- */ .panai-popup { font-size: 15px !important; width: 460px !important; max-width: 92vw !important; background: rgba(255, 255, 255, 0.6) !important; backdrop-filter: blur(20px) saturate(180%) !important; -webkit-backdrop-filter: blur(20px) saturate(180%) !important; border-radius: 24px !important; box-shadow: 0 25px 60px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(255, 255, 255, 0.5) inset !important; border: none !important; margin-top: 20px !important; } /* 标题 */ .panai-popup .swal2-title { font-size: 1.3rem !important; padding: 0.5rem 1rem 0 !important; color: #2d3436 !important; font-weight: 600 !important; } /* 内容(密码等) */ .panai-popup .swal2-html-container { font-size: 1rem !important; padding: 0.3rem 1rem 0.8rem !important; color: #2d3436 !important; } /* 密码文字单独显眼(紫色) */ .panai-popup .swal2-html-container .panai-pwd { color: #6c5ce7 !important; font-weight: 700 !important; letter-spacing: 0.5px; } /* 按钮容器 */ .panai-popup .swal2-actions { gap: 12px !important; padding-bottom: 0.8rem !important; } /* 打开按钮:紫色渐变 */ .panai-popup .swal2-confirm { background: linear-gradient(135deg, #6c5ce7, #a29bfe) !important; border: none !important; border-radius: 40px !important; padding: 0.6rem 2rem !important; font-weight: 500 !important; color: #ffffff !important; box-shadow: 0 4px 15px rgba(108, 92, 231, 0.3) !important; transition: all 0.25s ease !important; } .panai-popup .swal2-confirm:hover { transform: scale(1.04) !important; box-shadow: 0 8px 25px rgba(108, 92, 231, 0.4) !important; } /* 关闭按钮:极浅灰底 */ .panai-popup .swal2-cancel { background: rgba(0, 0, 0, 0.05) !important; border: none !important; border-radius: 40px !important; padding: 0.6rem 2rem !important; font-weight: 500 !important; color: #636e72 !important; transition: all 0.25s ease !important; } .panai-popup .swal2-cancel:hover { background: rgba(0, 0, 0, 0.1) !important; transform: scale(1.04) !important; } /* 设置面板样式(保留) */ .panai-setting-label { display: flex; align-items: center; justify-content: space-between; padding-top: 20px; } .panai-setting-checkbox { width: 16px; height: 16px; } `; // ---------- 工具函数 ---------- const utils = { clog(c) { console.group("%c %c [网盘智能识别助手(M)]", `background:url(${GM_info.script.icon}) center center no-repeat;background-size:12px;padding:3px`, ""); console.log(c); console.groupEnd(); }, parseQuery(name) { const reg = new RegExp(`(?<=(?:${name})\\=)(?:wss:[a-zA-Z0-9]+|[\\w-]+)`, "i"); const pd = location.href.replace(/%3A/g, ":").match(reg); return pd ? pd[0] : null; }, getValue(name) { return GM_getValue(name); }, setValue(name, value) { GM_setValue(name, value); }, sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }, addStyle(id, tag, cssOrUrl) { if (document.getElementById(id)) return; const el = document.createElement(tag); el.id = id; if (tag === 'style') { el.innerHTML = cssOrUrl; } else { el.rel = 'stylesheet'; el.href = cssOrUrl; } document.head.appendChild(el); }, isHidden(el) { try { return el.offsetParent === null; } catch { return false; } }, isMobile: (() => !!navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone|HarmonyOS|MicroMessenger)/i))(), query(selectors) { if (Array.isArray(selectors)) { for (const sel of selectors) { const el = document.querySelector(sel); if (el) return el; } return null; } return document.querySelector(selectors); }, debounce(fn, delay) { let timer = null; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; }, sanitizeUrl(url) { try { const u = new URL(url); if (!['http:', 'https:'].includes(u.protocol)) return null; return u.href; } catch { return null; } } }; // ---------- 网盘配置 ---------- const PAN_CONFIG = { 'baidu': { reg: /((?:https?:\/\/)?(?:e?yun|pan)\.baidu\.com\/(doc\/|enterprise\/)?(?:s\/[\w~]*(((-)?\w*)*)?|share\/\S{4,}))(?:\s*(?:提取码|密码)\s*[::]\s*(\w{4}))?/, host: /(pan|e?yun)\.baidu\.com/, input: ['#accessCode', '.share-access-code', '#wpdoc-share-page > .u-dialog__wrapper .u-input__inner', '#pwd'], button: ['#submitBtn', '.share-access .g-button', '#wpdoc-share-page > .u-dialog__wrapper .u-btn--primary', '.passwddiv-btn', '#sub'], name: '百度网盘', storage: 'hash' }, 'aliyun': { reg: /((?:https?:\/\/)?(?:(?:www\.)?(?:aliyundrive|alipan)\.com\/s|alywp\.net)\/[a-zA-Z\d]+)/, host: /www\.(aliyundrive|alipan)\.com|alywp\.net/, input: ['form .ant-input', 'form input[type="text"]', 'input[name="pwd"]'], button: ['form .button--fep7l', 'form button[type="submit"]'], name: '阿里云盘', storage: 'hash' }, 'quark': { reg: /((?:https?:\/\/)?pan\.quark\.cn\/s\/[a-zA-Z\d-]+)/, host: /pan\.quark\.cn/, input: ['.ant-input'], button: ['.ant-btn-primary'], name: '夸克网盘', storage: 'local', storagePwdName: 'tmp_quark_pwd' }, 'UC': { reg: /(?:https?:\/\/)?drive\.uc\.cn\/s\/[a-zA-Z\d]+/, host: /drive\.uc\.cn/, input: ["input[class*='ShareReceivePC--input']", '.input-wrap input'], button: ["button[class*='ShareReceivePC--submit-btn'", '.input-wrap button'], name: 'UC云盘', storage: 'hash' }, 'RUC': { reg: /(?:https?:\/\/)?fast\.uc\.cn\/s\/[a-zA-Z\d]+/, host: /fast\.uc\.cn/, input: ["input[class*='ShareReceivePC--input']", '.input-wrap input'], button: ["button[class*='ShareReceivePC--submit-btn'", '.input-wrap button'], name: 'UC云盘(转码)', storage: 'hash' }, 'weiyun': { reg: /((?:https?:\/\/)?share\.weiyun\.com\/[a-zA-Z\d]+)/, host: /share\.weiyun\.com/, input: ['.mod-card-s input[type=password]', 'input.pw-input'], button: ['.mod-card-s .btn-main', ".pw-btn-wrap button.btn"], name: '腾讯微云', storage: 'hash' }, 'lanzou': { reg: /((?:https?:\/\/)?(?:[a-zA-Z0-9\-.]+\.)?(?:lanzou[a-z]|lanzn)\.com\/[\w-]+(?:\/[\w-]+)*)/, host: /(?:[a-zA-Z\d-.]+)?(?:lanzou[a-z]|lanzn)\.com/, input: ['#pwd'], button: ['.passwddiv-btn', '#sub'], name: '蓝奏云(免登)', storage: 'hash' }, 'lanzou-Pre': { reg: /((?:https?:\/\/)?(?:[a-zA-Z0-9\-.]+)?(?:ilanzou)\.com\/[a-zA-Z\d_\-]+(?:\/[\w-]+)?)/, host: /(?:[a-zA-Z\d-.]+)?(?:ilanzou)\.com/, input: ['#pwd'], button: ['.passwddiv-btn', '#sub'], name: '蓝奏云·优享版', storage: 'hash' }, '123pan-N': { reg: /((?:https?:\/\/)?(?:www\.)?\d{6}\.com\/s\/[\w-]+(?:\?.*)?)/, host: /www\.123*\.com/, input: ['.ca-fot input', ".appinput .appinput"], button: ['.ca-fot button', ".appinput button"], name: '123云盘新域名(有限免登)', storage: 'hash' }, '4275': { reg: /((?:https?:\/\/)?(?:www\.)?4275\.com\/[\w-]+(?:\?.*)?)/, host: /4275\.com/, input: ['.ca-fot input', ".appinput .appinput"], button: ['.ca-fot button', ".appinput button"], name: '4275极速云文件中转站', storage: 'hash' }, 'CMCCdrive': { reg: /((?:https?:\/\/)?caiyun\.139\.com\/(?:m\/i|w\/i\/|web\/|front\/#\/detail)\??(?:linkID=)?[a-zA-Z\d]+)/, host: /(?:cai)?yun\.139\.com/, input: ['.token-form input[type=text]'], button: ['.token-form .btn-token'], name: '中国移动云盘(和彩云)', storage: 'local', storagePwdName: 'tmp_caiyun_pwd' }, 'CUCCdrive': { reg: /(?:https?:\/\/)?pan\.wo\.cn\/s\/[\w_]+/, host: /(pan\.wo\.cn|panservice\.mail\.wo\.cn)/, input: ['input.el-input__inner', ".van-field__control"], button: ['.s-button', ".share-code button"], name: '中国联通云盘(沃盘)', storage: (utils.isMobile ? 'local' : 'hash'), storagePwdName: 'tmp_wo_pwd' }, 'CTCCdrive': { reg: /((?:https?:\/\/)?cloud\.189\.cn\/(?:t\/|web\/share\?code=)?[a-zA-Z\d]+)/, host: /cloud\.189\.cn/, input: ['.access-code-item #code_txt', "input.access-code-input"], button: ['.access-code-item .visit', ".button"], name: '中国电信(天翼云)', storage: (utils.isMobile ? 'local' : 'hash'), storagePwdName: 'tmp_tianyi_pwd' }, 'xunlei': { reg: /((?:https?:\/\/)?pan\.xunlei\.com\/s\/[\w-]{10,})/, host: /pan\.xunlei\.com/, input: ['.pass-input-wrap .td-input__inner'], button: ['.pass-input-wrap .td-button'], name: '迅雷云盘', storage: 'hash' }, '115': { reg: /((?:https?:\/\/)?115cdn\.com\/s\/[a-zA-Z\d]+)/, host: /115cdn\.com/, input: ['.form-decode input'], button: ['.form-decode .submit a'], name: '115网盘', storage: 'hash' }, 'cowtransfer': { reg: /((?:https?:\/\/)?(?:[a-zA-Z\d-.]+)?cowtransfer\.com\/s\/[a-zA-Z\d]+)/, host: /(?:[a-zA-Z\d-.]+)?cowtransfer\.com/, input: ['.receive-code-input input'], button: ['.open-button'], name: '奶牛快传', storage: 'hash' }, 'ctfile': { reg: /((?:https?:\/\/)?(?:[a-zA-Z\d-.]+)?(?:ctfile|545c|u062|ghpym)\.com\/\w+\/[a-zA-Z\d-]+)/, host: /(?:[a-zA-Z\d-.]+)?(?:ctfile|545c|u062)\.com/, input: ['#passcode'], button: ['.card-body button'], name: '城通网盘', storage: 'hash' }, 'wenshushu': { reg: /((?:https?:\/\/)?(?:www\.wenshushu|ws28)\.cn\/(?:k|box|f)\/\w+)/, host: /www\.wenshushu\.cn/, input: ['.pwd-inp .ivu-input'], button: ['.pwd-inp .ivu-btn'], name: '文叔叔网盘', storage: 'hash' }, 'vdisk': { reg: /(?:https?:\/\/)?vdisk.weibo.com\/lc\/\w+/, host: /vdisk\.weibo\.com/, input: ['#keypass', "#access_code"], button: ['.search_btn_wrap a', "#linkcommon_btn"], name: '微盘', storage: 'hash', }, 'jianguoyun': { reg: /((?:https?:\/\/)?www\.jianguoyun\.com\/p\/[\w-]+)/, host: /www\.jianguoyun\.com/, input: ['input[type=password]'], button: ['.ok-button', '.confirm-button'], name: '坚果云', storage: 'hash' }, 'mega': { reg: /((?:https?:\/\/)?(?:mega\.nz|mega\.co\.nz)\/#F?![\w!-]+)/, host: /(?:mega\.nz|mega\.co\.nz)/, input: ['.dlkey-dialog input'], button: ['.dlkey-dialog .fm-dialog-new-folder-button'], name: 'Mega', storage: 'local' }, 'dudujb': { reg: /(?:https?:\/\/)?www\.dudujb\.com\/file-\d+\.html/, host: /www\.dudujb\.com/, name: '网赚:贵族网盘', }, '77file': { reg: /(?:https?:\/\/)?www\.77file\.com\/s\/[a-zA-Z\d]+/, host: /www\.77file\.com/, name: '网赚:77file', }, 'yifile': { reg: /(?:https?:\/\/)?www\.yifile\.com\/f\/\w+/, host: /www\.yifile\.com/, name: '网赚:YiFile', }, 'dufile': { reg: /(?:https?:\/\/)?dufile\.com\/file\/\w+\.html/, host: /dufile\.com/, name: 'duFile', }, 'flowus': { reg: /((?:https?:\/\/)?flowus\.cn\/[\S ^\/]*\/?share\/[a-z\d]{8}-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{12})/, host: /flowus\.cn/, name: 'FlowUs息流', storage: 'hash' }, 'chrome': { reg: /^https?:\/\/chrome.google.com\/webstore\/.+?\/([a-z]{32})(?=[\/#?]|$)/, host: /chrome\.google\.com/, replaceHost: "chrome.crxsoso.com", name: 'Chrome商店', }, 'edge': { reg: /^https?:\/\/microsoftedge.microsoft.com\/addons\/.+?\/([a-z]{32})(?=[\/#?]|$)/, host: /microsoftedge\.microsoft\.com/, replaceHost: "microsoftedge.crxsoso.com", name: 'Edge商店', }, 'firefox': { reg: /^https?:\/\/(reviewers\.)?(addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org)\/.*?(?:addon|review)\/([^/<>"'?#]+)/, host: /addons\.mozilla\.org/, replaceHost: "addons.crxsoso.com", name: 'Firefox商店', }, 'microsoft': { reg: /^https?:\/\/(?:apps|www).microsoft.com\/(?:store|p)\/.+?\/([a-zA-Z\d]{10,})(?=[\/#?]|$)/, host: /(apps|www)\.microsoft\.com/, replaceHost: "apps.crxsoso.com", name: 'Windows商店', }, }; // ---------- Toast 实例 ---------- let toast = Swal.mixin({ toast: true, position: 'top', showConfirmButton: false, timer: TOAST_DURATION, timerProgressBar: false, didOpen: (toastEl) => { toastEl.addEventListener('mouseenter', Swal.stopTimer); toastEl.addEventListener('mouseleave', Swal.resumeTimer); } }); // ---------- 核心逻辑 ---------- const main = { lastText: "lorem&", initValue() { const defaults = [ { name: 'setting_success_times', value: 0 }, { name: 'setting_auto_click_btn', value: true }, { name: 'setting_active_in_front', value: true }, { name: 'setting_timer_open', value: false }, { name: 'setting_timer', value: 5000 }, { name: 'setting_hotkeys', value: 'F1' } ]; defaults.forEach(({ name, value }) => { if (utils.getValue(name) === undefined) { utils.setValue(name, value); } }); }, debouncedIdentify: null, addPageListener() { this.debouncedIdentify = utils.debounce(this.handleSelection.bind(this), DEBOUNCE_DELAY); document.addEventListener("mouseup", this.debouncedIdentify, true); document.addEventListener("keydown", this.pressKey.bind(this), true); }, getSelectionHTML(selection, isDOM = false) { const div = document.createElement("div"); if (!selection.isCollapsed) { const frag = selection.getRangeAt(0).cloneContents(); div.appendChild(frag); } return isDOM ? div : selection.toString(); }, handleSelection(event, textOverride = '') { try { const selection = window.getSelection(); const text = textOverride || this.getSelectionHTML(selection); if (text === this.lastText || text === '') return; this.lastText = text; const start = performance.now(); let linkObj = this.parseLink(text); let link = linkObj.link; let name = linkObj.name; let pwd = this.parsePwd(text); if (!link) { const parentLinkObj = this.parseParentLink(selection); link = parentLinkObj.link; name = parentLinkObj.name; } if (link) { const sanitized = utils.sanitizeUrl(link); if (!sanitized) { utils.clog('链接不安全或无效,已阻止打开'); return; } link = sanitized; const end = performance.now(); utils.clog(`文本识别结果:${name} 链接:${link} 密码:${pwd} 耗时:${(end - start).toFixed(3)}ms`); this.showConfirmDialog(name, link, pwd, linkObj); } } catch (e) { console.error('[panAI] 识别出错:', e); } }, // ★ 修改:为密码文字添加 .panai-pwd 类,使其显眼 ★ showConfirmDialog(name, link, pwd, linkObj) { const option = { toast: true, showCancelButton: true, position: 'top', title: `发现${name}链接`, html: `${pwd ? '密码:' + pwd + '' : '是否打开?'}`, confirmButtonText: '打开', cancelButtonText: '关闭', customClass }; if (utils.getValue('setting_timer_open')) { option.timer = Math.max(utils.getValue('setting_timer'), MIN_TIMER); option.timerProgressBar = true; } utils.setValue('setting_success_times', utils.getValue('setting_success_times') + 1); Swal.fire(option).then((res) => { this.lastText = 'lorem&'; window.getSelection().empty(); if (res.isConfirmed || res.dismiss === 'timer') { if (linkObj.storage === "local") { utils.setValue(linkObj.storagePwdName, pwd); } const active = utils.getValue('setting_active_in_front'); let finalUrl = link; if (pwd) { const sep = link.includes('?') ? '&' : '?'; finalUrl = `${link}${sep}pwd=${pwd}#${pwd}`; } GM_openInTab(finalUrl, { active }); } }); }, pressKey(event) { if (event.key === 'Enter') { const confirmBtn = document.querySelector('.panai-container .swal2-confirm'); if (confirmBtn) confirmBtn.click(); } if (event.key === 'Escape') { const cancelBtn = document.querySelector('.panai-container .swal2-cancel'); if (cancelBtn) cancelBtn.click(); } }, addHotKey() { const hotkey = utils.getValue('setting_hotkeys'); hotkeys(hotkey, (event) => { event.preventDefault(); this.showIdentifyBox(); }); }, parseLink(text = '') { const result = { name: '', link: '', storage: '', storagePwdName: '' }; if (!text) return result; try { text = decodeURIComponent(text); } catch { /* ignore */ } text = text.replace(/[点點]/g, '.') .replace(/[\u4e00-\u9fa5()(),\u200B,\uD83C-\uDBFF\uDC00-\uDFFF]/g, '') .replace(/lanzous/g, 'lanzouw'); for (const [key, config] of Object.entries(PAN_CONFIG)) { if (config.reg.test(text)) { const match = text.match(config.reg); result.name = config.name; result.link = match[0]; result.storage = config.storage || ''; result.storagePwdName = config.storagePwdName || ''; if (config.replaceHost) { result.link = result.link.replace(config.host, config.replaceHost); } break; } } return result; }, parseParentLink(selection) { const dom = this.getSelectionHTML(selection, true).querySelector('*[href]'); return this.parseLink(dom ? dom.href : ""); }, parsePwd(text) { text = text.replace(/\u200B/g, '').replace('%3A', ":") .replace(/(?:本帖)?隐藏的?内容[::]?/, ""); const match = text.match(PWD_REGEX); return match ? match[0] : ''; }, panDetect() { const host = location.hostname; for (const [key, config] of Object.entries(PAN_CONFIG)) { if (config.host && config.host.test(host)) { return key; } } return ''; }, autoFillPassword() { const panType = this.panDetect(); if (!panType) return; const config = PAN_CONFIG[panType]; if (!config || !config.input) return; let pwd = utils.parseQuery('pwd|p') || location.hash.slice(1).replace(/\W/g, ""); if (config.storage === 'local') { pwd = pwd || utils.getValue(config.storagePwdName); } if (!pwd || !/^(?:wss:[a-zA-Z\d]+|[a-zA-Z0-9]{3,8})$/.test(pwd)) return; const target = document.body; const observer = new MutationObserver((mutations, obs) => { const input = utils.query(config.input); if (input && !utils.isHidden(input)) { obs.disconnect(); this.doFillAction(config.input, config.button, pwd); } }); observer.observe(target, { childList: true, subtree: true }); setTimeout(() => observer.disconnect(), OBSERVER_TIMEOUT); const immediateInput = utils.query(config.input); if (immediateInput && !utils.isHidden(immediateInput)) { observer.disconnect(); this.doFillAction(config.input, config.button, pwd); } }, doFillAction(inputSelectors, buttonSelectors, pwd) { const input = utils.query(inputSelectors); if (!input || utils.isHidden(input)) return; Swal.fire({ toast: true, position: 'top', showCancelButton: false, showConfirmButton: false, title: 'AI已识别到密码!正自动帮您填写', icon: 'success', timer: 2000, customClass }); const lastValue = input.value; input.value = pwd; const event = new Event('input', { bubbles: true }); const tracker = input._valueTracker; if (tracker) tracker.setValue(lastValue); input.dispatchEvent(event); if (utils.getValue('setting_auto_click_btn')) { utils.sleep(FILL_WAIT_BEFORE_CLICK).then(() => { const btn = utils.query(buttonSelectors); if (btn && !btn.disabled) { btn.click(); } else { utils.clog('未找到可用的提交按钮或按钮被禁用'); } }); } }, clearIdentifyTimes() { Swal.fire({ showCancelButton: true, title: '确定要重置识别次数吗?', icon: 'warning', confirmButtonText: '确定', cancelButtonText: '取消', customClass }).then(res => { if (res.isConfirmed) { utils.setValue('setting_success_times', 0); location.reload(); } }); }, showIdentifyBox() { Swal.fire({ title: '识别剪切板中文字', input: 'textarea', inputPlaceholder: '若选方式一,请按 Ctrl+V 粘贴要识别的文字', html: `