// ==UserScript== // @name 网盘智能识别助手-修改优化版 // @namespace https://github.com/Magiclyan/panAI // @version 261022 // @author MagicLyan(FuM),YouXiaoHou,52fisher,Modified // @description 智能识别选中文字中的🔗网盘链接和🔑提取码,识别成功打开网盘链接并自动填写提取码。支持60+网盘(含123云盘全部域名),含深浅模式、背景材质、13套主题等。内置星号密码显示助手,悬浮即显密码。内置通用保底识别引擎,网盘换域名也能识别。 // @license AGPL-3.0-or-later // @homepage https://github.com/Magiclyan/panAI // @supportURL https://github.com/Magiclyan/panAI // @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;utf8, // ==/UserScript== (function () { 'use strict'; /* ===== 网盘真实品牌图标库(内联 SVG,无外部依赖、无跨域问题)===== */ const PAN_ICON_DATA = {"baidu":"","alibabacloud":"","quark":"","dropbox":"","uc":"","qq":"","weiyun":"","sinaweibo":"","lanzou":"","123pan":"123","4275":"","caiyun":"","chinaunicom":"","chinatelecom":"","mediafire":"","xunlei":"","360":"","115":"","box":"","cowtransfer":"","ctfile":"","wenshushu":"","mega":"","wetransfer":"","nitroflare":"","crown":"","target":"","home":"","yifile":"","dufile":"","plane":"","silver":"","lansod":"","61":"","520":"","567":"","ayunpan":"","iycdn":"","cat":"","download":"","ox":"","snow":"","cloud":"","116":"","terabox":"","pikpak":"","google-drive":"","microsoft-onedrive":"","microsoft-edge":"","github":"","flowus":"","chrome-web-store":"","firefox-mono":"","microsoft-store":"","nutstore":"","4shared":"","kdocs":"","gofile":""}; /** * 获取网盘对应的品牌图标(SVG 字符串)。 * @param {string} key 图标标识,匹配 PAN_ICON_DATA 中的键 * @param {number} size 显示尺寸(px),默认 16 * @param {boolean} circle 是否裁成圆形头像,默认 true */ function getPanIcon(key, size, circle) { if (size === undefined) size = 16; if (circle === undefined) circle = true; var svg = PAN_ICON_DATA[key]; if (!svg) { return '☁️'; } return '' + svg + ''; } /* ===== 版本号(年月日)与版本日志 ===== */ const VERSION = '261022'; const CHANGELOG = [ { version: '261022', date: '2026-10-22', items: [ '1. 修复多链接弹窗尺寸未生效的 bug:CSS 类名未命中真实弹窗容器,且被全局尺寸规则覆盖', '2. 改用 JS 内联样式直接打到 DOM 上,优先级高于外部 CSS 与 !important,彻底锁死 500×800', '3. 多链接弹窗最多完整显示 12 个链接,长宽上限严格锁定 500×800,链接少时按实际高度不撑高', '4. 多链接弹窗 html-container 解除全局 max-height 限制,避免列表被压到 60px 并出现滚动条', '5. 文叔叔网盘域名更新为官方 www.wenshushu.cn,同时保留根域 wenshushu.cn 作为短链入口', '6. 文叔叔分享路径补全 /s /share 格式,覆盖更多链接形态', '7. 文叔叔密码输入框与按钮选择器扩展兜底,新增"取件码/密码"占位符匹配' ] }, { version: '261021', date: '2026-10-21', items: [ '1. 多链接弹窗尺寸重构:最多完整显示 12 个链接,长宽上限锁定 500×800', '2. 链接数较少时弹窗按实际内容高度自适应,不再强行撑高到上限', '3. 超过 12 个链接时才启用内部滚动条,弹窗始终不超过 500×800', '4. 蓝奏云补充官方真实分享二级域名(lanzouj / lanzoux / lanzouw 等),修复大量分享链接无法识别的问题', '5. 小飞机盘域名修正为官方 feijipan.com,修复原 share.feijipan.com 无法弹出的问题', '6. 银盘子域补充 silver.yukaidi.com,确保官方分享链接可正常识别打开', '7. 坚果云分享链接新增 /s/ 外链格式支持(原仅有 /p/)', '8. 中国移动云盘补充新官方域名 caiyun.feixin.10086.cn,与旧 caiyun.139.com 并存兼容' ] }, { version: '261020', date: '2026-10-20', items: [ '1. 修复多链接弹窗无法完整显示 15 个链接的问题,弹窗尺寸按链接数量自适应,一次最多露出 15 项且不出现内部滚动条', '2. 多链接弹窗宽度随链接数量动态计算(540~700px),高度按单项 46px 精确累加,确保 15 项全部可见', '3. 多链接列表高度在 didOpen 阶段精确计算,链接数 ≤15 时强制隐藏内部滚动条,超过 15 项才启用滚动', '4. 优化提取码识别引擎,新增"口令 / 验证码 / passwd / code"等关键词,并兼容 ?pwd= / &pwd= / # 等多种密码携带方式', '5. 新增密码识别兜底逻辑:从 URL 参数与文本末尾独立短码中二次提取,解决"明明带密码却识别不出来"的问题', '6. 修复识别型网盘(无 storage 配置)在文本含密码时丢失密码的问题,密码会自动拼接到 URL 参数', '7. 修复"爱丽丝的记事本"网盘死配置(reg:null 导致永不识别),补全 drive.noire.cc / noire.cc 真实域名并支持自动填码', '8. 支持网盘清单中"爱丽丝的记事本"改用可用配置,保证识别后可正常弹出并打开', '9. 更新日志全部改用数字序列编号,按版本号从新到旧排序,阅读更清晰', '10. 多链接弹窗底部提示改为卡片样式,视觉层级更分明' ] }, { version: '261019', date: '2026-10-19', items: [ '1. 新增「爱丽丝的记事本」网盘支持(drive.noire.cc / noire.cc:8888)', '2. 全新多链接识别功能:选中文本中含多个网盘链接时,全部识别并列举展示', '3. 多链接弹窗采用卡片式列表,可单独点击打开,也可一键全部打开', '4. 多链接打开时自动错开 300ms,避免浏览器弹窗拦截', '5. 新增 _openLink 统一打开逻辑,代码更简洁健壮', '6. 多链接列表支持深浅模式与 hover 高亮交互', '7. 新增 parseAllLinks 方法,返回所有匹配链接数组(非只返回第一个)', '8. 保留 _parseFirstLink 原有行为,确保向后兼容', '9. 爱丽丝的记事本使用 Material-UI 密码框 #pwd + 按钮选择器', '10. 爱丽丝的记事本密码通过 local 存储传递,originalLink 保留原始链接' ] }, { version: '261018', date: '2026-10-18', items: [ '1. 修复 123 云盘不识别的根本问题,新增支持新版原生分享链接格式', '2. 新版格式 {uid}.share.123pan.cn/123pan/{ShareKey}?pwd=xxx# 全面支持', '3. 新增 share.123pan.cn 域名识别(123 云盘新版分享域名)', '4. 123 云盘链接路径扩展,新增 /123pan/ 路径格式', '5. 密码自动填入全面升级,兼容 Vue2 / Vue3 / React 三大框架', '6. 输入框赋值改用原生 setter,解决 Vue3 Proxy 拦截导致赋值无效的问题', '7. 多选择器轮询机制,遍历所有备选选择器找到第一个可见输入框', '8. 等待时长从 8 秒延长至 32 秒(40 次重试),应对慢加载 SPA', '9. 按钮点击增加兜底逻辑:找不到按钮时自动 submit 表单或模拟 Enter', '10. 密码框选择器大幅扩展,覆盖新版 123 云盘页面更多变体', '11. 同时触发 input 和 change 事件,确保各框架都能感知到值变化' ] }, { version: '261017', date: '2026-10-17', items: [ '1. 123 云盘"永不失效"防变化机制:三层防御架构,域名再怎么变都能识别', '2. 第一层(精确):已知域名白名单 + 标准路径格式全覆盖', '3. 第二层(泛匹配):域名中含 123pan 关键词即可识别,未来换域名自动适配', '4. 第三层(行为特征):123 云盘专用保底规则,基于分享路径特征兜底识别', '5. 123 云盘域名泛匹配正则支持任意子域名 + 多后缀(com/cn/net/org/io/me)', '6. 123 云盘短链域名支持所有 6 位数字.com 格式(如 123684 / 123865 / 123952 等)', '7. 识别引擎升级为三层匹配:专用规则 → 123 云盘专用保底 → 通用保底', '8. 123 云盘提取码输入框选择器再扩展,覆盖更多新版页面变体' ] }, { version: '261016', date: '2026-10-16', items: [ '1. 全面升级 123 云盘域名适配,新增 123684 / 123865 / 123952 / 123912 等 16 个已知分享域名后缀', '2. 123 云盘链接路径扩展,支持 /s/ /v/ /f/ /d/ /share/ 等多种格式', '3. 新增通用保底识别规则(Fallback),网盘更换新域名/子域名/新开分站也能识别', '4. 保底规则覆盖 com/cn/net/org/io/me/xyz/top/vip/cloud/site/online/link/app 等主流后缀', '5. 识别引擎双层匹配架构:专用规则优先,未命中时自动启用保底规则兜底', '6. 修复正则匹配异常导致整个识别流程中断的问题,单条规则异常不再影响其他规则', '7. panDetect 域名检测函数增加异常防御,避免 host 正则异常导致脚本报错', '8. 优化 replaceHost 逻辑,增加 val.host 存在性检查,防止 replace 报错', '9. 123 云盘提取码输入框选择器扩展,适配新版页面结构' ] }, { version: '261015', date: '2026-10-15', items: [ '1. 修复中国移动和彩云(139 云盘)链接识别失败问题,正则支持 m/i/、w/i/、web/ 等完整路径', '2. 修复 4275 极速云文件链接识别不完整问题,正则支持多级路径匹配', '3. 新增全量网盘正则单元测试覆盖,58 个网盘逐一验证识别准确性', '4. 星号密码显示助手完整集成,支持悬浮/双击/单击/Ctrl+单击 四种触发方式', '5. 星号密码设置集成到「行为设置」分区,与网盘功能统一管理', '6. 优化正则匹配性能,减少不必要的回溯' ] }, { version: '261014', date: '2026-10-14', items: [ '1. 合并「星号密码显示助手」:悬浮密码框即显示星号密码,再也不怕忘记密码', '2. 新增密码显示方式:鼠标悬浮 / 双击 / 单击 / Ctrl+单击 四种触发模式', '3. 新增密码显示延迟时间调节(0~1000ms),避免误触导致密码泄露', '4. 星号密码设置集成到「行为设置」分区,与网盘功能统一管理', '5. 星号密码模块适配深浅模式与 13 套主题,视觉风格完全统一', '6. 修复 MutationObserver 兼容性问题,防止部分网站覆盖原生对象导致脚本报错', '7. 优化密码输入框事件监听,支持动态加载的密码框(SPA 单页应用完美兼容)', '8. 修复恢复默认设置时未重置星号密码相关配置的问题' ] }, { version: '261013', date: '2026-10-13', items: [ '1. 更换 ScriptCat 扩展列表图标为自定义紫色渐变云存储图标', '2. 夸克云盘图标更换为蓝紫渐变云朵图标', '3. 123 云盘图标更换为蓝紫圆角方形 123 图标', '4. Google Drive 图标更换为经典黄绿蓝三色三角拼合图标', '5. OneDrive 图标更换为蓝紫渐变云朵图标', '6. QQ 闪传图标更换为经典企鹅卡通形象图标', '7. 设置页面取消折叠收纳,所有功能默认全部展开', '8. 设置窗口尺寸增大至 760×820,功能完整显示无需滚动' ] }, { version: '261012', date: '2026-10-12', items: [ '1. 插件重命名为「网盘智能识别助手-修改优化版」', '2. ScriptCat 扩展列表图标更换为自定义紫色渐变云存储图标', '3. 删除「切换深浅并预览」按钮,简化设置页面操作', '4. 设置页面重新设计:按「外观设置 / 布局与位置 / 行为设置」三大区块分组,新增分区标题', '5. 新增背景材质效果:无 / 透明 / 模糊 / 亚克力模糊 / 云母 五种可选', '6. 透明材质在深浅模式下自动适配背景色,亚克力/云母材质含动态光泽层', '7. 更新日志抽屉 UI 适配深浅模式,使用 CSS 变量统一配色', '8. 首次安装或插件更新后自动弹出更新日志页面', '9. 修复恢复默认按钮未重置背景材质设置的问题' ] }, { version: '261011', date: '2026-10-11', items: [ '1. 新增外观模式切换:支持「跟随系统 / 始终浅色 / 始终深色」三种模式', '2. 扩展主题为 13 套(含明亮浅色、深夜暗黑两套基础主题),每套均提供独立暗色变体', '3. 主题卡片预览改为双色色块(左浅右深),一眼区分明暗与配色风格', '4. 设置页新增「🌗 切换深浅并预览」按钮,点击实时切换并预览效果', '5. 恢复默认按钮同步重置外观模式设置', '6. 全部 58 个网盘图标替换为对应网盘官方品牌 SVG 图标(百度蓝、阿里橙、腾讯蓝、Google 四色等)' ] }, { version: '261008', date: '2026-10-08', items: [ '1. 新增弹窗主题:提供 11 套拟真 Windows 外观(Win11/10/8.1/8/7/Vista/XP/ME/98/3.x/1.0&2.0)', '2. 新增弹窗位置设置:上 / 中 / 下,作用于全部 SweetAlert 弹窗', '3. 新增弹窗尺寸设置:宽度 / 高度自由调节,另含紧凑/标准/宽屏三档预设', '4. 新增「恢复默认」按钮,一键还原主题、位置、大小及所有设置项', '5. 设置页重构为分组折叠卡片式,保存无需刷新页面', '6. 网盘图标全部替换为真实品牌 SVG(百度蓝、阿里橙、腾讯微云、Google 四色、OneDrive 蓝、Edge 等)', '7. 识别结果弹窗与支持网盘清单的图标改为矢量图形,放大不失真,随主题自动适配背景' ] }, { version: '261007', date: '2026-10-07', items: [ '1. 新增适配主流网盘:QQ 闪传(qfile.qq.com)、NitroFlare,恢复 360 云盘', '2. 新增适配小众网盘:飞机盘、银盘(yukaidi)、小蓝云盘、六一云盘、520 云盘、567 盘、AYunPan、爱优网盘、飞猫盘、优云下载、迅牛网盘、雪球云盘、飞云网盘、116 盘,恢复 OwnFile', '3. 为每个网盘添加专属图标,识别弹窗与支持列表均展示图标', '4. 新增「更新日志」抽屉面板:最新版默认展开,历史版本收纳折叠、点击展开,版本号为年月日', '5. 新增「支持网盘」清单面板,按主流 / 海外 / 小众 / 商店分类展示全部已适配网盘及图标', '6. 修复 UC 网盘按钮选择器缺少右括号导致自动填码报错', '7. 修复识别型网盘(无需提取码)在打开链接时误附加密码的问题', '8. 提升健壮性:快捷键库加载失败不再中断脚本,缺失匹配项的网盘不再报错' ] }, { version: '260107', date: '2026-01-07', items: [ '1. 新增海外/国际化网盘:TeraBox、PikPak、Google Drive、OneDrive、Dropbox、MediaFire、Box、Gofile、WeTransfer、4shared', '2. 新增金山文档(WPS)、GitHub Releases、FlowUs 息流、4275 极速文件中转站适配', '3. 优化蓝奏云旧域名自动修正(lanzous → lanzouw)' ] }, { version: '250706', date: '2025-07-06', items: [ '1. 重构三大运营商网盘适配:中国移动(和彩云)、中国联通(沃盘)、中国电信(天翼云盘)', '2. 新增识别剪切板文字、自定义快捷键、前台打开等配置项', '3. 新增识别成功次数统计与重置功能' ] } ]; const customClass = { container: 'panai-container', popup: 'panai-popup', }; let toast = Swal.mixin({ toast: true, position: 'top', showConfirmButton: false, timer: 3500, timerProgressBar: false, didOpen: (toast) => { toast.addEventListener('mouseenter', Swal.stopTimer); toast.addEventListener('mouseleave', Swal.resumeTimer); } }); let util = { 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) { let reg = new RegExp(`(?<=(?:${name})\\=)(?:wss:[a-zA-Z0-9]+|[\\w-]+)`, "i") let pd = location.href.replace(/%3A/g, ":").match(reg); if (pd) { return pd[0]; } return null; }, getValue(name) { return GM_getValue(name); }, setValue(name, value) { GM_setValue(name, value); }, sleep(time) { return new Promise((resolve) => setTimeout(resolve, time)); }, addStyle(id, tag, css) { tag = tag || 'style'; let doc = document, styleDom = doc.getElementById(id); if (styleDom) return; let style = doc.createElement(tag); style.rel = 'stylesheet'; style.id = id; tag === 'style' ? style.innerHTML = css : style.href = css; document.head.appendChild(style); }, isHidden(el) { try { return el.offsetParent === null; } catch (e) { 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(selector) { if (Array.isArray(selector)) { let obj = null; for (let i = 0; i < selector.length; i++) { let o = document.querySelector(selector[i]); if (o) { obj = o; break; } } return obj; } return document.querySelector(selector); } }; /* 网盘域名标识识别区 格式: '<任意填写>': { reg: /<链接匹配正则表达式>/, host: /<链接匹配主机名>/, input: ['form .ant-input', 'form input[type="text"]', 'input[name="pwd"]'], button: ['form .button--fep7l', 'form button[type="submit"]'], name: '<提示显示文本>', icon: '<展示图标>', storage: 'hash' */ let opt = { '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: '百度网盘', icon: 'baidu', 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: '阿里云盘', icon: 'alibabacloud', 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: '夸克网盘', icon: 'quark', 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云盘', icon: '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云盘(转码)', icon: '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: '腾讯微云', icon: 'weiyun', storage: 'hash' }, 'lanzou': { // 官方分享二级域名:www.lanzou.com / ww.lanzouj.com / wws.lanzouj.com / wwd.lanzouw.com / www.lanzoux.com reg: /((?:https?:\/\/)?(?:[a-zA-Z0-9-]+\.)?(?:lanzou[a-z]|lanzouj|lanzoux|lanzouw|lanzn)\.com\/[\w-]+(?:\/[\w-]+)*)/, host: /(?:[a-zA-Z\d-]+\.)?(?:lanzou[a-z]|lanzouj|lanzoux|lanzouw|lanzn)\.com/, input: ['#pwd'], button: ['.passwddiv-btn', '#sub'], name: '蓝奏云(免登)', icon: 'lanzou', 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: '蓝奏云·优享版', icon: 'lanzou', storage: 'hash' }, '123pan': { /* ═══════════════════════════════════════════════════════════════ * 🔒 123云盘"永不失效"防变化识别策略(三层防御): * * 支持两种链接格式: * 【旧版】https://www.123pan.com/s/{ShareKey} * 【新版】https://{uid}.share.123pan.cn/123pan/{ShareKey}?pwd=xxx# * 【短链】https://www.123xxx.com/s/{ShareKey} * * 第一层(精确匹配):覆盖所有已知域名 + 所有路径格式 * - 123pan.com / 123pan.cn(官方主域名) * - share.123pan.cn / *.share.123pan.cn(新版原生分享域名) * - 全部 123xxx.com 短链域名(123865/123684/123952/123912 等) * - 多后缀支持:com/cn/net/org/io/me(未来可能扩展) * - 路径支持 /s/ /v/ /f/ /d/ /share/ /123pan/ 及 .html 后缀 * * 第二层(泛域名匹配):核心品牌特征 "123pan" 出现在域名中 * - 无论未来换什么域名,只要域名包含 "123pan" 即可识别 * * 第三层(行为特征兜底):见配置区末尾 _123pan_fallback 规则 * * 🔑 后置验证:parseLink 中对匹配结果做 host 合法性校验, * 防止误匹配 baidu123pan.com 等边界情况。 * ═══════════════════════════════════════════════════════════════ */ // 注意:正则拆为两个捕获组,通过 (?:A|B) 合并为一个整体捕获 reg: /((?:https?:\/\/)?(?:(?:(?:[a-z0-9-]+\.)*123pan\.(?:com|cn|net|org|io|me)|(?:www\.)?\d{6}\.(?:com|cn|net|org|io|me))\/(?:s|v|f|d|share)\/[\w.-]+|(?:(?:[a-z0-9]+\.)*share\.123pan\.(?:cn|com)\/123pan\/[\w.-]+))(?:\.html)?(?:\?[^\\s]*)?)/i, host: /(?:[a-z\d-]+\.)*123pan\.(?:com|cn|net|org|io|me)|(?:www\.)?\d{6}\.(?:com|cn|net|org|io|me)|(?:[a-z\d]+\.)*share\.123pan\.(?:cn|com)/i, input: ['.access-code input', '.verify-input input', '.input-wrap input', '.ca-fot input', "input[placeholder*='提取码'], input[placeholder*='密码'], input[placeholder*='pwd'], input[maxlength='4']"], button: ['.access-code button', '.verify-input button', '.input-wrap button', '.ca-fot button', "button:not([disabled])"], name: '123云盘(免登)', icon: '123pan', storage: 'hash', _validateHost: true // 标记需要后置 host 验证 }, '4275': { reg: /((?:https?:\/\/)?(?:www\.)?4275\.com\/[\w\/-]+(?:\?.*)?)/, host: /4275\.com/, input: ['.ca-fot input', ".appinput .appinput"], button: ['.ca-fot button', ".appinput button"], name: '4275极速云文件中转站', icon: '4275', storage: 'hash' }, /* 三大通信运营商网盘 */ 'CMCCdrive': { // 中国移动云盘:旧域 caiyun.139.com(和彩云)与新域 caiyun.feixin.10086.cn 均支持 reg: /((?:https?:\/\/)?(?:caiyun\.139\.com|caiyun\.feixin\.10086\.cn)\/(?:m\/i|w\/i\/|web\/|front\/#\/detail)\/?[\w-]+)/, host: /(?:cai)?yun\.(?:139\.com|feixin\.10086\.cn)/, input: ['.token-form input[type=text]', '.verify-form input[type=text]', 'input[type=text]'], button: ['.token-form .btn-token', '.verify-form .btn-verify'], name: '中国移动云盘(和彩云)', icon: 'caiyun', 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: '中国联通云盘(沃盘)', icon: 'chinaunicom', storage: (() => util.isMobile === true ? '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: '中国电信(天翼云)', icon: 'chinatelecom', storage: (() => util.isMobile === true ? 'local' : 'hash')(), storagePwdName: 'tmp_tianyi_pwd' }, /*——End——*/ '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: '迅雷云盘', icon: 'xunlei', storage: 'hash' }, '360': { reg: /((?:https?:\/\/)?(?:[a-zA-Z\d\-.]+)?(?:yunpan\.360\.cn|yunpan\.com)(\/lk)?\/surl_\w{6,})/, host: /[\w.]+?(?:yunpan\.360\.cn|yunpan\.com)/, input: ['.pwd-input'], button: ['.submit-btn'], name: '360云盘', icon: '360', storage: 'local', storagePwdName: 'tmp_360_pwd' }, '115': { reg: /((?:https?:\/\/)?115cdn\.com\/s\/[a-zA-Z\d]+)/, host: /115cdn\.com/, input: ['.form-decode input'], button: ['.form-decode .submit a'], name: '115网盘', icon: '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: '奶牛快传', icon: 'cowtransfer', 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|ghpym)\.com/, input: ['#passcode'], button: ['.card-body button'], name: '城通网盘', icon: 'ctfile', storage: 'hash' }, 'wenshushu': { // 官方主域 www.wenshushu.cn(文叔叔官网 / Microsoft Store 登记域名),根域 wenshushu.cn 作为短链解析入口一并纳入 reg: /((?:https?:\/\/)?(?:www\.)?wenshushu\.cn\/(?:k|box|f|s|share)\/[\w\-]+)/, host: /(?:www\.)?wenshushu\.cn/, input: ['.pwd-inp .ivu-input', '.pwd-box input', 'input[placeholder*="取件"]', 'input[placeholder*="密码"]'], button: ['.pwd-inp .ivu-btn', '.pwd-box .ivu-btn', 'button:contains("确认")', 'button:contains("提取")'], name: '文叔叔网盘', icon: 'wenshushu', 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: '微盘', icon: 'weibo', storage: 'hash', }, 'jianguoyun': { // 坚果云分享链接支持 /p/(旧)与 /s/(外链分享)两种格式 reg: /((?:https?:\/\/)?www\.jianguoyun\.com\/[ps]\/[\w-]+)/, host: /www\.jianguoyun\.com/, input: ['input[type=password]'], button: ['.ok-button', '.confirm-button'], name: '坚果云', icon: 'nutstore', 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', icon: 'mega', storage: 'local' }, 'qqfile': { reg: /((?:https?:\/\/)?qfile\.qq\.com\/q\/[\w]+)/, host: /qfile\.qq\.com/, name: 'QQ闪传', icon: 'qq' }, 'nitroflare': { reg: /((?:https?:\/\/)?(?:www\.)?(?:nitroflare\.com|nitro\.download)\/view\/[a-zA-Z0-9]+)/, host: /(?:www\.)?(?:nitroflare\.com|nitro\.download)/, name: 'NitroFlare', icon: '🔥' }, 'dudujb': { reg: /(?:https?:\/\/)?www\.dudujb\.com\/file-\d+\.html/, host: /www\.dudujb\.com/, name: '网赚:贵族网盘', icon: 'crown', }, '77file': { reg: /(?:https?:\/\/)?www\.77file\.com\/s\/[a-zA-Z\d]+/, host: /www\.77file\.com/, name: '网赚:77file', icon: 'target', }, 'ownfile': { reg: /(?:https?:\/\/)?ownfile\.net\/files\/[a-zA-Z\d]+\.html/, host: /ownfile\.net/, name: 'OwnFile', icon: 'home', }, 'yifile': { reg: /(?:https?:\/\/)?www\.yifile\.com\/f\/\w+/, host: /www\.yifile\.com/, name: '网赚:YiFile', icon: 'yifile', }, 'dufile': { reg: /(?:https?:\/\/)?dufile\.com\/file\/\w+\.html/, host: /dufile\.com/, name: 'duFile', icon: 'dufile', }, /* 小众网赚网盘(网赚盘,多为 /file-.html 格式) */ 'feijipan': { // 官方域名:www.feijipan.com;分享链接为 /s/{id} reg: /((?:https?:\/\/)?(?:www\.)?feijipan\.com\/s\/[\w]+)/, host: /(?:www\.)?feijipan\.com/, name: '小飞机网盘', icon: 'plane', }, 'yukaidi': { // 官方域名:silver.yukaidi.com(银盘子域) reg: /((?:https?:\/\/)?(?:silver|pan\d+)\.yukaidi\.com\/s\/[\w]+)/, host: /(?:silver|pan\d+)\.yukaidi\.com/, name: '银盘(yukaidi)', icon: 'silver', }, 'lansod': { reg: /((?:https?:\/\/)?pan\.lansod\.cn\/s\/[\w]+)/, host: /pan\.lansod\.cn/, name: '小蓝云盘(BlueDisk)', icon: 'lansod', }, 'now61': { reg: /((?:https?:\/\/)?www\.now61\.(?:com|cn)\/s\/[\w]+)/, host: /www\.now61\.(?:com|cn)/, name: '六一云盘', icon: '61', }, // 爱丽丝的记事本(爱丽丝网盘):天使动漫自建网盘 // 已核实的真实分享域名:drive.noire.cc / noire.cc(:8888) // 直接复用 noire 配置,保证带密码的分享链接可以正常识别并自动填码。 'alice': { reg: /((?:https?:\/\/)?(?:drive\.noire\.cc|noire\.cc(?::\d+)?)\/s\/[\w.-]+)/i, host: /(?:drive\.)?noire\.cc/, input: ['#pwd', 'input#pwd', 'input[name="pwd"]', '.pwd-input input', 'input[type="password"]', 'input[placeholder*="密码"], input[placeholder*="提取码"]'], button: ['button.MuiButton-containedSecondary', 'button:not([disabled])', '.MuiButton-containedSecondary', 'button[type="submit"]'], name: '爱丽丝的记事本', icon: 'cloud', storage: 'local', storagePwdName: 'tmp_noire_pwd', originalLink: true }, '520vip': { reg: /((?:https?:\/\/)?www\.(?:520-vip|eos-53)\.com\/file-\d+\.html)/, host: /www\.(?:520-vip|eos-53)\.com/, name: '520云盘', icon: '520', }, '567pan': { reg: /((?:\/\/)?www\.567(?:pan|yun|file|inc)\.(?:com|cn)\/file-\d+\.html)/, host: /www\.567(?:pan|yun|file|inc)\.(?:com|cn)/, name: '567盘', icon: '567', replaceHost: "www.567inc.com", }, 'ayunpan': { reg: /((?:https?:\/\/)?www\.ayunpan\.com\/file-\d+\.html)/, host: /www\.ayunpan\.com/, name: 'AYunPan', icon: 'ayunpan', }, 'iycdn': { reg: /((?:https?:\/\/)?www\.iycdn\.com\/file-\d+\.html)/, host: /www\.iycdn\.com/, name: '爱优网盘', icon: 'iycdn', }, 'feimaoyun': { reg: /((?:https?:\/\/)?www\.(?:feimaoyun\.com|feimao\.cloud)\/s\/[0-9a-zA-Z]+)/, host: /www\.(?:feimaoyun\.com|feimao\.cloud)/, name: '飞猫盘', icon: 'cat', }, 'uyunp': { reg: /((?:https?:\/\/)?download\.uyunp\.com\/share\/s\/short\/\?surl=[0-9a-zA-Z]+)/, host: /download\.uyunp\.com/, name: '优云下载', icon: 'download', }, 'xunniu': { reg: /(?:https?:\/\/)?www\.xunniu(?:fxp|wp|fx)\.com\/file-\d+\.html/, host: /www\.xunniu(?:fxp|wp|fx)\.com/, name: '迅牛网盘', icon: 'ox', }, 'xueqiupan': { reg: /(?:https?:\/\/)?www\.xueqiupan\.com\/file-\d+\.html/, host: /www\.xueqiupan\.com/, name: '雪球云盘', icon: 'snow', }, 'feiyunfile': { reg: /(?:https?:\/\/)?www\.feiyunfile\.com\/file\/[\w=]+\.html/, host: /www\.feiyunfile\.com/, name: '飞云网盘', icon: 'cloud', }, '116pan': { reg: /((?:https?:\/\/)?(?:www\.)?116pan\.(?:com|xyz)\/(?:s\/|file-)?[\w.\/-]+)/, host: /(?:www\.)?116pan\.(?:com|xyz)/, name: '116盘', icon: '116', }, /* 海外/国际化网盘 */ 'terabox': { reg: /((?:https?:\/\/)?(?:www\.)?(?:terabox|1024terabox|dubox|nephobox|teraboxapp)\.com\/(?:s\/[\w-]+|sharing\/link\?surl=[\w-]+))/, host: /(?:www\.)?(?:terabox|1024terabox|dubox|nephobox|teraboxapp)\.com/, name: 'TeraBox(海外盘)', icon: 'terabox', storage: 'hash' }, 'pikpak': { reg: /((?:https?:\/\/)?(?:www\.)?(?:mypikpak\.com|pikpak\.me|pikpak\.org)\/s\/[\w-]+)/, host: /(?:www\.)?(?:mypikpak\.com|pikpak\.me|pikpak\.org)/, name: 'PikPak', icon: 'pikpak', storage: 'hash' }, 'googledrive': { reg: /((?:https?:\/\/)?drive\.google\.com\/(?:file\/d\/[\w-]+|open\?id=[\w-]+|uc\?id=[\w-]+))/, host: /drive\.google\.com/, name: 'Google Drive', icon: 'google-drive', storage: 'hash' }, 'onedrive': { reg: /((?:https?:\/\/)?(?:1drv\.ms\/[\w]+|onedrive\.live\.com\/[\w?=&%.=\/-]+))/, host: /(?:1drv\.ms|onedrive\.live\.com)/, name: 'OneDrive', icon: 'microsoft-onedrive', storage: 'hash' }, 'dropbox': { reg: /((?:https?:\/\/)?(?:www\.)?dropbox\.com\/(?:s|sh|scl)\/[\w-]+(?:\/[\w-]+)*)/, host: /(?:www\.)?dropbox\.com/, name: 'Dropbox', icon: 'dropbox', storage: 'hash' }, 'mediafire': { reg: /((?:https?:\/\/)?(?:www\.)?mediafire\.com\/(?:file|folder)\/[\w]+)/, host: /(?:www\.)?mediafire\.com/, name: 'MediaFire', icon: 'mediafire', storage: 'hash' }, 'box': { reg: /((?:https?:\/\/)?(?:app|account)\.box\.com\/s\/[\w]+)/, host: /(?:app|account)\.box\.com/, name: 'Box', icon: 'box', storage: 'hash' }, 'gofile': { reg: /((?:https?:\/\/)?gofile\.io\/d\/[\w]+)/, host: /gofile\.io/, name: 'Gofile', icon: 'gofile', storage: 'hash' }, 'wetransfer': { reg: /((?:https?:\/\/)?(?:we\.tl\/[\w-]+|wetransfer\.com\/downloads\/[\w-]+))/, host: /(?:we\.tl|wetransfer\.com)/, name: 'WeTransfer', icon: 'wetransfer', storage: 'hash' }, '4shared': { reg: /((?:https?:\/\/)?(?:www\.)?4shared\.com\/(?:file|folder|web)\/[\w-]+)/, host: /(?:www\.)?4shared\.com/, name: '4shared', icon: '🔗', storage: 'hash' }, 'kdocs': { reg: /((?:https?:\/\/)?(?:www\.)?kdocs\.cn\/l\/[\w-]+)/, host: /kdocs\.cn/, name: '金山文档(WPS)', icon: 'kdocs', storage: 'hash' }, 'noire': { /* ═══════════════════════════════════════════════════════════════ * 爱丽丝的记事本(drive.noire.cc / noire.cc:8888) * - 轻之国度(LK) 使用的网盘分享平台 * - 链接格式:https://drive.noire.cc/s/{key} 或 https://noire.cc:8888/s/{key} * - 密码输入框:#pwd * - 确认按钮:button.MuiButton-containedSecondary(Material-UI) * - 使用 local 存储密码(原链接访问,不拼接 pwd 参数) * ═══════════════════════════════════════════════════════════════ */ reg: /((?:https?:\/\/)?(?:drive\.noire\.cc|noire\.cc(?::\d+)?)\/s\/[\w.-]+)/i, host: /(?:drive\.)?noire\.cc/, input: ['#pwd', 'input#pwd', 'input[name="pwd"]', '.pwd-input input', 'input[type="password"]', 'input[placeholder*="密码"], input[placeholder*="提取码"]'], button: ['button.MuiButton-containedSecondary', 'button:not([disabled])', '.MuiButton-containedSecondary', 'button[type="submit"]'], name: '爱丽丝的记事本', icon: 'cloud', storage: 'local', storagePwdName: 'tmp_noire_pwd', originalLink: true }, 'github': { reg: /((?:https?:\/\/)?github\.com\/[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+\/releases(?:\/[a-zA-Z0-9_.-]+)?)/, host: /github\.com/, name: 'GitHub Releases', icon: 'github', storage: 'hash' }, '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息流', icon: 'flowus', storage: 'hash' }, 'chrome': { reg: /^https?:\/\/chrome.google.com\/webstore\/.+?\/([a-z\d]{32})(?=[\/#?]|$)/, host: /chrome\.google\.com/, replaceHost: "chrome.crxsoso.com", name: 'Chrome商店', icon: '🌐', }, 'edge': { reg: /^https?:\/\/microsoftedge.microsoft.com\/addons\/.+?\/([a-z\d]{32})(?=[\/#?]|$)/, host: /microsoftedge\.microsoft\.com/, replaceHost: "microsoftedge.crxsoso.com", name: 'Edge商店', icon: 'microsoft-edge', }, 'firefox': { reg: /^https?:\/\/(reviewers\.)?(addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org)\/.*?(?:addon|review)\/([^/<>"'?#]+)/, host: /addons\.mozilla\.org/, replaceHost: "addons.crxsoso.com", name: 'Firefox商店', icon: 'firefox-mono', }, '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商店', icon: 'microsoft-store', }, /* ═══════════════════════════════════════════════════════════════ * 🛡️ 123云盘"终极防变化"保底规则(第二层防御) * 基于123云盘分享页面的核心特征: * 1. 域名中包含 "123pan"(如 123pan.com / 123pan.cn / xxx.123pan.net) * 2. 或域名为 6位数字.com(如 123684.com / 123865.com 等短链域名) * 3. 且路径中包含 /s/ 分享标识 * 即使123云盘未来更换域名/增加子域名,只要符合以上特征就能识别。 * 与第一层专用规则的区别:此规则优先级更低,仅在前两层都未命中时触发。 * ═══════════════════════════════════════════════════════════════ */ '_123pan_fallback': { reg: /((?:https?:\/\/)?(?:(?:(?:[a-z0-9-]+\.)*123pan\.(?:com|cn|net|org|io|me)|(?:\d{6})\.(?:com|cn|net|org|io|me))\/(?:s|v|f|d|share)\/[\w.-]+|(?:(?:[a-z0-9]+\.)*share\.123pan\.(?:cn|com)\/123pan\/[\w.-]+))(?:\.html)?(?:\?[^\\s]*)?)/i, host: /123pan\.(?:com|cn|net|org|io|me)|\d{6}\.(?:com|cn|net|org|io|me)|(?:[a-z\d]+\.)*share\.123pan\.(?:cn|com)/i, name: '123云盘(通用识别)', icon: '123pan', storage: 'hash', _fallback: true, _panType: '123pan', _validateHost: true }, /* ═══════════════════════════════════════════════════════════════ * 🛡️ 通用保底规则(Fallback) * 放在最后,作为"兜底":当以上所有专用规则都未匹配时, * 若文本中包含看起来像网盘分享链接的 URL,也会被识别。 * 用于应对:网盘更换域名/子域名/新开分站等突发情况。 * 注意:保底规则的优先级最低,不会覆盖已有的专用规则。 * ═══════════════════════════════════════════════════════════════ */ '_fallback_pan': { // 匹配常见的网盘路径模式:/s/xxx /share/xxx /file/xxx /d/xxx 等 reg: /((?:https?:\/\/)?[\w\-.]+\.(?:com|cn|net|org|io|me|xyz|top|vip|cloud|site|online|link|app)\/(?:s|share|file|d|f|v|dl|download|yun|pan|drive|folder)\/[\w\-./?=%&]+)/i, host: null, // 保底规则不设 host,不参与域名自动检测 name: '未知网盘(通用识别)', icon: 'cloud', _fallback: true // 标记为保底规则 }, }; /* 支持网盘分类(用于「支持网盘」抽屉面板展示) */ const PAN_GROUPS = [ ['主流网盘', ['baidu', 'aliyun', 'quark', 'UC', 'RUC', 'weiyun', 'lanzou', 'lanzou-Pre', '123pan', 'CMCCdrive', 'CUCCdrive', 'CTCCdrive', 'xunlei', '360', '115', 'cowtransfer', 'ctfile', 'wenshushu', 'vdisk', 'jianguoyun', 'pikpak', 'qqfile', 'googledrive', 'mega']], ['海外/国际网盘', ['terabox', 'onedrive', 'dropbox', 'mediafire', 'box', 'gofile', 'wetransfer', '4shared', 'nitroflare', 'kdocs', 'github', 'flowus', '4275']], ['小众网盘', ['feijipan', 'yukaidi', 'lansod', 'now61', 'noire', '520vip', '567pan', 'ayunpan', 'iycdn', 'feimaoyun', 'uyunp', 'dudujb', 'xunniu', 'xueqiupan', '77file', 'ownfile', 'feiyunfile', 'yifile', 'dufile', '116pan']], ['商店', ['chrome', 'edge', 'firefox', 'microsoft']], ]; let main = { lastText: "lorem&", // 弹窗外观默认值 DEFAULTS: { theme: 'win11', appearance: 'system', effect: 'none', position: 'top', width: 500, height: 280, sizePreset: 'standard', // 星号密码显示助手默认值 pwdReveal: true, pwdMethod: 0, pwdWait: 300 }, //初始化配置数据 initValue() { let value = [{ 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' }, { name: 'setting_theme', value: 'win11' }, { name: 'setting_appearance', value: 'system' }, { name: 'setting_effect', value: 'none' }, { name: 'setting_position', value: 'top' }, { name: 'setting_width', value: 500 }, { name: 'setting_height', value: 280 }, { name: 'setting_size_preset', value: 'standard' }, { name: 'setting_pwd_reveal', value: true }, { name: 'setting_pwd_method', value: 0 }, { name: 'setting_pwd_wait', value: 300 }]; value.forEach((v) => { if (util.getValue(v.name) === undefined) { util.setValue(v.name, v.value); } }); }, // 监听选择事件 addPageListener() { document.addEventListener("mouseup", this.smartIdentify.bind(this), true); document.addEventListener("keydown", this.pressKey.bind(this), true); }, // 获取选择内容的HTML和文本(增加兼容性) 或 DOM(节点遍历) getSelectionHTML(selection, isDOM = false) { const testDiv = document.createElement("div"); if (!selection.isCollapsed) { const docFragment = selection.getRangeAt(0).cloneContents(); testDiv.appendChild(docFragment); } return isDOM ? testDiv : selection.toString(); }, smartIdentify(event, str = '') { let selection = window.getSelection(); let text = str || this.getSelectionHTML(selection); if (text !== this.lastText && text !== '') { let start = performance.now(); this.lastText = text; // 使用 parseAllLinks 获取所有链接 let allLinks = this.parseAllLinks(text); let pwd = this.parsePwd(text); // 如果 parseAllLinks 没找到,尝试超链接方式 if (allLinks.length === 0) { let parentObj = this.parseParentLink(selection); if (parentObj.link) { allLinks = [parentObj]; } } if (allLinks.length > 0) { // 规范化所有链接 allLinks.forEach(item => { if (!/https?:\/\//.test(item.link)) { item.link = 'https://' + item.link; } }); let end = performance.now(); let time = (end - start).toFixed(3); // ===== 单链接:保持原有弹窗样式 ===== if (allLinks.length === 1) { let linkObj = allLinks[0]; util.clog(`文本识别结果:${linkObj.name} 链接:${linkObj.link} 密码:${pwd} 耗时:${time}毫秒`); let option = { toast: false, showCancelButton: true, position: this.getSwalPosition(), title: `${getPanIcon(linkObj.icon, 22, false)}发现${linkObj.name}链接`, html: `${!!pwd ? '密码:' + pwd : '是否打开?'}`, confirmButtonText: '打开', cancelButtonText: '关闭', customClass, width: util.getValue('setting_width'), ...(util.getValue('setting_height') ? {padding: '1.25em'} : {}) }; if (util.getValue('setting_timer_open')) { option.timer = util.getValue('setting_timer'); option.timerProgressBar = true; } util.setValue('setting_success_times', util.getValue('setting_success_times') + 1); this.applyTheme(); this.applySize(); Swal.fire(option).then((res) => { this.clearSize(); this.lastText = 'lorem&'; selection.empty(); if (res.isConfirmed || res.dismiss === 'timer') { this._openLink(linkObj, pwd); } }); } // ===== 多链接:展示列表弹窗 ===== else { util.clog(`文本识别结果:发现 ${allLinks.length} 个网盘链接 耗时:${time}毫秒`); // 构建多链接列表 HTML let listHtml = ''; allLinks.forEach((item, idx) => { listHtml += ` ${getPanIcon(item.icon, 18, false)} ${item.name} ${this._truncateLink(item.link)} `; }); listHtml += ''; util.setValue('setting_success_times', util.getValue('setting_success_times') + 1); this.applyTheme(); // 多链接弹窗尺寸由 didOpen 内联样式单独锁定(500×800), // 设置跳过标志,阻止 applySize 写入全局 .swal2-popup 规则覆盖锁定尺寸 this._skipNextSize = true; this.applySize(); // ===== 多链接弹窗尺寸(硬约束:宽 500 / 高 800 上限,CSS 层兜底锁死)===== // 单项布局:padding 10*2 + 内容 24 + border 2 = 46,与 gap 6 保持一致 const LIST_ITEM_HEIGHT = 46; // 单个链接条目占高(px) const LIST_GAP = 6; // 列表项间距 const LIST_TOP_PAD = 10; // 列表容器顶部留白 const LIST_BOT_PAD = 10; // 列表容器底部留白 const HEADER_H = 56; // 弹窗标题栏高度 const TIP_H = 44; // 底部提示卡片高度 const BTN_H = 88; // 按钮区高度 + 弹窗内外边距 const ITEMS_VISIBLE = 12; // 一次最多完整露出的链接数 const MAX_W = 500; // 弹窗最大宽度(硬上限) const MAX_H = 800; // 弹窗最大高度(硬上限) const MIN_W = 420; // 弹窗最小宽度 const MIN_H = 220; // 弹窗最小高度 const visibleCount = Math.min(allLinks.length, ITEMS_VISIBLE); // 列表内容净高:n 个条目 + (n-1) 个间距 const itemsContentH = visibleCount * LIST_ITEM_HEIGHT + Math.max(visibleCount - 1, 0) * LIST_GAP; // 列表容器高度:内容高 + 上下留白 const listBoxH = itemsContentH + LIST_TOP_PAD + LIST_BOT_PAD; // 弹窗自然高度 = 标题 + 列表 + 提示 + 按钮,链接少时自然更矮 const naturalH = HEADER_H + listBoxH + TIP_H + BTN_H; // 最终尺寸:夹在 [最小, 最大] 区间内,链接少绝不撑高; // 超过 12 个时弹窗整体锁死 500×800,列表内部启用滚动条(下拉栏)查看剩余链接 const popupHeight = allLinks.length > ITEMS_VISIBLE ? MAX_H : Math.min(MAX_H, Math.max(MIN_H, naturalH)); const popupWidth = MAX_W; Swal.fire({ toast: false, showCancelButton: true, showDenyButton: false, position: this.getSwalPosition(), title: `📋 发现 ${allLinks.length} 个网盘链接`, html: listHtml + `${allLinks.length > ITEMS_VISIBLE ? `💡 共 ${allLinks.length} 个链接,仅显示前 ${ITEMS_VISIBLE} 个,滚动查看剩余 ↓` : '💡 点击列表项可单独打开,或一键全部打开'}`, confirmButtonText: '全部打开', cancelButtonText: '关闭', customClass: { ...customClass, popup: (customClass.popup || '') + ' panai-multi-popup' }, width: popupWidth, height: popupHeight, heightAuto: false, didOpen: (popup) => { // ===== 弹窗尺寸硬锁死(修复:CSS 类名未命中 + applySize 全局覆盖 height:auto 导致 500×800 失效)===== // 用内联样式直接打到 DOM,优先级高于任何外部 CSS / !important,彻底兜底 if (popup) { const el = popup.classList ? popup : (popup instanceof Element ? popup : null) || document.querySelector('.panai-multi-popup'); if (el) { el.style.setProperty('width', popupWidth + 'px', 'important'); el.style.setProperty('max-width', popupWidth + 'px', 'important'); el.style.setProperty('height', popupHeight + 'px', 'important'); el.style.setProperty('min-height', popupHeight + 'px', 'important'); el.style.setProperty('max-height', '800px', 'important'); el.style.setProperty('overflow', 'hidden', 'important'); } // 列表容器高度:链接数 ≤12 时恰好装下全部条目并隐藏滚动条; // 超过 12 个时锁死为恰好装下 12 项的高度,多余项通过内部滚动条(下拉栏)查看 const listEl = popup.querySelector('.panai-multi-list'); if (listEl) { const items = listEl.querySelectorAll('.panai-multi-item'); const itemTotal = items.length * LIST_ITEM_HEIGHT + Math.max(items.length - 1, 0) * LIST_GAP; const boxH = itemTotal + LIST_TOP_PAD + LIST_BOT_PAD; if (items.length <= ITEMS_VISIBLE) { // 12 个及以内:完整显示,隐藏滚动条 listEl.style.maxHeight = boxH + 'px'; listEl.style.height = boxH + 'px'; listEl.style.overflowY = 'hidden'; } else { // 超过 12 个:锁定为 12 项的精确高度,启用内部下拉滚动条 const scrollH = ITEMS_VISIBLE * LIST_ITEM_HEIGHT + (ITEMS_VISIBLE - 1) * LIST_GAP + LIST_TOP_PAD + LIST_BOT_PAD; listEl.style.maxHeight = scrollH + 'px'; listEl.style.height = scrollH + 'px'; listEl.style.overflowY = 'auto'; listEl.classList.add('is-scrollable'); } } // html-container 在多链接弹窗中不能受 applySize 的 max-height 限制, // 否则会把列表压到 60px 并强制出现内部滚动 const htmlBox = popup.querySelector('.swal2-html-container'); if (htmlBox) { htmlBox.style.maxHeight = 'none'; htmlBox.style.overflow = 'visible'; } } } }).then((res) => { this.clearSize(); this.lastText = 'lorem&'; selection.empty(); if (res.isConfirmed || res.dismiss === 'timer') { // 全部打开 allLinks.forEach((item, idx) => { setTimeout(() => this._openLink(item, pwd), idx * 300); // 错开打开,避免被浏览器拦截 }); } }); // 事件委托:点击单个列表项 setTimeout(() => { const container = document.querySelector('.panai-multi-list'); if (container) { // 阻止列表被误判为可拖拽选区:用户点按钮时不再出现底部滑动选择条/下滑栏 container.style.userSelect = 'none'; container.style.webkitUserSelect = 'none'; container.addEventListener('selectstart', (e) => { e.preventDefault(); }); container.querySelectorAll('.panai-multi-item').forEach(el => { el.addEventListener('mousedown', (e) => { e.preventDefault(); }); el.addEventListener('click', () => { const idx = parseInt(el.dataset.idx); const item = allLinks[idx]; if (item) { // 高亮被点击的项 container.querySelectorAll('.panai-multi-item').forEach(e => e.classList.remove('panai-multi-active')); el.classList.add('panai-multi-active'); setTimeout(() => { Swal.close(); this._openLink(item, pwd); }, 150); } }); }); } }, 100); } } } }, // 截断过长的链接用于展示 _truncateLink(link) { if (!link) return ''; if (link.length <= 50) return link; return link.substring(0, 30) + '...' + link.substring(link.length - 15); }, // 打开单个链接(统一处理密码拼接) _openLink(linkObj, pwd) { if (!linkObj || !linkObj.link) return; let link = linkObj.link; let active = util.getValue('setting_active_in_front'); // originalLink: true 时保留原链接,不拼接 pwd if (linkObj.originalLink) { GM_openInTab(link, {active}); return; } if (pwd && linkObj.storage) { let extra = `${link}?pwd=${pwd}#${pwd}`; if (~link.indexOf('?')) { extra = `${link}&pwd=${pwd}#${pwd}`; } if (linkObj.storage === 'local') { util.setValue(linkObj.storagePwdName, pwd); } GM_openInTab(extra, {active}); } else if (pwd) { // 没有配置 storage 的网盘(多为识别型),也尝试把密码拼到 URL 上 // 兼容常见参数名 pwd / code,避免密码丢失 let sep = ~link.indexOf('?') ? '&' : '?'; let extra = `${link}${sep}pwd=${pwd}#${pwd}`; GM_openInTab(extra, {active}); } else { GM_openInTab(`${link}`, {active}); } }, pressKey(event) { if (event.key === 'Enter') { let confirmBtn = document.querySelector('.panai-container .swal2-confirm'); confirmBtn && confirmBtn.click(); } if (event.key === 'Escape') { let cancelBtn = document.querySelector('.panai-container .swal2-cancel'); cancelBtn && cancelBtn.click(); } }, addHotKey() { let hotkey = util.getValue('setting_hotkeys'); // 若 hotkeys 库未加载成功则不注册,避免中断脚本 if (typeof hotkeys === 'function') { hotkeys(hotkey, (event, handler) => { event.preventDefault(); this.showIdentifyBox(); }); } }, //正则解析网盘链接(支持多链接:返回所有匹配到的链接数组) parseLink(text = '') { // 单链接模式:返回第一个匹配(兼容旧调用) // 多链接模式:通过 parseAllLinks 获取全部 return this._parseFirstLink(text); }, // 解析文本中所有网盘链接 parseAllLinks(text = '') { let results = []; if (!text) return results; try { text = decodeURIComponent(text); } catch { } let processed = text.replace(/[点點]/g, '.'); processed = processed.replace(/[\u4e00-\u9fa5()(),\u200B,\uD83C-\uDBFF\uDC00-\uDFFF]/g, ''); processed = processed.replace(/lanzous/g, 'lanzouw'); // 后置验证函数 const validate123PanHost = (link) => { if (!link) return false; try { let host = link.replace(/^https?:\/\//, '').split('/')[0].replace(/^www\./, ''); if (/123pan\.(?:com|cn|net|org|io|me)/.test(host)) { const idx = host.indexOf('123pan'); const before = host.substring(0, idx); return before === '' || before.endsWith('.'); } if (/share\.123pan\.(?:cn|com)/.test(host)) return true; if (/^\d{6}\.(?:com|cn|net|org|io|me)/.test(host)) return true; return false; } catch (e) { return false; } }; const isStandaloneUrl = (link) => { if (!link) return true; const idx = processed.indexOf(link); if (idx <= 0) return true; const charBefore = processed.charAt(idx - 1); return /[\s,。、;:()()【】[\]{}<>"']/.test(charBefore); }; // 已匹配的链接区间记录(用于排除通用保底对已匹配区间的重复匹配) const matchedRanges = []; const isInMatchedRange = (start, end) => { for (const r of matchedRanges) { if (start >= r[0] && start < r[1]) return true; } return false; }; // 辅助:对单条规则匹配文本中所有不重叠的链接 const matchAllInText = (pattern, name, val, skipIfOverlap = false) => { const found = []; let globalPattern; try { globalPattern = new RegExp(pattern.source, (pattern.flags || '').includes('g') ? pattern.flags : (pattern.flags || '') + 'g'); } catch(e) { return found; } let match; try { while ((match = globalPattern.exec(processed)) !== null) { let link = match[0]; let matchStart = match.index; let matchEnd = matchStart + link.length; // 123云盘后置验证 if (val._validateHost && (!validate123PanHost(link) || !isStandaloneUrl(link))) { continue; } // 如果开启了区间检查,且当前匹配落在已匹配区间内,跳过 if (skipIfOverlap && isInMatchedRange(matchStart, matchEnd)) { continue; } found.push({ name: val.name, link: link, storage: val.storage, storagePwdName: val.storagePwdName || null, icon: val.icon, originalLink: val.originalLink || false }); // 记录已匹配区间 matchedRanges.push([matchStart, matchEnd]); // 防止零宽匹配导致死循环 if (match.index === globalPattern.lastIndex) globalPattern.lastIndex++; } } catch(e) {} return found; }; const seen = new Set(); const addUnique = (items) => { items.forEach(item => { if (!seen.has(item.link)) { seen.add(item.link); results.push(item); } }); }; // 第一轮:专用规则(精确匹配,优先) for (let name in opt) { let val = opt[name]; if (!val.reg || val._fallback) continue; try { addUnique(matchAllInText(val.reg, name, val, false)); } catch (e) { util.clog(`[网盘识别] 规则 ${name} 匹配异常:${e.message}`); } } // 第二轮:123云盘保底(仅匹配尚未被专用规则匹配的区间) for (let name in opt) { let val = opt[name]; if (!val.reg || !val._fallback || val._panType !== '123pan') continue; try { addUnique(matchAllInText(val.reg, name, val, true)); } catch (e) { util.clog(`[网盘识别] 123云盘保底规则匹配异常:${e.message}`); } } // 第三轮:通用保底(仅匹配尚未被任何规则匹配的区间) // 关键修复:跳过已被专用规则匹配的链接,避免出现"未知网盘(通用识别)" for (let name in opt) { let val = opt[name]; if (!val.reg || !val._fallback || val._panType === '123pan') continue; try { addUnique(matchAllInText(val.reg, name, val, true)); } catch (e) { util.clog(`[网盘识别] 通用保底规则匹配异常:${e.message}`); } } return results; }, // 解析第一个匹配到的链接(保持原有行为兼容) _parseFirstLink(text = '') { let obj = {name: ', link: ', storage: ', storagePwdName: ', icon: ''}; let all = this.parseAllLinks(text); if (all.length > 0) { return all[0]; } return obj; }, //正则解析超链接类型网盘链接 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', ":"); text = text.replace(/(?:本帖)?隐藏的?内容[::]?/, ""); // ① 优先匹配「关键词 + 提取码」紧邻的写法(口令 / 提取码 / 密码 / 访问码 / 解压码 / key / pwd 等) // 支持中文全半角标点,如:提取码:3x9k / 密码:abcd1234 / 口令:xyz789 let kwReg = /wss:[a-zA-Z0-9]+|(?<=\s*(?:密|提取|访问|解压|口令|验证码|訪問|key|password|passwd|pwd|#|\?p=)\s*[码碼]?\s*[::=]?\s*)[a-zA-Z0-9]{3,12}/i; let m = text.match(kwReg); if (m) return m[0]; // ② 兜底:从 URL 参数里抓 pwd / passwd / code(如 ?pwd=xxxx) let urlReg = /[?](?:pwd|passwd|code|key)=([a-zA-Z0-9]{3,12})/i; let u = text.match(urlReg); if (u) return u[1]; // ③ 再次兜底:行末 / 空白隔开的独立 4~8 位短码(形如"……3x9k"孤立成 token) let tailReg = /(?:^|[\s,。、;:>])[a-zA-Z0-9]{4,8}(?=[\s,。、;:<]|$)/; let t = text.match(tailReg); if (t) { let code = t[0].replace(/^[\s,。、;:>]+/, ''); return code; } return ''; }, //根据域名检测网盘类型 panDetect() { let hostname = location.hostname; for (let name in opt) { let val = opt[name]; // 防御①:host 缺失或为保底规则的条目跳过 if (!val.host || val._fallback) continue; try { if (val.host.test(hostname)) { return name; } } catch (e) { // 防御②:单个正则异常不影响整体检测 util.clog(`[域名检测] 规则 ${name} 检测异常:${e.message}`); } } return ''; }, //自动填写密码 autoFillPassword() { let query = util.parseQuery('pwd|p'); let hash = location.hash.slice(1).replace(/\W/g, "") //hash中可能存在密码,需要过滤掉非密码字符 let pwd = query || hash; let panType = this.panDetect(); for (let name in opt) { let val = opt[name]; if (panType === name) { if (val.storage === 'local') { //当前local存储的密码不一定是当前链接的密码,用户可能通过url直接访问或者恢复页面,这样取出来的密码可能是其他链接的 //如果能从url中获取到密码,则应该优先使用url中获取的密码 pwd = pwd || util.getValue(val.storagePwdName); pwd && this.doFillAction(val.input, val.button, pwd); } if (val.storage === 'hash') { if (!/^(?:wss:[a-zA-Z\d]+|[a-zA-Z0-9]{3,8})$/.test(pwd)) { //过滤掉不正常的Hash return; } //部分仅识别打开、未配置提取码输入框的网盘,跳过自动填码,避免 querySelector(undefined) 报错 pwd && val.input && this.doFillAction(val.input, val.button, pwd); } } } }, doFillAction(inputSelector, buttonSelector, pwd) { // inputSelector / buttonSelector 可能是数组(多个选择器备选) const inputSelectors = Array.isArray(inputSelector) ? inputSelector : [inputSelector]; const buttonSelectors = Array.isArray(buttonSelector) ? buttonSelector : [buttonSelector]; const maxAttempts = 40; // 最多尝试 40 次(约 32 秒),应对慢加载的 SPA 页面 let attempts = 0; let ins = setInterval(async () => { attempts++; if (attempts > maxAttempts) { clearInterval(ins); return; } // 遍历所有备选选择器,找到第一个可见的输入框 let input = null; for (const sel of inputSelectors) { if (!sel) continue; const el = util.query(sel); if (el && !util.isHidden(el) && el.offsetParent !== null) { input = el; break; } } if (!input) return; // 还没找到输入框,继续等待 clearInterval(ins); Swal.fire({ toast: true, position: 'top', showCancelButton: false, showConfirmButton: false, title: 'AI已识别到密码!正自动帮您填写', icon: 'success', timer: 2000, customClass }); // ===== 兼容 Vue2 / Vue3 / React 的赋值方式 ===== this._setNativeValue(input, pwd); // 如果设置了自动提交,则点击按钮 if (util.getValue('setting_auto_click_btn')) { await util.sleep(800); let button = null; for (const sel of buttonSelectors) { if (!sel) continue; const el = util.query(sel); if (el && !el.disabled && !util.isHidden(el)) { button = el; break; } } if (button) { button.click(); } else { // 兜底:尝试 submit 表单或按 Enter const form = input.closest('form'); if (form) { form.dispatchEvent(new Event('submit', {bubbles: true, cancelable: true})); } else { input.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true})); } } } }, 800); }, // 兼容 Vue3 的输入框赋值(Vue3 使用 Proxy,需要特殊处理) _setNativeValue(element, value) { const proto = element instanceof HTMLTextAreaElement ? window.HTMLTextAreaElement.prototype : window.HTMLInputElement.prototype; const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set; if (setter) { setter.call(element, value); } else { element.value = value; } // 触发 input 事件(兼容 Vue2 / React) element.dispatchEvent(new Event('input', {bubbles: true})); element.dispatchEvent(new Event('change', {bubbles: true})); }, //重置识别次数 clearIdentifyTimes() { this.applyTheme(); let res = Swal.fire({ showCancelButton: true, title: '确定要重置识别次数吗?', icon: 'warning', confirmButtonText: '确定', cancelButtonText: '取消', customClass }).then(res => { this.lastText = 'lorem&'; if (res.isConfirmed) { util.setValue('setting_success_times', 0); history.go(0); } }); }, //识别输入框中的内容 showIdentifyBox() { this.applyTheme(); Swal.fire({ title: '识别剪切板中文字', input: 'textarea', inputPlaceholder: '若选方式一,请按 Ctrl+V 粘贴要识别的文字', html: `提示:在任意网页按下 ${util.getValue("setting_hotkeys")} 键可快速打开本窗口。支持以下两种方式:方式一:直接粘贴文字到输入框,点击“识别方框内容”按钮。方式二:点击“读取剪切板”按钮。会弹出“授予网站读取剪切板”权限,同意后会自动识别剪切板中的文字。`, showCloseButton: false, showDenyButton: true, confirmButtonText: '识别方框内容', denyButtonText: '读取剪切板', customClass }).then(res => { if (res.isConfirmed) { this.smartIdentify(null, res.value); } if (res.isDenied) { navigator.clipboard.readText().then(text => { this.smartIdentify(null, text); }).catch(() => { toast.fire({title: '读取剪切板失败,请先授权或手动粘贴后识别!', icon: 'error'}); }); } }); }, // ===== 星号密码显示助手 ===== // 保存原始的 MutationObserver 引用(防止被部分网站覆盖) _pwdObserver: null, /** * 初始化星号密码显示功能 * 在密码框上绑定对应事件,根据 setting_pwd_method 选择触发方式 */ initPwdReveal() { // 保存原生 MutationObserver this._pwdObserver = window.MutationObserver; if (!util.getValue('setting_pwd_reveal')) return; this._pwdBindAll(); // 监听动态添加的密码框(SPA 兼容) const doc = window.document; const obs = new this._pwdObserver(() => this._pwdBindAll()); obs.observe(doc.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['type'] }); }, /** * 遍历页面中所有密码框并绑定事件 */ _pwdBindAll() { const method = parseInt(util.getValue('setting_pwd_method')) || 0; const wait = parseInt(util.getValue('setting_pwd_wait')) || 300; const inputs = document.querySelectorAll('input[type="password"]'); inputs.forEach(input => { if (input.dataset.pwdBound === '1') return; input.dataset.pwdBound = '1'; switch (method) { case 0: this._pwdBindHover(input, wait); break; case 1: this._pwdBindDblClick(input); break; case 2: this._pwdBindFocus(input); break; case 3: this._pwdBindCtrlClick(input); break; } }); }, /** * 方式0:鼠标悬浮时显示 */ _pwdBindHover(input, wait) { input.addEventListener('mouseover', () => { input._isMouseOver = true; setTimeout(() => { if (input._isMouseOver) input.type = 'text'; }, wait); }, false); input.addEventListener('mouseout', () => { input._isMouseOver = false; input.type = 'password'; }, false); input.addEventListener('blur', () => { input.type = 'password'; }, false); input.addEventListener('keydown', (e) => { if (e.keyCode === 13) input.type = 'password'; }, false); }, /** * 方式1:双击切换显示/隐藏 */ _pwdBindDblClick(input) { input.addEventListener('dblclick', () => { input.type = input.type === 'password' ? 'text' : 'password'; }, false); input.addEventListener('blur', () => { input.type = 'password'; }, false); input.addEventListener('keydown', (e) => { if (e.keyCode === 13) input.type = 'password'; }, false); }, /** * 方式2:单击(聚焦)时显示 */ _pwdBindFocus(input) { input.addEventListener('focus', () => { input.type = 'text'; }, false); input.addEventListener('blur', () => { input.type = 'password'; }, false); input.addEventListener('keydown', (e) => { if (e.keyCode === 13) input.type = 'password'; }, false); }, /** * 方式3:按住 Ctrl 单击时显示 */ _pwdBindCtrlClick(input) { let isHide = true, notPressCtrl = true, onlyCtrl = true; input.addEventListener('blur', () => { input.type = 'password'; isHide = true; notPressCtrl = true; onlyCtrl = true; }, false); input.addEventListener('keyup', (e) => { if (e.keyCode === 17) { // Ctrl if (onlyCtrl) isHide = !isHide; else isHide = false; input.type = isHide ? 'password' : 'text'; notPressCtrl = true; onlyCtrl = true; } }, false); input.addEventListener('keydown', (e) => { if (e.keyCode === 13) { input.type = 'password'; isHide = true; notPressCtrl = true; onlyCtrl = true; } else if (e.keyCode === 17) { // Ctrl if (notPressCtrl) { input.type = 'text'; notPressCtrl = false; onlyCtrl = true; } } else { onlyCtrl = notPressCtrl; } }, false); }, //显示设置 showSettingBox() { const themes = [ ['light', '☀️', '明亮浅色'], ['dark', '🌙', '深夜暗黑'], ['win11', '🪟', 'Windows 11'], ['win10', '🪟', 'Windows 10'], ['win81', '🧊', 'Windows 8.1'], ['win8', '🟦', 'Windows 8'], ['win7', '🔷', 'Windows 7'], ['vista', '🟦', 'Windows Vista'], ['xp', '🟢', 'Windows XP'], ['me', '💠', 'Windows ME'], ['98', '🩶', 'Windows 98'], ['3x', '🕹️', 'Windows 3.x'], ['12', '📺', 'Windows 1.0 / 2.0'] ]; const appearances = [['system', '🌗 跟随系统'], ['light', '☀️ 始终浅色'], ['dark', '🌙 始终深色']]; const effects = [['none', '🚫 无'], ['transparent', '👁️ 透明'], ['blur', '🌫️ 模糊'], ['acrylic', '💠 亚克力模糊'], ['mica', '✨ 云母']]; const positions = [['top', '上'], ['center', '中'], ['bottom', '下']]; const presets = [['compact', '紧凑'], ['standard', '标准'], ['wide', '宽屏']]; const cur = { theme: util.getValue('setting_theme'), appearance: util.getValue('setting_appearance'), effect: util.getValue('setting_effect'), position: util.getValue('setting_position'), width: util.getValue('setting_width'), height: util.getValue('setting_height'), preset: util.getValue('setting_size_preset') }; const appearanceRadios = appearances.map(([k, n]) => `${n}`).join(''); const effectRadios = effects.map(([k, n]) => `${n}`).join(''); const themeCards = themes.map(([k, ic, name]) => ` ${ic}${name} `).join(''); let html = ` 🎨 外观设置 🌗 深浅模式 ${appearanceRadios} 🖼️ 背景材质 ${effectRadios} 🎭 主题风格 ${themeCards} 📐 弹窗布局 弹窗位置 ${positions.map(([k, n]) => `${n}`).join('')} 尺寸预设 ${presets.map(([k, n]) => `${n}`).join('')} 自定义大小 宽 × 高 px ⚙️ 行为设置 填写密码后自动提交 前台打开网盘标签页 倒计时结束自动打开 倒计时时长 (${util.getValue('setting_timer') / 1000}秒) 快捷键设置 🔓 星号密码显示 🔑 启用星号密码显示 显示密码方式 ${[['0','🖱️ 鼠标悬浮'],['1','🖱️ 双击'],['2','🖱️ 单击'],['3','⌨️ Ctrl+单击']].map(([k,n]) => `${n}`).join('')} 显示延迟 (${util.getValue('setting_pwd_wait')}毫秒) `; const SET_W = 1233; const SET_H = Math.round(Math.min(window.innerHeight - 16, 1287)); const swalOpts = { title: '识别助手配置', html, showCloseButton: true, showCancelButton: true, confirmButtonText: '保存', cancelButtonText: '恢复默认', footer: '点击查看 使用说明,助手免费开源,Powered by 油小猴', customClass, width: SET_W, heightAuto: true, // 设置页使用独立大高度,不继承用户自定义的弹窗尺寸 didOpen: () => { const popup = document.querySelector('.panai-container .swal2-popup'); if (popup) { popup.style.width = SET_W + 'px'; popup.style.maxWidth = '96vw'; popup.style.maxHeight = SET_H + 'px'; popup.style.height = SET_H + 'px'; const body = popup.querySelector('.swal2-html-container'); if (body) { body.style.maxHeight = (SET_H - 180) + 'px'; body.style.overflowY = 'auto'; body.style.overflowX = 'hidden'; body.style.padding = '0 4px'; } } } }; this.applyTheme(); Swal.fire(swalOpts).then((res) => { if (res.dismiss === 'cancel') { // 恢复默认 const def = this.DEFAULTS; util.setValue('setting_theme', def.theme); util.setValue('setting_appearance', def.appearance); util.setValue('setting_effect', def.effect); util.setValue('setting_position', def.position); util.setValue('setting_width', def.width); util.setValue('setting_height', def.height); util.setValue('setting_size_preset', def.sizePreset); util.setValue('setting_auto_click_btn', true); util.setValue('setting_active_in_front', true); util.setValue('setting_timer_open', false); util.setValue('setting_timer', 5000); util.setValue('setting_hotkeys', 'F1'); // 星号密码显示助手 - 恢复默认 util.setValue('setting_pwd_reveal', true); util.setValue('setting_pwd_method', 0); util.setValue('setting_pwd_wait', 300); toast.fire({title: '已恢复全部默认设置', icon: 'success'}); } }); // 主题卡片点击 const box = document.querySelector('.panai-container .panai-set-layout') || document.querySelector('.panai-container .panai-set-wrap'); if (!box) return; // 外观模式单选 box.querySelectorAll('.panai-radio[data-app]').forEach(r => { r.addEventListener('click', () => { box.querySelectorAll('.panai-radio[data-app]').forEach(x => x.classList.remove('on')); r.classList.add('on'); const inp = r.querySelector('input'); inp.checked = true; util.setValue('setting_appearance', inp.value); this.applyTheme(); }); }); // 背景材质单选 box.querySelectorAll('.panai-radio[data-eff]').forEach(r => { r.addEventListener('click', () => { box.querySelectorAll('.panai-radio[data-eff]').forEach(x => x.classList.remove('on')); r.classList.add('on'); const inp = r.querySelector('input'); inp.checked = true; util.setValue('setting_effect', inp.value); this.applyTheme(); }); }); // 主题卡片点击 box.querySelectorAll('.panai-theme-card').forEach(card => { card.addEventListener('click', () => { box.querySelectorAll('.panai-theme-card').forEach(c => c.classList.remove('active')); card.classList.add('active'); util.setValue('setting_theme', card.dataset.theme); this.applyTheme(); }); }); // 位置单选 box.querySelectorAll('.panai-radio[data-pos]').forEach(r => { r.addEventListener('click', () => { box.querySelectorAll('.panai-radio[data-pos]').forEach(x => x.classList.remove('on')); r.classList.add('on'); const inp = r.querySelector('input'); inp.checked = true; util.setValue('setting_position', inp.value); }); }); // 尺寸预设 const PRESET_MAP = {compact: [420, 240], standard: [500, 280], wide: [620, 360]}; box.querySelectorAll('.panai-radio[data-pre]').forEach(r => { r.addEventListener('click', () => { box.querySelectorAll('.panai-radio[data-pre]').forEach(x => x.classList.remove('on')); r.classList.add('on'); const inp = r.querySelector('input'); inp.checked = true; const [w, h] = PRESET_MAP[inp.value] || PRESET_MAP.standard; util.setValue('setting_size_preset', inp.value); util.setValue('setting_width', w); util.setValue('setting_height', h); box.querySelector('#S-width').value = w; box.querySelector('#S-height').value = h; }); }); box.querySelector('#S-width').addEventListener('input', e => { util.setValue('setting_width', e.target.value); util.setValue('setting_size_preset', 'custom'); box.querySelectorAll('.panai-radio[data-pre]').forEach(x => x.classList.remove('on')); }); box.querySelector('#S-height').addEventListener('input', e => { util.setValue('setting_height', e.target.value); util.setValue('setting_size_preset', 'custom'); box.querySelectorAll('.panai-radio[data-pre]').forEach(x => x.classList.remove('on')); }); box.querySelector('#S-Auto').addEventListener('change', (e) => { util.setValue('setting_auto_click_btn', e.target.checked); }); box.querySelector('#S-Active').addEventListener('change', (e) => { util.setValue('setting_active_in_front', e.target.checked); }); box.querySelector('#S-Timer-Open').addEventListener('change', (e) => { let rangeWrapper = box.querySelector('#Panai-Range-Wrapper'); e.target.checked ? rangeWrapper.style.display = 'flex' : rangeWrapper.style.display = 'none'; util.setValue('setting_timer_open', e.target.checked); }); box.querySelector('#S-Timer').addEventListener('input', (e) => { util.setValue('setting_timer', e.target.value); box.querySelector('#Timer-Value').innerText = `(${e.target.value / 1000}秒)`; }); box.querySelector('#S-hotkeys').addEventListener('change', (e) => { util.setValue('setting_hotkeys', e.target.value); }); // ===== 星号密码显示助手 事件绑定 ===== box.querySelector('#S-PwdReveal').addEventListener('change', (e) => { util.setValue('setting_pwd_reveal', e.target.checked); }); box.querySelectorAll('.panai-radio[data-pwdm]').forEach(r => { r.addEventListener('click', () => { box.querySelectorAll('.panai-radio[data-pwdm]').forEach(x => x.classList.remove('on')); r.classList.add('on'); const inp = r.querySelector('input'); inp.checked = true; util.setValue('setting_pwd_method', inp.value); }); }); box.querySelector('#S-PwdWait').addEventListener('input', (e) => { util.setValue('setting_pwd_wait', e.target.value); box.querySelector('#Pwd-Wait-Value').innerText = `(${e.target.value}毫秒)`; }); // 所有分区默认全部展开,无需折叠 }, // 根据 setting_position 映射到 SweetAlert 支持的 position getSwalPosition() { const map = { 'top': 'top', 'center': 'center', 'bottom': 'bottom' }; return map[util.getValue('setting_position')] || 'top'; }, // 应用主题:注入对应主题样式表 applyTheme() { const theme = util.getValue('setting_theme') || 'win11'; const appearance = util.getValue('setting_appearance') || 'system'; let dark = false; if (appearance === 'dark') dark = true; else if (appearance === 'system') { dark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; } // 给容器和抽屉添加/移除 dark-mode class const container = document.querySelector('.panai-container'); const overlay = document.getElementById('panai-overlay'); if (container) container.classList.toggle('dark-mode', dark); if (overlay) overlay.classList.toggle('dark-mode', dark); let style = document.getElementById('panai-theme-style'); if (!style) { style = document.createElement('style'); style.id = 'panai-theme-style'; document.head.appendChild(style); } style.innerHTML = this.buildThemeCSS(theme); }, // 构建指定主题的拟真 CSS(支持亮/暗两种模式 + 背景材质) buildThemeCSS(theme) { const appearance = util.getValue('setting_appearance') || 'system'; const effect = util.getValue('setting_effect') || 'none'; // 判定实际暗色状态 let dark = false; if (appearance === 'dark') dark = true; else if (appearance === 'system') { dark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; } const THEMES = { // ===== 明亮浅色 ===== light: ` .swal2-popup { border-radius: 10px !important; box-shadow: 0 12px 40px rgba(0,0,0,.12), 0 0 0 1px rgba(0,0,0,.05) !important; font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif !important; background: #ffffff !important; } .swal2-title { font-weight: 600 !important; color: #1a1a2e !important; } .swal2-html-container { color: #4a4a5a !important; } .swal2-confirm { background: #4f46e5 !important; border-radius: 8px !important; font-weight: 600 !important; padding: 9px 24px !important; } .swal2-confirm:hover { background: #4338ca !important; } .swal2-cancel { background: #f1f1f4 !important; color: #333 !important; border-radius: 8px !important; padding: 9px 24px !important; } .swal2-cancel:hover { background: #e4e4e9 !important; } `, // ===== 深夜暗黑 ===== dark: ` .swal2-popup { border-radius: 10px !important; box-shadow: 0 12px 48px rgba(0,0,0,.6), 0 0 0 1px rgba(255,255,255,.08) !important; font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif !important; background: #1e1e2e !important; } .swal2-title { font-weight: 600 !important; color: #cdd6f4 !important; } .swal2-html-container { color: #a6adc8 !important; } .swal2-confirm { background: #cba6f7 !important; color: #1e1e2e !important; border-radius: 8px !important; font-weight: 600 !important; padding: 9px 24px !important; } .swal2-confirm:hover { background: #b4befe !important; } .swal2-cancel { background: #313244 !important; color: #cdd6f4 !important; border-radius: 8px !important; padding: 9px 24px !important; } .swal2-cancel:hover { background: #45475a !important; } .swal2-icon { filter: invert(.9) hue-rotate(180deg); } `, // Windows 11:圆角、Mica 蓝 win11: ` .swal2-popup { border-radius: 12px !important; box-shadow: 0 24px 60px rgba(0,0,0,.28), 0 0 0 1px rgba(0,0,0,.06) !important; font-family: "Segoe UI Variable","Segoe UI",system-ui,sans-serif !important; } .swal2-title { font-weight: 600 !important; } .swal2-confirm { background: #0067c0 !important; border-radius: 6px !important; font-weight: 600 !important; padding: 9px 22px !important; } .swal2-confirm:hover { background: #1a78c8 !important; } .swal2-cancel { border-radius: 6px !important; padding: 9px 22px !important; } .swal2-cancel:hover { filter: brightness(.95); } .swal2-icon { border-radius: 50% !important; } `, // Windows 10:平面 + 强调蓝 win10: ` .swal2-popup { border-radius: 2px !important; box-shadow: 0 6px 24px rgba(0,0,0,.32) !important; font-family: "Segoe UI",system-ui,sans-serif !important; } .swal2-title { font-weight: 400 !important; } .swal2-confirm { background: #0078d4 !important; border-radius: 0 !important; font-weight: 600 !important; padding: 8px 24px !important; } .swal2-confirm:hover { background: #106ebe !important; } .swal2-cancel { border-radius: 0 !important; padding: 8px 24px !important; } .swal2-cancel:hover { filter: brightness(.95); } `, // Windows 8.1:全屏方块磁贴感 win81: ` .swal2-popup { border-radius: 0 !important; box-shadow: 0 0 0 2px #0067b8, 0 8px 30px rgba(0,0,0,.3) !important; font-family: "Segoe UI",system-ui,sans-serif !important; } .swal2-title { font-weight: 300 !important; letter-spacing: .5px; } .swal2-confirm { background: #0067b8 !important; border-radius: 0 !important; padding: 9px 28px !important; font-weight: 600 !important; } .swal2-confirm:hover { background: #005a9e !important; } .swal2-cancel { border-radius: 0 !important; padding: 9px 28px !important; } .swal2-cancel:hover { filter: brightness(.95); } `, // Windows 8:深蓝主题 win8: ` .swal2-popup { border-radius: 0 !important; box-shadow: 0 4px 20px rgba(0,0,0,.35) !important; font-family: "Segoe UI",system-ui,sans-serif !important; } .swal2-title { font-weight: 300 !important; } .swal2-confirm { background: #2d89ef !important; border-radius: 0 !important; padding: 9px 28px !important; } .swal2-confirm:hover { background: #2272c4 !important; } .swal2-cancel { border-radius: 0 !important; padding: 9px 28px !important; } .swal2-cancel:hover { filter: brightness(.95); } `, // Windows 7:玻璃蓝渐变 + 圆角 win7: ` .swal2-popup { border-radius: 6px !important; box-shadow: 0 0 0 1px #7ba2e0, 0 8px 28px rgba(0,0,0,.35) !important; font-family: "Segoe UI",system-ui,sans-serif !important; } .swal2-title { font-weight: 700 !important; text-shadow: 0 1px 0 rgba(255,255,255,.5); } .swal2-confirm { background: linear-gradient(#fdfefe,#c3daf7) !important; border: 1px solid #3c7fb1 !important; border-radius: 4px !important; padding: 6px 20px !important; text-shadow: 0 1px 0 #fff; } .swal2-confirm:hover { background: linear-gradient(#eaf4fd,#a8cef5) !important; } .swal2-cancel { background: linear-gradient(#fdfefe,#e7e7e7) !important; border: 1px solid #8a8a8a !important; border-radius: 4px !important; padding: 6px 20px !important; } .swal2-cancel:hover { background: linear-gradient(#f5f5f5,#d8d8d8) !important; } `, // Windows Vista:Aero 玻璃 + 发光 vista: ` .swal2-popup { border-radius: 10px !important; box-shadow: 0 0 0 1px #7ba2e0, 0 12px 40px rgba(0,60,130,.4) !important; font-family: "Segoe UI",system-ui,sans-serif !important; background: rgba(240,245,255,.97) !important; } .swal2-title { font-weight: 700 !important; } .swal2-confirm { background: linear-gradient(#5c9de6,#1b5fa8) !important; border: 1px solid #12406f !important; border-radius: 5px !important; color: #fff !important; padding: 7px 22px !important; } .swal2-confirm:hover { background: linear-gradient(#6ca7ea,#2268b2) !important; } .swal2-cancel { background: linear-gradient(#f0f5fb,#d3dde8) !important; border: 1px solid #8ba0b4 !important; border-radius: 5px !important; padding: 7px 22px !important; } .swal2-cancel:hover { background: linear-gradient(#e2ebf5,#c1cddb) !important; } `, // Windows XP:经典蓝绿「青柠」风 xp: ` .swal2-popup { border-radius: 8px !important; box-shadow: 0 0 0 1px #003c74, 0 8px 26px rgba(0,0,0,.35) !important; font-family: "Tahoma","Microsoft YaHei",sans-serif !important; } .swal2-title { font-weight: 700 !important; } .swal2-confirm { background: linear-gradient(#4b9cf5,#1c5aa6) !important; border: 1px solid #003c74 !important; border-radius: 5px !important; color: #fff !important; padding: 6px 20px !important; } .swal2-confirm:hover { background: linear-gradient(#5ca6f7,#2362b0) !important; } .swal2-cancel { background: linear-gradient(#f6f4ea,#cfcbb4) !important; border: 1px solid #7f7c64 !important; border-radius: 5px !important; padding: 6px 20px !important; } .swal2-cancel:hover { background: linear-gradient(#fffdf5,#ddd9c2) !important; } `, // Windows ME:纯平面,灰蓝经典 me: ` .swal2-popup { border-radius: 0 !important; box-shadow: 0 6px 20px rgba(0,0,0,.3) !important; font-family: "Tahoma","Microsoft Sans Serif",sans-serif !important; border: 1px solid #404040 !important; } .swal2-title { font-weight: 700 !important; } .swal2-confirm { border: 1px solid #404040 !important; border-radius: 0 !important; padding: 4px 18px !important; } .swal2-confirm:hover { filter: brightness(.95); } .swal2-cancel { border: 1px solid #404040 !important; border-radius: 0 !important; padding: 4px 18px !important; } .swal2-cancel:hover { filter: brightness(.95); } `, // Windows 98:经典立体浮雕边框 '98': ` .swal2-popup { border-radius: 0 !important; box-shadow: none !important; font-family: "Pixelated MS Sans Serif","MS Sans Serif","Tahoma",sans-serif !important; border-top: 2px solid #fff !important; border-left: 2px solid #fff !important; border-right: 2px solid #404040 !important; border-bottom: 2px solid #404040 !important; } .swal2-title { font-weight: 700 !important; } .swal2-confirm { border-top: 2px solid #fff !important; border-left: 2px solid #fff !important; border-right: 2px solid #404040 !important; border-bottom: 2px solid #404040 !important; border-radius: 0 !important; padding: 4px 18px !important; } .swal2-confirm:hover { filter: brightness(1.05); } .swal2-cancel { border-top: 2px solid #fff !important; border-left: 2px solid #fff !important; border-right: 2px solid #404040 !important; border-bottom: 2px solid #404040 !important; border-radius: 0 !important; padding: 4px 18px !important; } .swal2-cancel:hover { filter: brightness(1.05); } `, // Windows 3.x:程序管理器灰 + 标题栏渐变 '3x': ` .swal2-popup { border-radius: 0 !important; box-shadow: none !important; font-family: "MS Sans Serif","Tahoma",sans-serif !important; border-top: 2px solid #dfdfdf !important; border-left: 2px solid #dfdfdf !important; border-right: 2px solid #404040 !important; border-bottom: 2px solid #404040 !important; } .swal2-title { font-weight: 700 !important; background: linear-gradient(90deg,#000080,#1084d0) !important; padding: 3px 10px !important; margin: -1.25em -1.6em 1em !important; font-size: 13px !important; } .swal2-confirm { border-top: 2px solid #dfdfdf !important; border-left: 2px solid #dfdfdf !important; border-right: 2px solid #404040 !important; border-bottom: 2px solid #404040 !important; border-radius: 0 !important; padding: 3px 16px !important; font-size: 12px !important; } .swal2-cancel { border-top: 2px solid #dfdfdf !important; border-left: 2px solid #dfdfdf !important; border-right: 2px solid #404040 !important; border-bottom: 2px solid #404040 !important; border-radius: 0 !important; padding: 3px 16px !important; font-size: 12px !important; } `, // Windows 1.0 / 2.0:黑底白字单色复古终端风 '12': ` .swal2-popup { border-radius: 0 !important; box-shadow: 0 0 0 2px #fff, 0 0 0 4px #000 !important; font-family: "Perfect DOS VGA 437","Consolas","Courier New",monospace !important; background: #000 !important; } .swal2-title { font-weight: 700 !important; text-transform: uppercase; letter-spacing: 1px; } .swal2-html-container { color: #c0c0c0 !important; } .swal2-confirm { background: #000 !important; border: 2px solid #fff !important; border-radius: 0 !important; font-family: inherit !important; } .swal2-confirm:hover { background: #fff !important; } .swal2-cancel { background: #000 !important; border: 2px solid #fff !important; border-radius: 0 !important; font-family: inherit !important; } .swal2-cancel:hover { background: #fff !important; } .swal2-input, .swal2-textarea { background: #000 !important; border: 1px solid #fff !important; font-family: inherit !important; } ` }; // 获取基础主题 CSS let base = (THEMES[theme] || THEMES.win11); // 暗色模式覆盖层 const DARK_OVERRIDE = ` .swal2-popup { background: #1e1e2e !important; color: #cdd6f4 !important; box-shadow: 0 24px 60px rgba(0,0,0,.6), 0 0 0 1px rgba(255,255,255,.08) !important; } .swal2-title { color: #cdd6f4 !important; } .swal2-html-container { color: #a6adc8 !important; } .swal2-confirm { background: #cba6f7 !important; color: #1e1e2e !important; } .swal2-confirm:hover { background: #b4befe !important; } .swal2-cancel { background: #313244 !important; color: #cdd6f4 !important; } .swal2-cancel:hover { background: #45475a !important; } .swal2-input, .swal2-textarea { background: #313244 !important; color: #cdd6f4 !important; border-color: #585b70 !important; } .swal2-icon { filter: invert(.85) hue-rotate(180deg); } `; // Win7/XP 等带渐变按钮的主题,暗色下需额外处理按钮 const DARK_BTN_OVERRIDE = ` .swal2-confirm { background: #cba6f7 !important; color: #1e1e2e !important; border-color: #cba6f7 !important; text-shadow: none !important; } .swal2-cancel { background: #313244 !important; color: #cdd6f4 !important; border-color: #585b70 !important; text-shadow: none !important; } `; // ===== 背景材质效果 ===== const EFFECTS = { none: ``, transparent: ` .swal2-popup { background: transparent !important; backdrop-filter: none !important; -webkit-backdrop-filter: none !important; } .swal2-popup .swal2-title { text-shadow: 0 1px 3px rgba(0,0,0,.3); } `, blur: ` .swal2-popup { backdrop-filter: blur(12px) !important; -webkit-backdrop-filter: blur(12px) !important; } `, acrylic: ` .swal2-popup { backdrop-filter: blur(20px) saturate(180%) !important; -webkit-backdrop-filter: blur(20px) saturate(180%) !important; } .swal2-popup::before { content: ''; position: absolute; inset: 0; background: linear-gradient(135deg, rgba(255,255,255,.1), rgba(255,255,255,.03)); border-radius: inherit; pointer-events: none; z-index: -1; } `, mica: ` .swal2-popup { backdrop-filter: blur(30px) saturate(150%) brightness(1.05) !important; -webkit-backdrop-filter: blur(30px) saturate(150%) brightness(1.05) !important; } .swal2-popup::before { content: ''; position: absolute; inset: 0; background: linear-gradient(160deg, rgba(255,255,255,.08) 0%, rgba(255,255,255,.02) 50%, rgba(0,0,0,.02) 100%); border-radius: inherit; pointer-events: none; z-index: -1; } ` }; let result = base; // 应用背景材质(放在最外层,不随主题变) const effectCSS = EFFECTS[effect] || ''; // 暗色/亮色处理 if (dark) { result += DARK_OVERRIDE; if (['win7','vista','xp','me','98','3x'].includes(theme)) { result += DARK_BTN_OVERRIDE; } if (theme === 'win11' || theme === 'win10') { result += `.swal2-confirm { background: #4f46e5 !important; } .swal2-confirm:hover { background: #6366f1 !important; }`; } } else { result += this._getLightColors(theme); } // 透明效果在亮色下需要特殊处理背景色 let transparentOverride = ''; if (effect === 'transparent') { transparentOverride = dark ? `.swal2-popup { background: rgba(30,30,46,.85) !important; }` : `.swal2-popup { background: rgba(255,255,255,.85) !important; }`; } return result + effectCSS + transparentOverride + ` .swal2-popup { transition: width .2s, border-radius .2s !important; position: relative; } .swal2-actions { gap: 10px !important; } `; }, // 获取亮色模式的文字/背景色补充 _getLightColors(theme) { const LIGHT = { win11: `.swal2-title { color: #1b1b1b !important; } .swal2-html-container { color: #424242 !important; } .swal2-cancel { background: #e8e8e8 !important; color: #1b1b1b !important; } .swal2-cancel:hover { background: #dcdcdc !important; }`, win10: `.swal2-title { color: #323130 !important; } .swal2-cancel { background: #e6e6e6 !important; color: #323130 !important; } .swal2-cancel:hover { background: #d6d6d6 !important; }`, win81: `.swal2-title { color: #0067b8 !important; } .swal2-cancel { background: #f2f2f2 !important; color: #505050 !important; } .swal2-cancel:hover { background: #e0e0e0 !important; }`, win8: `.swal2-popup { background: #f0f0f0 !important; } .swal2-title { color: #2d89ef !important; } .swal2-cancel { background: #cccccc !important; color: #222 !important; } .swal2-cancel:hover { background: #bbbbbb !important; }`, win7: `.swal2-popup { background: #f5f6f7 !important; } .swal2-title { color: #104a8e !important; } .swal2-confirm { color: #0c3d6e !important; } .swal2-cancel { color: #333 !important; }`, vista: `.swal2-title { color: #12406f !important; } .swal2-confirm { color: #fff !important; } .swal2-cancel { color: #22405c !important; }`, xp: `.swal2-popup { background: #ece9d8 !important; } .swal2-title { color: #003c74 !important; } .swal2-confirm { color: #fff !important; } .swal2-cancel { color: #333 !important; }`, me: `.swal2-popup { background: #d4d0c8 !important; } .swal2-title { color: #000080 !important; } .swal2-confirm { background: #d4d0c8 !important; color: #000 !important; } .swal2-cancel { background: #d4d0c8 !important; color: #000 !important; } .swal2-html-container { color: #000 !important; }`, '98': `.swal2-popup { background: #c0c0c0 !important; } .swal2-title { color: #000 !important; } .swal2-html-container { color: #000 !important; } .swal2-confirm { background: #c0c0c0 !important; color: #000 !important; } .swal2-cancel { background: #c0c0c0 !important; color: #000 !important; }`, '3x': `.swal2-popup { background: #c0c0c0 !important; } .swal2-title { color: #fff !important; } .swal2-confirm { background: #c0c0c0 !important; color: #000 !important; } .swal2-cancel { background: #c0c0c0 !important; color: #000 !important; }`, '12': `.swal2-title { color: #fff !important; } .swal2-html-container { color: #c0c0c0 !important; } .swal2-confirm { color: #fff !important; } .swal2-cancel { color: #fff !important; } .swal2-input, .swal2-textarea { color: #fff !important; }` }; return LIGHT[theme] || LIGHT['win11']; }, // 应用自定义尺寸:动态写入尺寸变量 applySize(force) { const w = parseInt(util.getValue('setting_width')) || 500; const h = parseInt(util.getValue('setting_height')) || 280; let style = document.getElementById('panai-size-style'); if (!style) { style = document.createElement('style'); style.id = 'panai-size-style'; document.head.appendChild(style); } // 多链接弹窗尺寸由 didOpen 内联样式单独锁定(500×800), // 跳过写入全局 .swal2-popup 规则,避免覆盖其锁定尺寸导致上限失效 if (this._skipNextSize) { this._skipNextSize = false; return; } // 只限制内容区(html-container)高度,弹窗整体高度由 padding/内容自然撑开, // 避免整体 height 被锁死后截断底部按钮与确认栏。 style.innerHTML = ` .swal2-popup { width: ${w}px !important; max-width: 96vw !important; height: auto !important; } .swal2-popup .swal2-html-container { max-height: ${Math.max(h - 160, 60)}px; overflow-y: auto; } `; }, // 清除尺寸样式,避免影响后续弹窗 clearSize() { const style = document.getElementById('panai-size-style'); if (style) style.remove(); }, // 抽屉式:更新日志 + 支持网盘列表(最新版默认展开,历史版本折叠收纳,点击展开) showUpdateDrawer() { let overlay = document.getElementById('panai-overlay'); if (overlay) { overlay.style.display = 'flex'; return; } const logHtml = CHANGELOG.map((v, i) => { const open = i === 0; return ` v${v.version} ▶ ${v.items.map(it => `${it}`).join('')} `; }).join(''); const panHtml = PAN_GROUPS.map(([g, keys]) => { const chips = keys.map(k => { const o = opt[k]; if (!o) return ''; const pending = o._pending ? '(域名待补全)' : ''; return `${getPanIcon(o.icon, 18)}${o.name}${pending}`; }).join(''); return `${g}${chips}`; }).join(''); overlay = document.createElement('div'); overlay.id = 'panai-overlay'; overlay.className = 'panai-overlay'; overlay.innerHTML = ` 🔗 网盘智能识别助手 ✕ 📋 更新日志 🗂 支持网盘 ${logHtml} ${panHtml} 当前版本 v${VERSION} · 修改优化版 `; document.body.appendChild(overlay); const close = () => { overlay.style.display = 'none'; }; overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); }); overlay.querySelector('#panai-drawer-close').addEventListener('click', close); // 标签切换 overlay.querySelectorAll('.panai-tab').forEach(t => { t.addEventListener('click', () => { overlay.querySelectorAll('.panai-tab').forEach(x => x.classList.remove('active')); t.classList.add('active'); overlay.querySelectorAll('.panai-panel').forEach(p => p.style.display = 'none'); const panel = overlay.querySelector(`.panai-panel[data-panel="${t.dataset.tab}"]`); if (panel) panel.style.display = 'block'; }); }); // 版本日志折叠/展开 overlay.querySelectorAll('.panai-log-head').forEach(h => { h.addEventListener('click', () => { const item = h.parentElement; const body = overlay.querySelector(`.panai-log-body[data-log-body="${h.dataset.log}"]`); const open = item.classList.toggle('open'); if (body) body.style.display = open ? '' : 'none'; }); }); overlay.style.display = 'flex'; this.applyTheme(); }, registerMenuCommand() { GM_registerMenuCommand('👀 已识别:' + util.getValue('setting_success_times') + '次', () => { this.clearIdentifyTimes(); }); GM_registerMenuCommand(`📋️ 识别剪切板中文字(快捷键 ${util.getValue('setting_hotkeys')})`, () => { this.showIdentifyBox(); }); GM_registerMenuCommand('📋 更新日志', () => { this.showUpdateDrawer(); }); GM_registerMenuCommand('⚙️ 设置', () => { this.showSettingBox(); }); }, addPluginStyle() { let style = ` /* ===== 深浅模式 CSS 变量 ===== */ .panai-overlay, .panai-container { --panai-bg: #ffffff; --panai-bg2: #f7f8fa; --panai-bg3: #f0f2f5; --panai-border: #e2e2e8; --panai-border2: #eef0f4; --panai-text: #1a1b26; --panai-text2: #4a4b57; --panai-text3: #6b6c78; --panai-accent: #6366f1; --panai-accent-hover: #818cf8; --panai-accent-text: #ffffff; --panai-shadow: rgba(0,0,0,.08); --panai-shadow-lg: rgba(0,0,0,.14); --panai-overlay: rgba(0,0,0,.4); --panai-tag-bg: rgba(99,102,241,.1); --panai-tag-text: #6366f1; } .panai-container.dark-mode, .panai-overlay.dark-mode { --panai-bg: #1e1e2e; --panai-bg2: #242436; --panai-bg3: #2d2d44; --panai-border: #3a3a52; --panai-border2: #313144; --panai-text: #cdd6f4; --panai-text2: #a6adc8; --panai-text3: #7c819c; --panai-accent: #cba6f7; --panai-accent-hover: #d8b9ff; --panai-accent-text: #1e1e2e; --panai-shadow: rgba(0,0,0,.3); --panai-shadow-lg: rgba(0,0,0,.5); --panai-overlay: rgba(0,0,0,.6); --panai-tag-bg: rgba(203,166,247,.15); --panai-tag-text: #cba6f7; } .panai-container { z-index: 99999!important } .panai-popup { font-size: 14px !important } .panai-setting-label { display: flex;align-items: center;justify-content: space-between;padding-top: 14px; cursor: pointer; } .panai-setting-checkbox { width: 16px;height: 16px; accent-color: var(--panai-accent); } /* ===== 设置面板:左右两栏网格布局 ===== */ .panai-set-wrap { text-align: left; padding: 4px 2px; } /* 左右两栏 */ .panai-set-layout { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; } .panai-set-col { display: flex; flex-direction: column; gap: 16px; } /* 分区容器 */ .panai-set-section { border: 1px solid var(--panai-border); border-radius: 14px; overflow: hidden; background: var(--panai-bg); box-shadow: 0 1px 4px var(--panai-shadow); } .panai-set-section-bar { display: flex; align-items: center; gap: 8px; padding: 12px 18px; background: linear-gradient(135deg, var(--panai-bg2), var(--panai-bg3)); border-bottom: 1px solid var(--panai-border2); } .panai-set-bar-ic { font-size: 16px; line-height: 1; } .panai-set-bar-tx { font-size: 14px; font-weight: 700; color: var(--panai-text); letter-spacing: .3px; } .panai-set-section-body { padding: 16px 18px 14px; } /* 字段行 */ .panai-set-field { padding: 5px 0; } .panai-set-field-label { font-size: 13px; font-weight: 600; color: var(--panai-text2); margin-bottom: 8px; } /* 单选按钮 */ .panai-set-radios { display: flex; gap: 8px; flex-wrap: wrap; } .panai-radios-full { width: 100%; } .panai-radio { display: inline-flex; align-items: center; gap: 5px; padding: 8px 18px; border: 1.5px solid var(--panai-border); border-radius: 22px; font-size: 13px; cursor: pointer; background: var(--panai-bg2); color: var(--panai-text2); transition: all .18s ease; user-select: none; white-space: nowrap; } .panai-radio input { display: none; } .panai-radio:hover { border-color: var(--panai-accent-hover); background: var(--panai-bg3); color: var(--panai-text); transform: translateY(-1px); } .panai-radio.on { background: var(--panai-accent); border-color: var(--panai-accent); color: var(--panai-accent-text); font-weight: 600; box-shadow: 0 3px 10px var(--panai-shadow); } /* 尺寸输入 */ .panai-set-size-vals { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--panai-text3); } .panai-size-box { position: relative; display: inline-flex; align-items: center; } .panai-size-box input { width: 78px; padding: 7px 26px 7px 10px; border: 1.5px solid var(--panai-border); border-radius: 8px; font-size: 13px; text-align: center; background: var(--panai-bg2); color: var(--panai-text); transition: border-color .15s; } .panai-size-box input:focus { outline: none; border-color: var(--panai-accent); box-shadow: 0 0 0 3px var(--panai-tag-bg); } .panai-size-box i { position: absolute; right: 8px; font-style: normal; font-size: 11px; color: var(--panai-text3); pointer-events: none; } .panai-size-unit { font-size: 12px; color: var(--panai-text3); margin-left: 2px; } /* 复选框行 */ .panai-setting-label { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; margin-bottom: 4px; cursor: pointer; border-radius: 10px; transition: background .15s; } .panai-setting-label:hover { background: var(--panai-bg2); } .panai-set-cb-label { font-size: 13px; color: var(--panai-text2); } .panai-setting-checkbox { width: 18px; height: 18px; accent-color: var(--panai-accent); cursor: pointer; } .panai-setting-range { gap: 12px; } .panai-setting-range input[type="range"] { flex: 1; max-width: 240px; accent-color: var(--panai-accent); height: 4px; } .panai-set-hotkey-input { width: 110px; padding: 7px 12px; border: 1.5px solid var(--panai-border); border-radius: 8px; font-size: 13px; text-align: center; background: var(--panai-bg2); color: var(--panai-text); font-family: 'SF Mono', 'Cascadia Code', monospace; } .panai-set-hotkey-input:focus { outline: none; border-color: var(--panai-accent); box-shadow: 0 0 0 3px var(--panai-tag-bg); } /* 主题选择卡片 - 网格 */ .panai-theme-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 10px; max-height: 520px; overflow-y: auto; padding-right: 4px; } .panai-theme-grid::-webkit-scrollbar { width: 6px; } .panai-theme-grid::-webkit-scrollbar-track { background: var(--panai-bg3); border-radius: 3px; } .panai-theme-grid::-webkit-scrollbar-thumb { background: var(--panai-border); border-radius: 3px; } .panai-theme-grid::-webkit-scrollbar-thumb:hover { background: var(--panai-text3); } .panai-theme-card { border: 2px solid var(--panai-border); border-radius: 12px; overflow: hidden; cursor: pointer; background: var(--panai-bg2); transition: all .18s ease; } .panai-theme-card:hover { border-color: var(--panai-accent-hover); transform: translateY(-2px); box-shadow: 0 6px 16px var(--panai-shadow); } .panai-theme-card.active { border-color: var(--panai-accent); box-shadow: 0 3px 14px var(--panai-shadow); } .panai-theme-thumb { height: 36px; } .panai-theme-meta { display: flex; align-items: center; gap: 5px; padding: 6px 8px; font-size: 11px; color: var(--panai-text2); white-space: nowrap; overflow: hidden; } .panai-theme-ic { font-size: 12px; } .panai-theme-name { overflow: hidden; text-overflow: ellipsis; } /* 各主题缩略图 */ .panai-theme-thumb.t-light { background: linear-gradient(90deg,#ffffff 0%,#ffffff 50%,#e8e8f0 50%,#e8e8f0 100%); } .panai-theme-thumb.t-dark { background: linear-gradient(90deg,#1e1e2e 0%,#1e1e2e 50%,#313244 50%,#313244 100%); } .panai-theme-thumb.t-win11 { background: linear-gradient(135deg,#eaf3fc,#f7fbff); } .panai-theme-thumb.t-win10 { background: #0078d4; } .panai-theme-thumb.t-win81 { background: #0067b8; } .panai-theme-thumb.t-win8 { background: #2d89ef; } .panai-theme-thumb.t-win7 { background: linear-gradient(180deg,#5b9de0,#2b6fb0); } .panai-theme-thumb.t-vista { background: linear-gradient(135deg,#7ec0ff,#2a6fc4); } .panai-theme-thumb.t-xp { background: linear-gradient(180deg,#3b8ef5,#1c5aa6); } .panai-theme-thumb.t-me { background: #d4d0c8; } .panai-theme-thumb.t-98 { background: #c0c0c0; } .panai-theme-thumb.t-3x { background: linear-gradient(90deg,#000080,#1084d0); } .panai-theme-thumb.t-12 { background: #000; } /* 更新日志抽屉 */ .panai-overlay { position: fixed; inset: 0; z-index: 999999; background: var(--panai-overlay); display: flex; justify-content: flex-end; backdrop-filter: blur(2px); } .panai-drawer { width: 380px; max-width: 92vw; height: 100%; background: var(--panai-bg); color: var(--panai-text); box-shadow: -4px 0 24px var(--panai-shadow-lg); display: flex; flex-direction: column; animation: panai-slide .25s ease; } @keyframes panai-slide { from { transform: translateX(100%); } to { transform: translateX(0); } } .panai-drawer-head { display: flex; align-items: center; justify-content: space-between; padding: 16px 18px; border-bottom: 1px solid var(--panai-border2); } .panai-drawer-title { font-size: 16px; font-weight: 700; } .panai-drawer-close { border: none; background: var(--panai-bg3); width: 30px; height: 30px; border-radius: 50%; font-size: 14px; line-height: 1; cursor: pointer; color: var(--panai-text3); transition: all .15s; } .panai-drawer-close:hover { background: var(--panai-accent); color: var(--panai-accent-text); } .panai-drawer-tabs { display: flex; border-bottom: 1px solid var(--panai-border2); } .panai-tab { flex: 1; padding: 11px; border: none; background: var(--panai-bg2); cursor: pointer; font-size: 13px; color: var(--panai-text3); transition: all .15s; } .panai-tab:hover { background: var(--panai-bg3); } .panai-tab.active { background: var(--panai-bg); color: var(--panai-accent); font-weight: 600; border-bottom: 2px solid var(--panai-accent); } .panai-drawer-body { flex: 1; overflow-y: auto; padding: 16px 18px; } .panai-drawer-foot { padding: 12px 16px; border-top: 1px solid var(--panai-border2); font-size: 12px; color: var(--panai-text3); text-align: center; } .panai-log-item { border: 1px solid var(--panai-border2); border-radius: 10px; margin-bottom: 10px; overflow: hidden; } .panai-log-head { padding: 11px 13px; cursor: pointer; display: flex; align-items: center; justify-content: space-between; background: var(--panai-bg2); font-size: 13px; font-weight: 600; color: var(--panai-text); transition: background .15s; } .panai-log-head:hover { background: var(--panai-bg3); } .panai-log-ver { color: var(--panai-accent); } .panai-log-tag { background: var(--panai-tag-bg); color: var(--panai-tag-text); font-size: 11px; font-weight: 400; padding: 2px 8px; border-radius: 10px; } .panai-log-arrow { font-size: 10px; color: var(--panai-text3); transition: transform .2s; } .panai-log-item.open .panai-log-arrow { transform: rotate(90deg); } .panai-log-body { padding: 10px 13px; font-size: 12px; color: var(--panai-text2); line-height: 1.9; } .panai-log-line { padding-left: 14px; position: relative; } .panai-log-line::before { content: '•'; position: absolute; left: 0; color: var(--panai-accent); } .panai-group-title { font-size: 13px; font-weight: 700; color: var(--panai-text); margin: 14px 0 8px; padding-bottom: 4px; border-bottom: 1px solid var(--panai-border2); } .panai-group-title:first-child { margin-top: 0; } .panai-pan-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; } .panai-pan-chip { display: flex; align-items: center; gap: 6px; padding: 6px 9px; border: 1px solid var(--panai-border2); border-radius: 8px; font-size: 12px; color: var(--panai-text2); background: var(--panai-bg2); overflow: hidden; white-space: nowrap; } .panai-pan-chip > span.panai-pan-icon { width: 18px; height: 18px; flex-shrink: 0; } .panai-pan-icon { flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; vertical-align: middle; } .panai-pan-icon svg { width: 100%; height: 100%; display: block; } .panai-pan-icon--circle { border-radius: 50%; overflow: hidden; background: var(--panai-bg3); padding: 2px; box-sizing: border-box; } .panai-result-title { display: flex; align-items: center; gap: 8px; } .panai-result-icon { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 24px; height: 24px; } .panai-result-icon svg { width: 100%; height: 100%; display: block; } .panai-result-name { color: var(--panai-accent); margin: 0 4px; } .panai-pan-name { overflow: hidden; text-overflow: ellipsis; } .panai-pan-pending { color: #d14529; font-size: 11px; margin-left: 2px; } /* ===== 星号密码显示助手 设置样式 ===== */ .panai-radio[data-pwdm] { flex: 1; justify-content: center; min-width: 0; font-size: 12px; padding: 7px 10px; } /* 星号密码分区标题栏微妙强调 */ .panai-set-section .panai-set-section-bar .panai-set-bar-ic { filter: drop-shadow(0 0 2px var(--panai-tag-bg)); } /* ===== 多链接列表弹窗样式 ===== */ /* 尺寸硬约束:最多完整显示 12 个链接,长宽上限锁定 500×800; 链接数少于 12 时按实际内容高度,绝不撑高到上限。 注意:弹窗容器真实类为 .swal2-popup,.panai-multi-popup 是其附加类, 因此两条都要命中,避免被 applySize() 注入的全局 .swal2-popup 规则覆盖 */ .swal2-popup.panai-multi-popup { max-width: 500px !important; width: 500px !important; max-height: 800px !important; height: auto !important; } .panai-multi-popup { max-width: 500px !important; width: 500px !important; max-height: 800px !important; height: auto !important; } .panai-multi-list { display: flex; flex-direction: column; gap: 6px; overflow-y: auto; padding: 4px 2px; margin: 0 -2px; scrollbar-width: thin; } /* 超过 12 个链接时锁定列表高度并启用内部下拉滚动条,弹窗整体保持 500×800 不撑高 */ .panai-multi-list.is-scrollable { overflow-y: scroll; } /* 点击列表项时禁止选中文字,避免出现系统底部滑动选择栏 */ .panai-multi-list, .panai-multi-item { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; } .panai-multi-item { touch-action: manipulation; } .panai-multi-list::-webkit-scrollbar { width: 6px; } .panai-multi-list::-webkit-scrollbar-thumb { background: var(--panai-border); border-radius: 3px; } .panai-multi-list::-webkit-scrollbar-track { background: transparent; } .panai-multi-item { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border: 1px solid var(--panai-border2); border-radius: 10px; background: var(--panai-bg2); transition: background .15s ease, border-color .15s ease, box-shadow .15s ease; cursor: pointer; user-select: none; } .panai-multi-item:hover { background: var(--panai-tag-bg); border-color: var(--panai-accent); } .panai-multi-item.panai-multi-active { background: var(--panai-tag-bg); border-color: var(--panai-accent); box-shadow: 0 0 0 2px var(--panai-accent); } .panai-multi-icon { width: 24px; height: 24px; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; } .panai-multi-icon svg { width: 100%; height: 100%; display: block; } .panai-multi-name { font-size: 13px; font-weight: 600; color: var(--panai-text); flex-shrink: 0; white-space: nowrap; } .panai-multi-link { font-size: 11px; color: var(--panai-text3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; text-align: right; font-family: 'SF Mono', 'Cascadia Code', monospace; } .panai-multi-tip { margin-top: 10px; font-size: 12px; color: var(--panai-text3); text-align: center; padding: 6px; background: var(--panai-bg2); border-radius: 8px; } .panai-result-count { color: var(--panai-accent); font-weight: 700; font-size: 1.2em; } `; if (document.head) { util.addStyle('swal-pub-style', 'style', GM_getResourceText('swalStyle')); util.addStyle('panai-style', 'style', style); } const headObserver = new MutationObserver(() => { util.addStyle('swal-pub-style', 'style', GM_getResourceText('swalStyle')); util.addStyle('panai-style', 'style', style); }); headObserver.observe(document.head, {childList: true, subtree: true}); }, isTopWindow() { return window.self === window.top; }, init() { this.initValue(); this.addPluginStyle(); this.addHotKey(); this.autoFillPassword(); this.addPageListener(); this.initPwdReveal(); this.isTopWindow() && this.registerMenuCommand(); this.checkVersionAndShowChangelog(); }, // 检测首次使用或版本更新,自动弹出更新日志 checkVersionAndShowChangelog() { const lastVersion = util.getValue('setting_last_version'); if (lastVersion !== VERSION) { util.setValue('setting_last_version', VERSION); // 延迟弹出,确保样式已加载 setTimeout(() => { this.showUpdateDrawer(); }, 800); } }, }; main.init(); })();