// ==UserScript== // @name 菠萝猫阅读助手(主版本) // @namespace local.boluomao.nativepager // @version 1.9.19 // @downloadURL https://paste.brianrianrehan.com/raw/VxSzy6nf3 // @updateURL https://paste.brianrianrehan.com/raw/VxSzy6nf3 // @description 增加正文净化:选中文字、段落或正则规则,预览并可撤销。 // @match https://www.boluomao1.com/* // @match https://www.boluomao1.com/book/* // @match https://www.boluomao1.com/read/* // @match https://www.rrssk.com/* // @match https://rrssk.com/* // @match https://www.boluomao1.com/WAF/VERIFY/CAPTCHA* // @match https://boluomao1.com/WAF/VERIFY/CAPTCHA* // @run-at document-start // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @grant GM.getValue // @grant GM.setValue // @grant GM.xmlHttpRequest // @grant GM_info // @grant unsafeWindow // @connect aip.baidubce.com // @noframes // ==/UserScript== /* 版本更新日志(新版本在最上方;保留历史,不覆盖旧记录) * 1.9.19 · 2026-10-31 * - 增加正文净化:长按选中文字或整段,支持文本和正则规则,预览后保存。 * - 规则默认仅本书生效,可选择本站所有书;支持停用、删除和恢复,保留原始正文与阅读进度。 * - 支持规则文本备份和导入部分阅读(Legado)规则;导入规则默认停用,需测试后启用。 * - 正则在独立线程执行并限制运行时间;不支持时不处理正文,防止复杂规则卡住手机。 * 1.9.18 · 2026-10-31 * - 验证码有效六位结果立即填入并提交原表单,保留当前图片及手动输入检查,不增加模型分析或确认等待。 * - 取消重试及刷新后的固定3秒等待;可重试失败后刷新换图,新图片就绪即识别,不反复识别旧失败图片。 * - 保留三轮共六次上限、跨页计数、手动停止、后台暂停、完整日志及所有既有阅读与账号功能。 * 1.9.17 · 2026-10-09 * - 验证码保留三轮、每轮两次识别的跨页恢复记录;错误验证码原生提交返回后继续本轮,轮间才刷新。 * - 进入普通本站页面尽早安装已核实广告CSS,首页和个人中心同样覆盖;验证码和登录页隔离。 * - 配合X浏览器数字广告入口正则与人人联盟原生请求规则,不屏蔽整个public目录。 * - 登录后按网站当天状态自动签到,同步网站书架与最近阅读;页面关闭后不承诺后台执行。 * - 收藏变更在本地保存成功后同步;最近阅读仅上传真实已保存的可见章节,保留本机段落精确续读。 * - 登录、验证、限流和异常响应暂停账号自动操作,可从网站账号入口重新连接;不自动评分或评论。 * - 原主名称、namespace、GM权限、安装地址、阅读键、书架、TXT、备份、外观和历史日志全部保留。 * 1.9.16 · 2026-10-09 * - 主脚本更名“菠萝猫阅读助手(主版本)”,为Via生成独立标识,避免同名旧条目先匹配并静默拒绝GM权限。 * - 版本号不参与Via脚本标识;更新后仅启用本主版本,关闭旧网页并重新打开,旧版可保留以便回退。 * - 同一页面重复注入复用在线验证助手;验证控件按实例归属处理,避免重复请求和旧监听截获新控件。 * - GM错误回调保留HTTP状态;服务HTTP错误不再混报网络断开,仍不记录服务原文、密钥或图片。 * - 实际经过总期限的回调与正文不再使用;后台计时器暂停后也不会接受过期识别结果。 * - 原名称及新名称的GM权限和请求格式经精确Via SDK对照;不改document-start,不削弱已有阅读和广告防护。 * - 全部阅读本地存储键保持不变,保留书架、进度、目录、重复日志、TXT、备份、四种背景及在线验证日志。 * 1.9.15 · 2026-10-09 * - 补记GM开始与状态1回调,区分请求桥未回调、原生已启动但无HTTP、已收到HTTP但完整回应超时。 * - 兼容JSON字符串状态回调;重复状态不反复写日志,迟到回调不污染当前流程。 * - 复制日志附受控脚本接口与请求阶段诊断;接口版本不冒充浏览器版本,不复制密钥或回应原文。 * - 百度鉴权POST明确发送JSON空对象,兼容旧原生桥对缺省正文的处理差异;不改变识别请求或增加重试。 * - 保留总期限、取消隔离、预算、手动验证、日志归档及全部阅读功能。 * 1.9.14 · 2026-10-09 * - 按用户明确授权,将预置百度双凭据的个人版发布到原在线安装地址;安装后无需另填或保存。 * - 在线更新与下载元数据指向原地址,保留已验证的1.9.13识别与阅读逻辑及历史回退。 * 1.9.13 · 2026-10-09 * - 在线识别迁移百度智能云,默认数字识别,可主动选择高精度接口;个人版直接预置双凭据,无需填写或保存。 * - 百度配置独立于旧硅基配置,保留关闭/清除选择;鉴权令牌仅按凭据在本页内存缓存。 * - 增加鉴权、识别、HTTP和业务错误日志;测试连接只鉴权,不发送图片或扣识别次数。 * - GM跨域优先,明确早期失败才顺序兼容一次;鉴权与识别共用30/45秒总期限,停止丢弃迟到结果。 * - 令牌失效最多刷新并识别一次,实际识别请求才扣预算;额度、权限及非完整六位结果不自动重复请求。 * - 保留日志七天归档、阅读设置、目录、书架、进度、重复检测、TXT和备份恢复;保留1.9.12回退。 * 1.9.12 · 2026-10-07 * - 修复日志只存标签页、15分钟后或关闭标签后消失:同站点本地持久归档保留最近7天、最多300条。 * - 实时窗口仍最多60条,历史按需查看;设置加入全部历史复制、导出和清空,不生成大量隐藏节点。 * - 搜索站正常页增加验证日志入口,验证页跳走后仍可看;历史显示日期并续接流程编号,坏缓存可主动清空修复。 * - 本地归档写后回读验证;持久存储失败明确提示并退回标签页临时记录,不显示假保存成功。 * - 归档按事件标识去重并合并同域标签记录,清空标记阻止另一标签把旧记录带回;各域名日志独立。 * - 保留安全阶段、HTTP状态和耗时,日志不保存密钥、图片、验证码、Cookie或服务原文。 * - 保留1.9.11完整阶段日志、网页兼容/油猴通道、原模型和超时、停止与重试,以及全部阅读界面和数据。 * 1.9.11 · 2026-10-07 * - 最近60条安全日志保留15分钟,可跨刷新查看;提交接口、导航等待与过期结果丢弃均有记录。 * - 每次在线验证有流程编号和明确失败原因;存储桥与请求接口分别显示,网页等待改为“尚未收到响应头”。 * - 沿原计时器在5/15/30秒记录等待阶段,收到HTTP后单独报告正文读取,修复正文失败被误报成跨域失败。 * - 手动重试可在早期明确接口失败时顺序兼容一次;只允许一次提交,失败后暂停后续导航尝试。 * - 增加用户主动“刷新验证码页”,成功写入下一页一次识别额度才刷新,不自动连续换图或请求。 * - API设置加入“测试连接(不识别)”:只GET模型列表、显示HTTP状态与实际通道,不发送图片或站点Cookie。 * - 保留原OCR模型、45/30秒时限、停止、迟到结果隔离,以及1.9.10阅读界面和全部阅读数据。 * 1.9.10 · 2026-10-07 * - 贴边入口单击后同时展示整本百分比、背景和字号设置,修复展开菜单后进度一起消失的问题。 * - 顶部左“设置”、右“书架”等宽、同字号、同字重,×独立44px触摸区,两侧对称。 * - 进度摘要行直接打开阅读记录,原重复进度按钮合并;搜索、目录、工具、退出仍保留。 * - 百分比沿用真实目录和可见正文位置,一位小数;固定更新悬浮、菜单与进度页三个节点。 * - 展开后在恢复正文位置后刷新进度,不新增定时器、目录查询或历史卡片。 * - 保留重复检测、书架、TXT、验证设置、备份恢复和1.9.9回退版本。 * 1.9.9 · 2026-10-07 * - 贴边入口点击一次就展开完整阅读设置,保留触摸/拖动与兼容click防重复执行。 * - 菜单右上角改为书架;×直接收起并贴边;原单独贴边按钮删除。 * - 主菜单:搜索、目录、进度、工具、退出;字号与四种背景仍在阅读设置内。 * - 进度仅展示整本百分比和阅读记录;阅读工具展示收藏、重复日志、TXT、在线验证与备份恢复。 * - 工具页不生成隐藏的历史卡片;分组复用原面板与原事件,保留已有进度、书架和备份数据。 * - 保留1.9.8透明箭头、全屏删除和全脚本性能优化;不增加网络请求。 * 1.9.8 · 2026-10-07 * - 完全删除网页全屏按钮、自动请求、方向锁和事件监听,避免浏览器标识漏判后继续触发横屏。 * - 贴边入口改为14×24淡色箭头(22%不透明度),背景、边框大底和阴影透明;保留48×48触摸区。 * - 已贴边时跳过滑动的样式查询;已收起时不重复扫描正文和写收起状态;相同位置不重复存储。 * - 缓存按钮外观,相同外观不重复设置样式;连续视口变化按动画帧合并绘制。 * - 全脚本审查:进度历史与目录/字号/主题相同数据不重复写入;背景触摸后的兼容click不重复换肤。 * - 全书进度计算按同页地址复用解析,百分比相同不反复改DOM;正文内部变动不再触发全页重绑扫描。 * - 下载保留串行、断点和容量限额,目录保留有界查询,验证码保留单请求/时限与最新配置;未改请求频率。 * - 保留两步展开、拖动取消保护、正文滑动贴边及所有阅读进度、目录、书架、下载和日志。 * 1.9.7 · 2026-10-07 * - 修复贴边按钮只依赖click和PointerEvent能力判断导致的触摸漏触发:按实际触摸/指针松手响应。 * - 点击贴边入口先拉出,再点进入设置;合并触摸、指针和后续兼容click,一次手势只执行一次。 * - 触摸目标扩大到48×48,距离屏幕边缘8px;12px内轻微抖动保留点击,真实拖动才屏蔽尾随click。 * - 拖动后新的触摸立即可用;取消、捕获丢失、失焦和后台切换清理拖动状态,避免卡住。 * - 设置展开引起的程序滚动不会触发自动收起;回到正文滑动才贴边;拉出后留4.5秒操作窗口。 * - 新增触摸、指针、混合事件、拖动取消和20轮连续操作回归;保留1.9.6其他功能与回退。 * 1.9.6 · 2026-10-07 * - OCR配置并行读取与回读核对,合并并发读取,避免逐套等待油猴存储桥;保留网页兼容通道。 * - 网页兼容通道等待上限45秒,避免已在服务处理中但第31秒返回的有效结果被旧30秒超时丢弃。 * - 整页重复重取仅等待剩余请求间隔,最多两次,不再额外叠加2秒和4秒等待。 * - 实时日志分别显示配置、图片准备、网络与服务等待、回应读取耗时;不以缩短超时冒充加速。 * - Via Android改用竖屏沉浸阅读,停止触发可能强制横屏的原生网页全屏;浏览器底栏仍由Via控制。 * - 其他浏览器全屏以实际视口检测横屏,避免方向接口滞后漏判;保持所有权与退出保护。 * - 滑动正文自动收起菜单并贴边,普通闲置2.5秒贴边;面板操作和拖动期间保持可用。 * - 重复段落按内容索引匹配,减少全表扫描;校正下一章优先利用已确认目录,避免重复取目录。 * - 保留两秒请求间隔、完整重复比较规则、进度、书架、下载、备份和历史回退。 * 1.9.5 · 2026-10-06 * - 修复大量进度记录一次生成和平方级去重导致的长时间阻塞:线性去重,首次20条,按需追加20条。 * - 重复日志、书架、TXT下载、在线验证、备份和恢复入口直接显示;保留旧记录和重复检测逻辑。 * - 阅读菜单也可拖动,闲置六秒贴边收起,可手动贴边;点击边缘按钮直接展开,拖动后防误触。 * - 全屏尝试锁定竖屏;浏览器强制横屏且无法保持竖屏时退回原阅读,退出释放脚本自己的方向锁。 * - 阅读菜单/进度等控制不触发自动全屏,避免界面操作同时发生全屏切换。 * - 保留1.9.4网页兼容识别通道、实时日志和历史版本回退。 * 1.9.4 · 2026-10-06 * - 进入正文默认收起工具栏,菜单与整本百分比合为小按钮;展开后统一排布阅读设置。 * - 悬浮阅读入口提高对比度,支持拖动、保存位置及防误触;原生退出隐藏也能及时恢复入口。 * - 支持网页全屏;有权限时自动请求,否则等待真实触摸,退出只恢复脚本开启的全屏。 * - 目录缓存立即定位当前章,冷加载优先当前组,最多查三组;完整已有目录元数据可跨刷新复用。 * - 重排阅读进度界面:整本百分比、最远已读、当前在读、最近记录;其他功能合并到更多工具。 * - 在线识别显示时间日志、请求通道、回调、HTTP 与填入提交阶段;加入 Via 请求兼容路径。 * - 保留个人 API 预置和历史设置,公版不含密钥;保留旧版本回退文件。 * 1.9.3 · 2026-10-06 * - 顶部统一加入退出阅读;原生 Via 和兼容阅读界面均可退出。 * - 退出后保留淡色悬浮“阅”入口,重新进入恢复可见段落、字号和背景;退出取消未完成接续。 * - 整本书进度显示一位小数,按真实目录章序与章内分页估算,分卷重置不误算。 * - 总章数由最后目录组实际链接确认,目录暂不可用显示待确认,不编造百分比。 * - 目录元数据独立限量请求、节流与取消,不缓存正文;百分比随当前实际阅读位置更新。 * - 保留 1.9.2 私用 API 预置、保存回读诊断及历史功能。 * 1.9.2 · 2026-10-06 * - 修复配置接口静默失败时仍显示“已保存”的缺口:写入后回读核对,失败显示具体存储状态。 * - 动态发现传统与 Promise GM 接口,兼容旧对象和 JSON 字符串;支持本站恢复镜像及本页缓存。 * - 专用构建可在本地预置个人 API,无需手动粘贴;公用源码不包含个人密钥。 * - 验证页和设置中显示版本、域名、是否有密钥、读写核验、请求阶段及错误码,避免无反馈。 * - 关闭与删除保留明确状态;有界超时及重试继续生效,原站表单自动带本站会话 Cookie。 * - 保留 1.9.1 的搜索、阅读布局和验证码识别逻辑;实际 Via 7.2.1 需以手机诊断信息确认接口。 * 1.9.1 · 2026-10-06 * - 阅读工具栏增加搜索,离开前保存当前阅读位置;进度面板统一放置在线验证设置。 * - 只在菠萝猫和 rrssk 的 GoEdge 验证页识别图片,使用硅基流动免费 PaddleOCR-VL-1.5。 * - 识别结果严格检查 6 位数字、图片与表单状态,再通过原站原表单提交,保留会话与跳转。 * - 验证图先合成白底、按两倍平滑放大,避免透明底降低可读性;仅容忍数字末尾单个句号。 * - API key 存在脚本管理器私有设置中,不写入更新源码、阅读备份或日志;设置随同名脚本更新保留。 * - 单请求、超时与跨导航尝试上限避免循环;错误时保留原手动输入和停止、重试按钮。 * - 阅读接续遇到验证跳转时,可在已启用在线验证后自动打开原站目标页;登录仍由用户完成。 * - 使用 GM 跨域请求和跨域设置;缺少 GM 的浏览器提示兼容性问题,不改用付费模型。 * 1.9.0 · 2026-10-04 * - 进度面板和书籍页加入 TXT 下载;无需独立 APK,普通同源网页串行请求。 * - 下载正文与断点在 IndexedDB 中逐页原子提交;重新打开后手动继续,不影响原阅读进度。 * - 默认每次请求间隔 10 秒、最多 10 章;可调整、暂停、续传并导出已完成内容。 * - 跨标签租约与提交校验避免重复任务;手动暂停取消请求,丢弃过期响应。 * - 登录、验证码、限流和异常分页保留断点;尊重 Retry-After,不自动处理验证或受限章节。 * - 后台网页仍运行时继续;系统挂起或离开脚本页面后需重新打开继续,不能保证常驻后台。 * - 下载单独保存正文,阅读 JSON 备份不包含下载文件;准备 TXT 后由用户点击保存。 * 1.8.4 · 2026-10-04 * - 阅读工具栏增加一键收藏星星;进度面板和书籍页增加“我的书架”。 * - 收藏和最近阅读显示真实书名;续读读取每本书最后阅读位置,回看旧章后仍回到该位置。 * - 本书在当前阅读层恢复;跨书保留目标段落进度,进入正文后自动恢复。 * - 取消收藏保留阅读记录;书架加入原 JSON 备份,兼容无书架字段的旧版备份。 * - 书架只保存书名、收藏状态和阅读位置,不保存正文;按需显示列表,避免一次生成过多节点。 * 1.8.3 · 2026-10-04 * - 打开目录时跟随实际可见的章节,自动加载对应目录组、滚到当前章并高亮。 * - 以真实章节路径确认位置;章节顺序或章名数字仅作查找提示,兼容分卷章号重置。 * - 目录定位只滚动目录面板,保留正文位置;关闭、手动切组、选章取消旧定位请求。 * - 目录组条固定在顶部并定位当前组;保留此前版本的重复日志、备份和自动接续。 * 1.8.2 · 2026-10-04 * - 修复真实章节中“新正文混着大段旧正文”使整页摘要检测失效的问题。 * - 同章连续至少 5 段、160 字的完全重复长块才清理;新文字、改动段落和短对话保留。 * - 有长块重复证据且整页全部在本章出现过时,沿原站已验证的下一页自动接续。 * - 保留原始段号及重复位置映射,兼容旧进度;已释放正文只保留有限的段落摘要。 * - 章节标题、浏览器标题与网址跟随可见正文;更换标题、释放前文时保持阅读位置。 * - 进度面板新增“重复日志”:记录章名、页码、清理数量和处理方式,当前标签最多 80 条。 * - 修复用例:2266178 章首页 84 段、第 2 页 92→11 段,第 3~5 页重复后接入 2266179 章。 * - 这证明原站响应含重复文字;公开前端代码不足以证明服务器按阅读速度触发防采集。 * 1.8.1 · 2026-10-04 * - 整页正文摘要防重复;自动重取、清理本站出错页缓存,再按真实目录校正下一章。 * - 保留阅读位置;修复取消自动恢复任务后无法继续接续。 * 1.8.0 · 2026-10-04 * - 连读超过 12 页时逐步释放视野外正文;加入全部书进度和外观的 JSON 文件/文本备份恢复。 * - 过滤独立 chapter_(); 标记。 * 1.7.0 * - 搜索结果直达当前菠萝猫域名;正文页自动阅读,原生未响应时使用兼容阅读层。 * - 旧进度补齐章名,单列页码、段落。 * 1.6.0 * - 历史进度面板:最远已读置顶,保留旧章位置;跨章原位恢复。 * 1.5.0 * - 目录在当前阅读层换章;隔离旧请求,切章同步标题、地址和进度。 * 1.4.0 * - 荣耀 V30 Pro 手机工具栏;字号按 1 px 调整并保留可见段落;固定在线更新地址。 * 1.3.0 * - 自动接续、四款阅读背景、目录、续读及可收起工具栏。 */ /* 在线验证使用脚本管理器 GM_*;原生阅读未响应时显示兼容阅读层。 * 与 Via 广告规则配合使用;请停用旧版“菠萝猫纯净连读”脚本。 */ (function(root,factory){if(typeof module==="object"&&module.exports)module.exports=factory;else root.createBoluomaoSiteAccount=factory})(typeof globalThis!=="undefined"?globalThis:this,function createBoluomaoSiteAccount(options){"use strict";options=options||{};var win=options.window||window;var doc=options.document||win.document;var store=options.storage||win.localStorage;var api=options.adapter||{};var hooks=options.hooks||{};var now=options.now||Date.now;var origin=options.origin||win.location.origin;var storageKey=options.storageKey||"bm-reader-site-account-v1";var ttl=Math.max(6e4,Number(options.ttl)||5*60*1e3);var status={phase:"idle",message:"网站账号尚未连接",loggedIn:false,signedToday:false,serverDay:"",lastSyncAt:0,pending:0};var scope="",cache=blankCache(),inspection=null,inFlight=null;var running=false,stopped=false,manualStopped=false,suspended=false,listenersBound=false;var suspendedPhase="",suspendedMessage="",epoch=0,timer=null;var controllers=new Set,rows=[],mutationChain=Promise.resolve();var recentQueue=new Map,recentTimer=null,recentLastSent=new Map;var earlyFavoriteIntents=new Map,needsFavoriteSync=false;function blankCache(){return{version:1,scope:"",shelf:[],recent:[],manualFavorite:{},pendingFavorite:{},confirmedDay:"",confirmedAt:0,lastSyncAt:0}}function idOf(value){return typeof value==="string"&&/^\d{1,18}$/.test(value)?value:""}function plain(value){return!!value&&typeof value==="object"&&!Array.isArray(value)}function text(value,max){return typeof value==="string"?value.trim().slice(0,max):""}function time(value){return typeof value==="number"&&Number.isFinite(value)&&value>=0&&value<=864e13?value:0}function day(value){return typeof value==="string"&&/^\d{4}-\d{2}-\d{2}$/.test(value)?value:""}function opaque(value){return typeof value==="string"&&/^[a-f0-9]{16,128}$/i.test(value)?value:""}function safeUrl(value,book){try{var url=new URL(value,origin);if(url.origin!==origin||url.protocol!=="https:"||url.username||url.password)return null;if(book&&!new RegExp("^/read/"+book+"/\\d+\\.html$").test(url.pathname))return null;url.hash="";return url}catch(_){return null}}function normalShelf(items){if(!Array.isArray(items)||items.length>1e3)throw problem("format","网站书架格式异常,现有书架已保留");var seen=new Set;return items.map(function(item){var id=plain(item)&&idOf(item.id);if(!id||seen.has(id))throw problem("format","网站书架记录异常,现有书架已保留");seen.add(id);return{id:id,name:text(item.name,120),updatedAt:time(item.updatedAt)}})}function normalRecent(items){if(!Array.isArray(items)||items.length>1e3)throw problem("format","网站最近阅读格式异常,现有进度已保留");var seen=new Set,result=[];items.forEach(function(item){var id=plain(item)&&idOf(item.id);if(!id||seen.has(id))throw problem("format","网站最近阅读记录异常,现有进度已保留");seen.add(id);var url=safeUrl(item.url,id);if(!url)throw problem("format","网站最近阅读地址异常,现有进度已保留");result.push({id:id,name:text(item.name,120),url:url.href,title:text(item.title,120),time:time(item.time)})});return result}function problem(kind,message){var error=new Error(message);error.bmAccountReason=kind;return error}function isVisible(){return!doc||!doc.hidden}function publicStatus(){return Object.assign({},status)}function emit(){status.pending=Object.keys(cache.pendingFavorite).length+recentQueue.size+earlyFavoriteIntents.size;rows=rows.filter(function(node){return node.isConnected!==false});rows.forEach(function(node){node.textContent=status.message});if(typeof hooks.statusChanged==="function")hooks.statusChanged(publicStatus())}function pause(error){status.phase="paused";status.message=error&&error.bmAccountReason?error.message:"网站连接异常,自动操作已暂停,请稍后点重新连接";if(error&&(error.bmAccountReason==="login"||error.bmAccountReason==="verification"))status.loggedIn=false;if(!scope&&earlyFavoriteIntents.size){earlyFavoriteIntents.clear();needsFavoriteSync=true}clearTimeout(timer);timer=null;clearTimeout(recentTimer);recentTimer=null;emit()}function persist(){try{store.setItem(storageKey,JSON.stringify(cache))}catch(_){throw problem("storage","账号同步记录无法保存,现有书架与进度已保留")}}function activateScope(value){var next=opaque(value);if(!next)throw problem("format","网站登录状态无法确认,自动操作已暂停");if(scope===next)return;scope=next;recentQueue.clear();recentLastSent.clear();cache=blankCache();cache.scope=next;try{var saved=JSON.parse(store.getItem(storageKey)||"null");if(plain(saved)&&saved.version===1&&saved.scope===next){cache.shelf=normalShelf(saved.shelf);cache.recent=normalRecent(saved.recent);cache.confirmedDay=day(saved.confirmedDay);cache.confirmedAt=time(saved.confirmedAt);cache.lastSyncAt=time(saved.lastSyncAt);["manualFavorite","pendingFavorite"].forEach(function(key){if(!plain(saved[key]))return;Object.keys(saved[key]).slice(0,1e3).forEach(function(id){var entry=saved[key][id];if(idOf(id)&&plain(entry)&&typeof entry.favorite==="boolean"&&time(entry.time))cache[key][id]={favorite:entry.favorite,time:time(entry.time)}})})}}catch(_){cache=blankCache();cache.scope=next}}async function request(path,init){if(stopped)throw problem("cancelled","页面已离开,自动操作已停止");var url=safeUrl(path);if(!url)throw problem("format","网站接口地址不正确,自动操作已暂停");var config=Object.assign({},init||{});config.credentials="include";config.redirect="manual";var controller=typeof win.AbortController==="function"?new win.AbortController:null;if(controller){controllers.add(controller);config.signal=controller.signal}var timeout;try{var fetcher=options.fetch||win.fetch.bind(win);return await Promise.race([(async function(){var response=await fetcher(url.href,config);if(response.type==="opaqueredirect"||response.status>=300&&response.status<400||response.status===401)throw problem("login","网站登录已失效,请在个人中心登录后重新连接");if(response.status===403||response.status===429)throw problem("verification","网站要求验证或请求受限,请处理后重新连接");if(!response.ok)throw problem("network","网站连接失败("+response.status+"),自动操作已暂停");if(response.url&&!safeUrl(response.url))throw problem("login","网站登录跳转异常,自动操作已暂停");var body=await response.text();if(/<(?:input|form)\b[^>]*(?:GOEDGE_WAF_CAPTCHA_CODE|loginForm)|(?:身份验证|安全验证|人机验证|Just a moment)/i.test(body))throw problem("verification","网站需要验证,请处理后重新连接");return{text:body,status:response.status,url:response.url||url.href,header:function(name){return response.headers&&response.headers.get(name)}}})(),new Promise(function(_,reject){timeout=setTimeout(function(){if(controller)controller.abort();reject(problem("network","网站连接超时,自动操作已暂停"))},15e3)})])}catch(error){if(error&&error.bmAccountReason)throw error;throw problem("network","网站网络连接失败,自动操作已暂停")}finally{clearTimeout(timeout);if(controller)controllers.delete(controller)}}var context={request:request,origin:origin,document:doc,problem:problem,safeUrl:safeUrl,now:now,isVisible:isVisible};function scopedContext(token){return Object.assign({},context,{request:async function(path,config){if(token!==epoch||stopped)throw problem("cancelled","页面状态已变化,旧请求已停止");var response=await request(path,config);if(token!==epoch||stopped)throw problem("cancelled","页面状态已变化,旧响应已忽略");return response},isVisible:function(){return token===epoch&&!stopped&&isVisible()}})}function mergeRemoteIntoLocal(){if(typeof hooks.loadLibrary!=="function"||typeof hooks.storeLibrary!=="function")return;var library=hooks.loadLibrary();if(!plain(library)||library.version!==1||!Array.isArray(library.books)||library.books.length>1e3)throw problem("storage","本地书架格式异常,现有书架已保留");var books=new Map;library.books.forEach(function(book){books.set(book.id,Object.assign({},book))});cache.shelf.forEach(function(remote){var local=books.get(remote.id),manual=cache.manualFavorite[remote.id];var cancelled=manual&&!manual.favorite&&(!remote.updatedAt||manual.time>=remote.updatedAt);if(!local)local={id:remote.id,name:remote.name,favorite:!cancelled,createdAt:remote.updatedAt||0,updatedAt:remote.updatedAt||0};else{if(!cancelled)local.favorite=true;if(!local.name&&remote.name)local.name=remote.name;local.updatedAt=Math.max(time(local.updatedAt),remote.updatedAt)}books.set(remote.id,local)});cache.recent.forEach(function(remote){if(!books.has(remote.id))books.set(remote.id,{id:remote.id,name:remote.name,favorite:false,createdAt:remote.time||0,updatedAt:remote.time||0});else if(!books.get(remote.id).name&&remote.name)books.get(remote.id).name=remote.name});if(books.size>1e3)throw problem("storage","合并后的书架超过 1000 本,现有书架已保留");var merged={version:1,books:Array.from(books.values())};if(typeof hooks.validateLibrary==="function")merged=hooks.validateLibrary(merged);if(JSON.stringify(merged)!==JSON.stringify(library))hooks.storeLibrary(merged);if(typeof hooks.libraryChanged==="function")hooks.libraryChanged()}function decorateShelf(entries){if(!status.loggedIn||!(entries instanceof Map))return entries;cache.recent.forEach(function(remote){var entry=entries.get(remote.id);if(!entry)entry={id:remote.id,name:remote.name,favorite:false,createdAt:0,updatedAt:0};entry.siteRecent=Object.assign({},remote);entries.set(remote.id,entry)});return entries}function getRemoteResume(id){if(!status.loggedIn||!idOf(id))return null;var entry=cache.recent.find(function(item){return item.id===id});return entry?Object.assign({},entry):null}function scheduleVisible(){clearTimeout(timer);timer=null;if(!stopped&&running&&isVisible()&&status.phase!=="paused")timer=setTimeout(function(){refresh(false)},ttl)}async function inspect(){if(typeof api.inspect!=="function")throw problem("unavailable","账号接口尚未连接");var token=epoch;var result=await api.inspect(scopedContext(token));if(token!==epoch||stopped)throw problem("cancelled","页面状态已变化,旧登录状态已忽略");if(!plain(result)||result.loggedIn!==true)throw problem("login","请先在网站个人中心登录,再点重新连接");activateScope(result.scope);inspection=result;status.loggedIn=true;status.serverDay=day(result.serverDay);status.signedToday=result.signedToday===true;return result}async function checkIn(result,token){if(token!==epoch||stopped||result.scope!==scope||status.phase==="paused")return;if(!day(result.serverDay)||typeof result.signedToday!=="boolean")return;if(result.signedToday)return;if(typeof api.signIn!=="function")return;var response=await api.signIn(scopedContext(token),result);if(token!==epoch||stopped||result.scope!==scope)return;if(!plain(response)||response.success!==true||response.signedToday!==true||day(response.serverDay)!==result.serverDay)throw problem("format","网站尚未确认签到成功,自动操作已暂停");status.signedToday=true;cache.confirmedDay=response.serverDay;cache.confirmedAt=now();persist()}async function flushFavorites(token,expectedScope){if(typeof api.setFavorite!=="function")return;expectedScope=expectedScope||scope;var session=inspection;var ids=Object.keys(cache.pendingFavorite);for(var i=0;i=1e3&&!earlyFavoriteIntents.has(id)){needsFavoriteSync=true;status.message="本地收藏已保存;连接后请点“同步本地已收藏”上传";emit();return Promise.resolve(false)}earlyFavoriteIntents.set(id,action);status.message="本地收藏已保存,正在等待网站账号确认后同步";emit();return Promise.resolve(false)}cache.manualFavorite[id]=action;cache.pendingFavorite[id]=Object.assign({},action);try{persist();emit()}catch(error){pause(error);return Promise.resolve(false)}if(status.phase==="paused"||!status.loggedIn)return Promise.resolve(false);var token=epoch,expectedScope=scope;return enqueueMutation(function(){return flushFavorites(token,expectedScope)},expectedScope).then(function(){return status.phase!=="paused"})}function syncLocalFavorites(){if(stopped||!status.loggedIn||!scope||typeof hooks.loadLibrary!=="function")return Promise.resolve(false);try{hooks.loadLibrary().books.filter(function(entry){return entry.favorite===true}).forEach(function(entry){var at=time(entry.updatedAt)||now();cache.manualFavorite[entry.id]={favorite:true,time:at};cache.pendingFavorite[entry.id]={favorite:true,time:at}});persist();emit()}catch(error){pause(error);return Promise.resolve(false)}if(status.phase==="paused")return Promise.resolve(false);var token=epoch,expectedScope=scope;return enqueueMutation(function(){return flushFavorites(token,expectedScope)},expectedScope).then(function(){var success=token===epoch&&!stopped&&expectedScope===scope&&status.phase!=="paused";if(success){needsFavoriteSync=false;emit()}return success})}function progressChanged(id,progress){if(stopped||!status.loggedIn||status.phase==="paused"||!isVisible()||typeof api.writeRecent!=="function"||!idOf(id)||!plain(progress))return;var url=safeUrl(progress.url,id),savedAt=time(progress.time);if(!url||!savedAt||!Number.isInteger(progress.index)||progress.index<0||typeof progress.ratio!=="number"||progress.ratio<0||progress.ratio>1)return;var page=Number(url.searchParams.get("p"))||1;var key=url.pathname+"?p="+page,previous=recentQueue.get(id);var readyAt=previous&&previous.key===key?previous.readyAt:now()+4e3;recentQueue.set(id,{id:id,url:url.href,title:text(progress.title,120),time:savedAt,key:key,readyAt:readyAt});emit();if(recentTimer||!isVisible())return;recentTimer=setTimeout(function(){recentTimer=null;flushRecent()},Math.max(5e3,Number(options.recentDelay)||15e3))}function flushRecent(){if(stopped||!status.loggedIn||status.phase==="paused"||!isVisible())return Promise.resolve(false);var token=epoch,expectedScope=scope,session=inspection;return enqueueMutation(async function(){var entries=Array.from(recentQueue.values());for(var i=0;i0)value=String(value);return typeof value==="string"&&/^[1-9]\d{0,17}$/.test(value)?value:""}function parseJson(response,context,label){var value;try{value=JSON.parse(response.text)}catch(_){throw context.problem("format",label+"响应异常,自动操作已暂停")}if(!value||typeof value!=="object"||Array.isArray(value)||value.code!==0)throw context.problem("login",label+"未通过网站确认,请检查登录后重新连接");return value}async function hashScope(value,context){var crypto=options.crypto||win.crypto;var Encoder=options.TextEncoder||win.TextEncoder;if(!crypto||!crypto.subtle||typeof crypto.subtle.digest!=="function"||typeof Encoder!=="function")throw context.problem("unavailable","浏览器无法安全区分网站账号,自动同步已暂停");var bytes=(new Encoder).encode("bm-boluomao-site-account-v1:"+value);var hashed=await crypto.subtle.digest("SHA-256",bytes);return Array.from(new Uint8Array(hashed)).map(function(byte){return byte.toString(16).padStart(2,"0")}).join("")}function parseServerDay(calendar,context){var month=calendar&&calendar.textContent.match(/(\d{4})年\s*(\d{1,2})月/);var today=calendar&&calendar.querySelector(".mcCalDay.today");var match=today&&today.textContent.trim().match(/^(\d{1,2})(?:\D|$)/);if(!month||!match)throw context.problem("format","无法确认网站当天日期,自动签到已暂停");var year=Number(month[1]),monthNumber=Number(month[2]),date=Number(match[1]);var stamp=new Date(Date.UTC(year,monthNumber-1,date));if(stamp.getUTCFullYear()!==year||stamp.getUTCMonth()!==monthNumber-1||stamp.getUTCDate()!==date)throw context.problem("format","网站当天日期异常,自动签到已暂停");return{value:month[1]+"-"+String(monthNumber).padStart(2,"0")+"-"+String(date).padStart(2,"0"),today:today}}async function inspect(context){var response=await context.request("/user/center/");if(typeof Parser!=="function")throw context.problem("unavailable","浏览器无法解析个人中心,自动同步已暂停");var document=(new Parser).parseFromString(response.text,"text/html");var script=Array.from(document.querySelectorAll("script")).map(function(node){return node.textContent}).join("\n");var uid=script.match(/\bcurrentUid\s*=\s*([1-9]\d*)\s*;/);var checked=script.match(/\btodayChecked\s*=\s*(true|false)\s*;/);var calendar=document.querySelector(".mcCalendar");if(!uid||!calendar||document.querySelector('#loginForm, form[action*="login"]'))throw context.problem("login","请先在网站个人中心登录,再点重新连接");var server=parseServerDay(calendar,context);if(!checked||checked[1]==="true"!==server.today.classList.contains("checked"))throw context.problem("format","网站签到状态尚未明确,自动签到已暂停");var scope=await hashScope(uid[1],context);return{loggedIn:true,scope:scope,serverDay:server.value,signedToday:checked[1]==="true"}}async function signIn(context,previous){var response=await context.request("/api/checkin.php",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:"action=checkin"});parseJson(response,context,"签到");var current=await inspect(context);if(!current.signedToday||current.scope!==previous.scope||current.serverDay!==previous.serverDay)throw context.problem("format","网站尚未确认今日签到成功,自动操作已暂停");return{success:true,signedToday:true,serverDay:current.serverDay}}async function readShelf(context){var books=new Map;for(var page=1;page<=1e3;page++){var response=parseJson(await context.request("/api/bookcase.php?page="+page),context,"网站书架");var total=Number(response.total),data=response.data;if(!Number.isInteger(total)||total<0||total>1e3||!Array.isArray(data)||data.length>1e3)throw context.problem("format","网站书架数量或格式异常,现有书架已保留");var previousSize=books.size;data.forEach(function(entry){var book=entry&&id(entry.articleid);if(!book||typeof entry.articlename!=="string")throw context.problem("format","网站书架内容异常,现有书架已保留");books.set(book,{id:book,name:entry.articlename.trim().slice(0,120),updatedAt:0})});if(books.size>1e3)throw context.problem("format","网站书架超过 1000 本,现有书架已保留");if(books.size>=total)return Array.from(books.values());if(!data.length||books.size===previousSize)throw context.problem("format","网站书架分页未完整返回,请重新连接;现有书架已保留")}throw context.problem("format","网站书架分页未完成,现有书架已保留")}async function readRecent(context){var response=parseJson(await context.request("/api/bookmark.php?action=recent"),context,"网站最近阅读");if(!Array.isArray(response.data)||response.data.length>1e3)throw context.problem("format","网站最近阅读格式异常,现有进度已保留");return response.data.map(function(entry){var book=entry&&id(entry.articleid),chapter=entry&&id(entry.chapterid);var page=entry&&entry.page==null?1:Number(entry&&entry.page);var seconds=entry&&entry.updatetime;var readingTime=typeof seconds==="number"&&Number.isFinite(seconds)?seconds*1e3:NaN;if(!book||!chapter||!Number.isInteger(page)||page<1||page>1e4||typeof entry.articlename!=="string"||typeof entry.chaptername!=="string"||!Number.isFinite(readingTime)||!(seconds>1e9&&seconds<1e11)||readingTime>864e13)throw context.problem("format","网站最近阅读内容异常,现有进度已保留");var url=new URL("/read/"+book+"/"+chapter+".html",context.origin);if(page>1)url.searchParams.set("p",String(page));return{id:book,name:entry.articlename.trim().slice(0,120),url:url.href,title:entry.chaptername.trim().slice(0,120),time:readingTime}})}async function setFavorite(context,book,favorite,previous){book=id(book);if(!book||typeof favorite!=="boolean"||!previous||!previous.scope)throw context.problem("format","收藏同步参数无法确认,自动操作已暂停");var current=await inspect(context);if(current.scope!==previous.scope)throw context.problem("login","网站账号已变化,请重新连接后再同步收藏");var response=await context.request("/api/bookcase.php",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:"action="+(favorite?"add":"remove")+"&articleid="+encodeURIComponent(book)});parseJson(response,context,"收藏同步");return{success:true}}async function writeRecent(context,entry,previous){var book=entry&&id(entry.id),url;try{url=new URL(entry&&entry.url,context.origin)}catch(_){}var chapter=url&&new RegExp("^/read/"+book+"/([1-9]\\d{0,17})\\.html$").exec(url.pathname);var page=url&&url.searchParams.has("p")?Number(url.searchParams.get("p")):1;if(!book||!url||url.origin!==context.origin||url.protocol!=="https:"||url.username||url.password||!chapter||url.searchParams.getAll("p").length>1||!Number.isInteger(page)||page<1||page>1e4||!previous||!previous.scope)throw context.problem("format","最近阅读同步地址无法确认,自动操作已暂停");var current=await inspect(context);if(current.scope!==previous.scope)throw context.problem("login","网站账号已变化,请重新连接后再同步最近阅读");if(typeof context.isVisible==="function"&&!context.isVisible())return{deferred:true};var response=await context.request("/api/bookmark.php",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:"action=last&articleid="+encodeURIComponent(book)+"&chapterid="+encodeURIComponent(chapter[1])+"&page="+page});parseJson(response,context,"最近阅读同步");return{success:true}}return{inspect:inspect,signIn:signIn,readShelf:readShelf,readRecent:readRecent,setFavorite:setFavorite,writeRecent:writeRecent}}); (function () { 'use strict'; if (window.top !== window) return; var pageWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; // BEGIN EARLY AD GUARD 1.9.18 (function installEarlyAdGuard(){"use strict";if(window.top!==window||location.hostname!=="www.boluomao1.com"||/^\/WAF(?:\/|$)/i.test(location.pathname)||/^\/user\/(?:login|register|forgot|reset)(?:\/|$)/i.test(location.pathname))return;var host=typeof pageWindow!=="undefined"?pageWindow:typeof unsafeWindow!=="undefined"?unsafeWindow:window;var slot="__bmEarlyAdGuardV1",previous=host[slot];if(previous&&previous.document===document&&typeof previous.refresh==="function"){previous.refresh();return}var styleId="bm-early-ad-style";var selector=".adBannerPC,#annOnceOverlay,.urn-wrap.urn-fixed,.urn-wrap.urn-float,"+'iframe.urn-frame,iframe[src^="https://www.rrunion.com/api/frame.php?"],'+'iframe[src^="http://www.rrunion.com/api/frame.php?"],'+'iframe[src^="//www.rrunion.com/api/frame.php?"],'+'iframe[src^="https://rrunion.com/api/frame.php?"],'+'iframe[src^="http://rrunion.com/api/frame.php?"],'+'iframe[src^="//rrunion.com/api/frame.php?"]';var css=selector+"{display:none!important;visibility:hidden!important;"+"pointer-events:none!important;}";var styleNode=null;function ensureStyle(){var parent=document.head||document.documentElement;if(!parent)return;var existing=document.getElementById(styleId);if(existing&&existing.tagName==="STYLE")styleNode=existing;if(!styleNode){styleNode=document.createElement("style");styleNode.id=styleId;styleNode.type="text/css"}if(styleNode.textContent!==css)styleNode.textContent=css;if(styleNode.parentNode!==parent)parent.appendChild(styleNode)}function hide(node){if(!node||node.nodeType!==1||!node.matches(selector))return;[["display","none"],["visibility","hidden"],["pointer-events","none"]].forEach(function(pair){if(node.style.getPropertyValue(pair[0])!==pair[1]||node.style.getPropertyPriority(pair[0])!=="important"){node.style.setProperty(pair[0],pair[1],"important")}})}function cleanTree(node){if(!node||node.nodeType!==1)return;hide(node);if(!node.childElementCount)return;var matches=node.querySelectorAll(selector);for(var i=0;i=0;var isCaptchaPage=allowed&&location.pathname===captchaPath;var controlOwner={};var stopped=false,busy=false,submitted=false,sequence=0,transport=null;var card=null,status=null,configPanel=null,challenge=null,startupObserver=null;var startupTimer=null,navigationTimer=null,ownedCode="",lastMessage="";var controlTouch={control:null,id:null,x:0,y:0,at:0,moved:false,last:null,lastAt:0};var cachedConfig=null,savingConfig=false,configReadPending=null;var storageError="",requestError="",requestStage="初始化",configSource="未检测";var readVerified=false,saveVerified=false,sharedVerified=false,saveAttempted=false;var capabilityTimers=[];var trace=[],traceLimit=60,traceKey="bm-private-captcha-trace-v1",flowStartedAt=0,requestStartedAt=0,lastElapsedMs=0;var historyKey="bm-private-captcha-history-v1",historyLimit=300,historyLifetime=7*24*60*60*1e3;var history=[],historyClearedAt=0,historyRevision=0,historyStorage="尚未核对",historySequence=0,historyWriter=newHistoryWriter();var historyExportUrls=[],historyEntryWaiting=false;var phaseFence=0,phaseTransport="未发出",phaseCallback=false,phaseHttp=false,phaseStarted=false;var phaseNativeStarted=false,phaseReadyState=-1,phaseCallbackType="";var elapsedTimer=null,flowNumber=0,waitCheckpoint=0,phasePending=false;var nativeRequestSubmit=typeof HTMLFormElement!=="undefined"&&HTMLFormElement.prototype.requestSubmit;var nativeSubmit=typeof HTMLFormElement!=="undefined"&&HTMLFormElement.prototype.submit;var traceStages=["auth_start","auth_done","auth_cache","auth_invalidate","waiting","config_saved","card","api_probe_cancel","config_read","stop","api_probe_start","api_probe_result","api_probe_fallback","api_probe_wait","api_probe_callback","api_probe_send","transport_select","timeout","request_failed","ignored_result","callback","http","body_received","parse_detail","parse","bridge_wait","send","network_state","http_headers","body","compatibility","fill","submit","submit_called","navigation_wait","manual","begin","config_blocked","config","snapshot","budget_blocked","refresh_failed","refresh","navigation_complete","navigation_departure","attempt_reserved","attempt_failed","round_advance","round_ready","cycle_stopped","cycle_restart","navigation_rejected"];restoreTrace();restoreHistory();installHistoryEntry();if(allowed)window.addEventListener("storage",function(event){if(event.key!==historyKey&&event.key!==null)return;try{if(event.storageArea&&event.storageArea!==localStorage)return;var record=event.newValue===null?{v:1,at:Date.now(),clearedAt:Date.now(),clearRevision:historyRevision+1,entries:[]}:parseHistory(event.newValue);if(!record)return;var changed=mergeHistory(record);historyStorage="localStorage 读取已核对";if(changed){pruneTraceAfterClear();persistTrace();updateDiagnostics()}}catch(_){historyStorage="持久保存失败,仅本标签临时保存";updateDiagnostics()}});function newHistoryWriter(){var result="";try{var words=new Uint32Array(4);crypto.getRandomValues(words);for(var index=0;indexnow||item.time<0||!Number.isFinite(item.elapsedMs)||item.elapsedMs<0||item.elapsedMs>24*60*60*1e3||!Number.isInteger(item.flow)||item.flow<0||item.flow>1e6||typeof item.stage!=="string"||traceStages.indexOf(item.stage)<0||typeof item.message!=="string"||item.message.length>160||/[\r\n]/.test(item.message)||/sk-[A-Za-z0-9_-]{8,256}|Bearer |data:image|\b[0-9]{6,}\b/.test(item.message)||/[A-Za-z]/.test(item.message.replace(/GM_legacy|GM_promise|onreadystatechange|readystatechange|ontimeout|onerror|onabort|onloadstart|onload|Promise|Cookie|HTTP|fetch|CORS|JSON|POST|GET|OCR|API|GM|ms/g,""))||requireId&&(typeof item.id!=="string"||!/^[0-9a-f]{40}$/.test(item.id)||typeof item.clearRevision!=="undefined"&&(!Number.isInteger(item.clearRevision)||item.clearRevision<0||item.clearRevision>1e6)))return null;var clean={time:item.time,elapsedMs:item.elapsedMs,flow:item.flow,stage:item.stage,message:item.message};if(requireId){clean.id=item.id;clean.clearRevision=item.clearRevision||0}return clean}function canonicalTrace(item){return{time:item.time,elapsedMs:item.elapsedMs,flow:item.flow,stage:item.stage,message:item.message}}function legacyHistoryId(item){var text=JSON.stringify(canonicalTrace(item)),result="";for(var index=0;index<5;index++)result+=hash(String(index)+":"+text).padStart(8,"0");return result}function restoreTrace(){if(!allowed)return;try{var text=sessionStorage.getItem(traceKey);if(!text||text.length>4e4)return;var value=JSON.parse(text),now=Date.now();if(!value||value.v!==1||!Number.isFinite(value.at)||value.at>now||now-value.at>15*60*1e3||!Number.isInteger(value.flow)||value.flow<0||value.flow>1e6||!Array.isArray(value.entries)||value.entries.length>traceLimit)return;var restored=value.entries.map(function(item){return safeLogEntry(item,false)});if(restored.some(function(item){return!item||item.flow>value.flow}))return;var ids=Array.isArray(value.ids)&&value.ids.length===restored.length&&value.ids.every(function(id){return typeof id==="string"&&/^[0-9a-f]{40}$/.test(id)})?value.ids:null;var revisions=Array.isArray(value.revisions)&&value.revisions.length===restored.length&&value.revisions.every(function(revision){return Number.isInteger(revision)&&revision>=0&&revision<=1e6})?value.revisions:null;trace=restored.map(function(item,index){item.id=ids?ids[index]:legacyHistoryId(item);item.clearRevision=revisions?revisions[index]:0;return item});flowNumber=value.flow}catch(_){}}function persistTrace(){if(!allowed)return;try{sessionStorage.setItem(traceKey,JSON.stringify({v:1,at:Date.now(),flow:flowNumber,entries:trace.map(canonicalTrace),ids:trace.map(function(item){return item.id}),revisions:trace.map(function(item){return item.clearRevision||0})}))}catch(_){}}function normalizeHistory(entries,clearedAt,clearRevision){var cutoff=Date.now()-historyLifetime,seen=Object.create(null);entries.forEach(function(item){var entry=safeLogEntry(item,true);if(!entry||entry.timeright.id?1:0)}).slice(-historyLimit)}function parseHistory(text){if(typeof text!=="string"||text.length>2e5)return null;try{var value=JSON.parse(text),now=Date.now();if(!value||value.v!==1||!Number.isFinite(value.at)||value.at<0||value.at>now||!Number.isFinite(value.clearedAt)||value.clearedAt<0||value.clearedAt>now||typeof value.clearRevision!=="undefined"&&(!Number.isInteger(value.clearRevision)||value.clearRevision<0||value.clearRevision>1e6)||!Array.isArray(value.entries)||value.entries.length>historyLimit||value.entries.some(function(item){return!safeLogEntry(item,true)}))return null;return{v:1,at:value.at,clearedAt:value.clearedAt,clearRevision:value.clearRevision||0,entries:normalizeHistory(value.entries,value.clearedAt,value.clearRevision||0)}}catch(_){return null}}function readHistoryRecord(){var raw=localStorage.getItem(historyKey);if(raw===null)return{v:1,at:Date.now(),clearedAt:0,clearRevision:0,entries:[]};var value=parseHistory(raw);if(!value)throw new Error("LOG_ARCHIVE_INVALID");return value}function mergeHistory(record,extra){var old=JSON.stringify(history),oldClear=historyClearedAt,oldRevision=historyRevision;if(record.clearedAt>historyClearedAt||record.clearedAt===historyClearedAt&&record.clearRevision>historyRevision){historyClearedAt=record.clearedAt;historyRevision=record.clearRevision}history=normalizeHistory(history.concat(record.entries,extra||[]),historyClearedAt,historyRevision);return oldClear!==historyClearedAt||oldRevision!==historyRevision||old!==JSON.stringify(history)}function pruneTraceAfterClear(){if(historyClearedAt)trace=trace.filter(function(item){return item.time>historyClearedAt||item.time===historyClearedAt&&(item.clearRevision||0)===historyRevision})}function persistHistory(entry,userClear){if(!allowed)return false;try{var remote;try{remote=readHistoryRecord()}catch(error){if(!userClear)throw error;remote={v:1,at:Date.now(),clearedAt:historyClearedAt,clearRevision:historyRevision,entries:[]}}mergeHistory(remote,entry?[entry]:[]);pruneTraceAfterClear();var value={v:1,at:Date.now(),clearedAt:historyClearedAt,clearRevision:historyRevision,entries:history};localStorage.setItem(historyKey,JSON.stringify(value));var readBackRaw=localStorage.getItem(historyKey),readBack=parseHistory(readBackRaw);if(!readBack||readBack.clearedAt!==historyClearedAt||readBack.clearRevision!==historyRevision||JSON.stringify(readBack.entries)!==JSON.stringify(history))throw new Error("LOG_ARCHIVE_UNVERIFIED");historyStorage="localStorage 写入回读已验证";return true}catch(_){if(entry)history=normalizeHistory(history.concat([entry]),historyClearedAt,historyRevision);historyStorage="持久保存失败,仅本标签临时保存";return false}}function restoreHistory(){if(!allowed)return;try{var record=readHistoryRecord();mergeHistory(record,trace);pruneTraceAfterClear();history.forEach(function(entry){flowNumber=Math.max(flowNumber,entry.flow)});persistTrace();persistHistory(null)}catch(_){history=normalizeHistory(trace,historyClearedAt,historyRevision);historyStorage="持久保存失败,仅本标签临时保存"}}function refreshHistory(){if(!allowed)return history;try{mergeHistory(readHistoryRecord());pruneTraceAfterClear()}catch(_){historyStorage="持久保存失败,仅本标签临时保存"}history=normalizeHistory(history,historyClearedAt,historyRevision);return history}function historyText(){return formatLogEntries(refreshHistory(),true)}function historyStatusText(){return"本地历史 · "+location.hostname+" · 最近 7 天 / 最多 300 条 · "+history.length+" 条 · "+historyStorage+";各域名分别保存。"}function clearHistory(control){if(!allowed)return;try{mergeHistory(readHistoryRecord())}catch(_){}var nextClear=Math.max(historyClearedAt,Date.now());historyRevision=nextClear===historyClearedAt?historyRevision+1:1;historyClearedAt=nextClear;history=[];trace=[];persistTrace();var saved=persistHistory(null,true);updateDiagnostics();var owner=control&&control.closest("[data-bm-captcha-log-panel]");if(owner){revokeHistoryExport(owner);owner.querySelector("[data-bm-captcha-copy-status]").textContent=saved?"本站历史日志已清空,清空状态已回读验证。":"本标签日志已清空;持久保存失败,其它标签的历史未确认清空。"}}function installHistoryEntry(){if(!allowed||isCaptchaPage||!/^(?:www\.)?rrssk\.com$/.test(location.hostname))return;if(!document.body){if(!historyEntryWaiting){historyEntryWaiting=true;document.addEventListener("DOMContentLoaded",function(){historyEntryWaiting=false;installHistoryEntry()},{once:true})}return}var entry=document.querySelector("[data-bm-captcha-history-entry]");if(!history.length&&historyStorage.indexOf("持久保存失败")!==0){if(entry)entry.hidden=true;return}if(!entry){entry=button("验证日志","data-bm-captcha-history-entry");entry.setAttribute("data-bm-captcha-config","");entry.setAttribute("aria-label","查看本站验证历史日志");entry.style.cssText="position:fixed;right:14px;bottom:calc(16px + env(safe-area-inset-bottom,0px));z-index:2147483645;"+"min-height:40px;max-width:140px;padding:7px 12px;border:1px solid #a2b5aa;border-radius:18px;"+"background:#eef2ed;color:#315547;box-shadow:0 2px 10px rgba(0,0,0,.12);font:13px/1.3 sans-serif;";document.body.appendChild(entry)}entry.hidden=false}function openHistory(){openConfig();if(!configPanel)return;var toggle=configPanel.querySelector("[data-bm-captcha-history-toggle]");var owner=toggle&&toggle.closest("[data-bm-captcha-log-panel]");if(!owner)return;owner.open=true;if(owner.getAttribute("data-bm-history-open")!=="1")toggleHistory(toggle);if(typeof owner.scrollIntoView==="function")owner.scrollIntoView({block:"nearest",behavior:"auto"})}function toggleHistory(control){var owner=control&&control.closest("[data-bm-captcha-log-panel]");if(!owner)return;var historyMode=owner.getAttribute("data-bm-history-open")!=="1";owner.setAttribute("data-bm-history-open",historyMode?"1":"0");if(historyMode){refreshHistory();var output=owner.querySelector("[data-bm-captcha-history-text]");if(!output){output=document.createElement("pre");output.setAttribute("data-bm-captcha-history-text","");output.style.cssText=owner.querySelector("[data-bm-captcha-log-text]").style.cssText;owner.querySelector("[data-bm-captcha-log-text]").after(output)}}renderLogs()}function revokeHistoryExport(owner){if(!owner)return;var link=owner.querySelector("[data-bm-captcha-history-download]");if(link&&link._bmHistoryUrl){try{URL.revokeObjectURL(link._bmHistoryUrl)}catch(_){}historyExportUrls=historyExportUrls.filter(function(value){return value!==link._bmHistoryUrl});link._bmHistoryUrl=""}if(link){link.hidden=true;link.style.setProperty("display","none","important");link.removeAttribute("href")}}function exportHistory(control){var owner=control&&control.closest("[data-bm-captcha-log-panel]");if(!owner)return;var message=owner.querySelector("[data-bm-captcha-copy-status]");refreshHistory();if(typeof Blob!=="function"||typeof URL.createObjectURL!=="function"){message.textContent="浏览器不支持文件下载链接,请使用复制全部历史。";return}try{revokeHistoryExport(owner);var safe={version:1,host:location.hostname,exportedAt:Date.now(),retentionDays:7,entries:history.map(function(item){return safeLogEntry(item,true)})};var url=URL.createObjectURL(new Blob([JSON.stringify(safe,null,2)],{type:"application/json;charset=utf-8"}));historyExportUrls.push(url);var link=owner.querySelector("[data-bm-captcha-history-download]");if(!link){link=document.createElement("a");link.setAttribute("data-bm-captcha-history-download","");link.style.cssText="display:block;margin-top:8px;font-size:13px;";owner.appendChild(link)}link._bmHistoryUrl=url;link.href=url;link.download="reader-log-"+(new Date).toISOString().slice(0,10)+".json";link.textContent="点击下载本站历史日志 JSON";link.hidden=false;link.style.setProperty("display","block","important");message.textContent="安全历史日志已准备好,请点击下方下载链接;不含密钥、验证码图片或会话信息。"}catch(_){message.textContent="文件准备失败,请使用复制全部历史。"}}function sixDigits(value){if(typeof value!=="string")return"";var clean=value.trim();return/^[0-9]{6}[.\u3002]?$/.test(clean)?clean.slice(0,6):""}function validKey(value){return typeof value==="string"&&/^[A-Za-z0-9_-]{8,128}$/.test(value.trim())&&!/^__BM_PRIVATE/.test(value.trim())}function parseConfig(value){for(var depth=0;depth<2&&typeof value==="string";depth++){if(value.length>4096)return null;try{value=JSON.parse(value)}catch(_){return null}}if(!value||typeof value!=="object"||Array.isArray(value)||value.version!==3||value.provider!=="baidu"||typeof value.enabled!=="boolean"||typeof value.key!=="string"||typeof value.secret!=="string"||value.key&&!validKey(value.key)||value.secret&&!validKey(value.secret)||!!value.key!==!!value.secret||value.enabled&&(!value.key||!value.secret)||typeof value.deleted!=="undefined"&&typeof value.deleted!=="boolean"||typeof value.updatedAt!=="undefined"&&(!Number.isFinite(value.updatedAt)||value.updatedAt<0)||typeof value.transportMethod!=="undefined"&&["auto","fetch","gm"].indexOf(value.transportMethod)<0||typeof value.ocrType!=="undefined"&&["numbers","accurate_basic"].indexOf(value.ocrType)<0||value.deleted&&(value.enabled||value.key||value.secret))return null;return{version:3,provider:"baidu",enabled:value.enabled,key:value.key.trim(),secret:value.secret.trim(),deleted:!!value.deleted,updatedAt:value.updatedAt||0,transportMethod:value.transportMethod||"auto",ocrType:value.ocrType||"numbers"}}function emptyConfig(){return{version:3,provider:"baidu",enabled:false,key:"",secret:"",deleted:false,updatedAt:0,transportMethod:"auto",ocrType:"numbers"}}function sameConfig(first,second){return!!first&&!!second&&first.provider===second.provider&&first.enabled===second.enabled&&first.key===second.key&&first.secret===second.secret&&first.deleted===second.deleted&&first.updatedAt===second.updatedAt&&first.transportMethod===second.transportMethod&&first.ocrType===second.ocrType}function adapters(){var list=[];if(typeof GM_getValue==="function"&&typeof GM_setValue==="function"){list.push({id:"GM_legacy",shared:true,get:function(){return GM_getValue(configKey,null)},set:function(value){return GM_setValue(configKey,value)}})}if(typeof GM!=="undefined"&&GM&&typeof GM.getValue==="function"&&typeof GM.setValue==="function"){list.push({id:"GM_promise",shared:true,get:function(){return GM.getValue(configKey,null)},set:function(value){return GM.setValue(configKey,value)}})}list.push({id:"localStorage",shared:false,get:function(){return localStorage.getItem(configKey)},set:function(value){localStorage.setItem(configKey,value)}});return list}function bounded(operation,code){return new Promise(function(resolve,reject){var settled=false;var timer=setTimeout(function(){if(!settled){settled=true;reject(new Error(code))}},1200);Promise.resolve().then(operation).then(function(value){if(settled)return;settled=true;clearTimeout(timer);resolve(value)},function(){if(!settled){settled=true;clearTimeout(timer);reject(new Error(code))}})})}function safeViaRuntime(){try{var page=typeof unsafeWindow!=="undefined"&&unsafeWindow?unsafeWindow:window;var bridge=page&&(page.via_gm||page.via);return!!bridge&&(typeof bridge==="object"||typeof bridge==="function")&&/Android|(?:^|\W)Via(?:Browser)?(?:\W|$)/i.test(String(navigator.userAgent||""))}catch(_){return false}}function elapsedMs(){return busy&&requestStartedAt?Math.max(0,Date.now()-requestStartedAt):lastElapsedMs}function logEvent(stage,message){var entry=safeLogEntry({id:historyWriter+(++historySequence).toString(16).padStart(8,"0"),clearRevision:historyRevision,time:Date.now(),flow:flowNumber,elapsedMs:flowStartedAt?Math.max(0,Date.now()-flowStartedAt):0,stage:String(stage).slice(0,32),message:String(message).replace(/[\r\n]/g," ").slice(0,160)},true);if(!entry)return;trace.push(entry);if(trace.length>traceLimit)trace.splice(0,trace.length-traceLimit);persistHistory(entry);persistTrace();updateDiagnostics()}function logText(){return formatLogEntries(trace)}function formatLogEntries(entries,dated){return entries.map(function(item){var date=new Date(item.time),stamp=[date.getHours(),date.getMinutes(),date.getSeconds()].map(function(number){return String(number).padStart(2,"0")}).join(":");if(dated)stamp=date.getFullYear()+"-"+String(date.getMonth()+1).padStart(2,"0")+"-"+String(date.getDate()).padStart(2,"0")+" "+stamp;return"["+stamp+(item.flow?" #"+item.flow:"")+" +"+(item.elapsedMs/1e3).toFixed(1)+"s] "+item.message}).join("\n")}function renderLogs(){var text=logText()||"等待开始识别。";document.querySelectorAll("[data-bm-captcha-log-text]").forEach(function(node){if(node.textContent!==text)node.textContent=text});var fullText=null;document.querySelectorAll("[data-bm-captcha-log-panel]").forEach(function(owner){var opened=owner.getAttribute("data-bm-history-open")==="1",output=owner.querySelector("[data-bm-captcha-history-text]");owner.querySelector("[data-bm-captcha-log-text]").hidden=opened;owner.querySelector("[data-bm-captcha-history-toggle]").textContent=opened?"返回实时日志":"历史日志";if(output){output.hidden=!opened;if(opened){if(fullText===null)fullText=formatLogEntries(history,true)||"本站暂无历史日志。";if(output.textContent!==fullText)output.textContent=fullText}}var note=owner.querySelector("[data-bm-captcha-history-note]"),statusText=historyStatusText();if(note&¬e.textContent!==statusText)note.textContent=statusText});installHistoryEntry()}function addLogs(container){var details=document.createElement("details");details.setAttribute("data-bm-captcha-log-panel","");details.open=true;details.style.cssText="margin:10px 0 0;border-top:1px solid #c4d3cb;padding-top:7px;";var heading=document.createElement("summary");heading.textContent="运行日志";heading.style.cssText="font-size:13px;font-weight:600;cursor:pointer;";details.appendChild(heading);var output=document.createElement("pre");output.setAttribute("data-bm-captcha-log-text","");output.setAttribute("aria-live","polite");output.style.cssText="max-height:160px;overflow:auto;margin:8px 0;padding:8px;border-radius:6px;"+"background:rgba(80,110,90,.07);font:11px/1.6 monospace;white-space:pre-wrap;overflow-wrap:anywhere;";details.appendChild(output);button("复制日志","data-bm-captcha-copy-log",details);var archiveActions=document.createElement("div");archiveActions.style.cssText="display:flex;flex-wrap:wrap;gap:6px;margin-top:7px;";button("历史日志","data-bm-captcha-history-toggle",archiveActions);button("复制全部历史","data-bm-captcha-copy-history",archiveActions);button("导出历史","data-bm-captcha-export-history",archiveActions);button("清空历史","data-bm-captcha-clear-history",archiveActions);details.appendChild(archiveActions);var archiveNote=document.createElement("div");archiveNote.setAttribute("data-bm-captcha-history-note","");archiveNote.style.cssText="font-size:11px;line-height:1.5;margin-top:7px;opacity:.8;";details.appendChild(archiveNote);var message=document.createElement("div");message.setAttribute("data-bm-captcha-copy-status","");message.style.cssText="font-size:12px;margin-top:5px;";details.appendChild(message);container.appendChild(details);renderLogs()}function copyLog(control){var owner=control&&control.closest("[data-bm-captcha-log-panel]");if(!owner)return;var full=control.hasAttribute("data-bm-captcha-copy-history");var text="在线验证 v1.9.18 · "+location.hostname+"\n"+diagnosticText()+(full?" · 本站最近 7 天历史\n"+historyText():"\n"+logText()),message=owner.querySelector("[data-bm-captcha-copy-status]");function fallback(){var field=owner.querySelector("[data-bm-captcha-log-copy-text]");if(!field){field=document.createElement("textarea");field.setAttribute("data-bm-captcha-log-copy-text","");field.readOnly=true;field.style.cssText="display:block;width:100%;height:110px;box-sizing:border-box;font:11px/1.5 monospace;";owner.appendChild(field)}field.value=text;field.focus();field.select();message.textContent="复制接口不可用,可长按下方日志文本复制。"}try{if(!navigator.clipboard||typeof navigator.clipboard.writeText!=="function"){fallback();return}Promise.resolve(navigator.clipboard.writeText(text)).then(function(){message.textContent="已复制日志,不含密钥、验证码图片和会话信息。"},fallback)}catch(_){fallback()}}function armElapsed(){clearTimeout(elapsedTimer);elapsedTimer=setTimeout(function tick(){elapsedTimer=null;if(!busy||stopped||submitted||!phasePending)return;var checkpoints=[5e3,15e3,3e4],waited=elapsedMs();while(waitCheckpoint=checkpoints[waitCheckpoint]){var checkpointSeconds=checkpoints[waitCheckpoint++]/1e3;logEvent("waiting","本阶段已等待 "+checkpointSeconds+" 秒;"+(phaseHttp?"已收到 HTTP 响应头,正在等待回应正文。":phaseNativeStarted?"原生接口已报告启动,尚未收到 HTTP 状态。":phaseCallback?"请求桥已有回调,尚未收到 HTTP 状态。":"尚未收到请求桥回调,无法确认请求是否启动。"))}updateDiagnostics();if(busy&&!stopped&&!submitted&&phasePending)elapsedTimer=setTimeout(tick,1e3)},1e3)}function browserHandlerInfo(){var name="",version="";try{var info=typeof GM_info!=="undefined"&&GM_info;var label=info&&info.scriptHandler;if(typeof label==="string"){if(/^via$/i.test(label))name="Via";else if(/^xbrowser$/i.test(label))name="XBrowser";else if(/^tampermonkey$/i.test(label))name="Tampermonkey";else if(/^violentmonkey$/i.test(label))name="Violentmonkey"}if(name&&typeof info.version==="string"&&/^\d{1,3}\.\d{1,3}(?:\.\d{1,3})?$/.test(info.version))version=info.version}catch(_){}if(!name&&safeViaRuntime())name="Via";return{name:name||"未识别",version:version}}function requestCapabilities(){var names=[];if(typeof GM_xmlhttpRequest==="function")names.push("GM_legacy");if(typeof GM!=="undefined"&&GM&&typeof GM.xmlHttpRequest==="function")names.push("GM_promise");if(typeof fetch==="function"&&typeof AbortController==="function")names.push("fetch");return names.join("+")||"不可用"}function diagnostics(){var bridge=requestAdapter(),handler=browserHandlerInfo();return{nativeStarted:phaseNativeStarted,readyState:phaseReadyState,callbackType:phaseCallbackType,browserHandler:handler.name,browserHandlerVersion:handler.version,version:"1.9.18",host:location.hostname,flow:flowNumber,storage:configSource,storageDetected:adapters().filter(function(adapter){return adapter.shared}).map(function(adapter){return adapter.id}).join("+")||"localStorage",requestDetected:requestCapabilities(),transport:phaseStarted?phaseTransport:bridge?bridge.id:typeof fetch==="function"&&typeof AbortController==="function"?"fetch":"不可用",enabled:!!(cachedConfig&&cachedConfig.enabled),provider:"baidu",keyPresent:!!(cachedConfig&&cachedConfig.key&&cachedConfig.secret),preset:configSource==="个人版源码预置",ocrType:cachedConfig&&cachedConfig.ocrType||"numbers",readVerified:readVerified,saveVerified:saveVerified,sharedVerified:sharedVerified,saveAttempted:saveAttempted,stage:requestStage,error:requestError||storageError||"",requestMethod:cachedConfig&&cachedConfig.transportMethod||"auto",viaDetected:safeViaRuntime(),activeTransport:phaseTransport,elapsedMs:elapsedMs(),transportInvoked:phaseStarted,requestPending:phasePending,callbackSeen:phaseCallback,httpSeen:phaseHttp,logStorage:historyStorage,logCount:history.length,logRetentionDays:7,logHost:location.hostname,captchaRound:budgetSnapshot&&budgetSnapshot.round||1,captchaRoundLimit:maxRounds,captchaAttempt:budgetSnapshot&&budgetSnapshot.attempts||0,captchaAttemptLimit:maxAttempts,captchaTotalAttempts:budgetSnapshot&&budgetSnapshot.total||0,captchaCyclePhase:budgetSnapshot&&budgetSnapshot.phase||"ready",automaticRetryPending:!!retryAction}}function diagnosticText(){var value=diagnostics();return"v"+value.version+" · "+value.host+(value.flow?" · 流程 #"+value.flow:"")+" · 百度智能云 · 脚本接口 "+value.browserHandler+(value.browserHandlerVersion?"/"+value.browserHandlerVersion:"")+" · 存储桥 "+value.storageDetected+" · 请求接口 "+value.requestDetected+" · 通道 "+value.transport+" · 存储 "+value.storage+" · 启用 "+(value.enabled?"是":"否")+" · 密钥 "+(value.keyPresent?"有":"无")+" · 回读 "+(value.readVerified?"已验证":"未验证")+" · 保存 "+(value.saveVerified?"已验证":value.saveAttempted?"未验证":"本页未写入")+" · 日志 "+value.logStorage+" · 历史 "+value.logCount+" 条"+(isCaptchaPage?" · 验证 "+value.captchaRound+"/3 轮 "+value.captchaAttempt+"/2 次 · 总尝试 "+value.captchaTotalAttempts+"/6":"")+" · 原生启动 "+(value.nativeStarted?"已收到":"未确认")+" · 请求状态 "+(value.readyState>=0?value.readyState:"未收到")+" · 阶段 "+value.stage+" · "+(value.elapsedMs/1e3).toFixed(1)+"秒"+(value.requestPending&&value.transportInvoked&&!value.httpSeen?value.transport==="fetch"?" · 尚未收到响应头":value.callbackSeen?value.nativeStarted?" · 原生已启动,尚无 HTTP 结果":" · 桥已回调,尚无 HTTP 结果":" · 尚无回调":"")+(value.error?" · "+value.error:"")}function updateDiagnostics(){var text=diagnosticText();document.querySelectorAll("[data-bm-captcha-diagnostics]").forEach(function(node){node.textContent=text});if(status)status.textContent=lastMessage+(busy&&requestStartedAt?"(已等待 "+(elapsedMs()/1e3).toFixed(1)+" 秒)":"");renderLogs();if(configPanel){var note=configPanel.querySelector("[data-bm-captcha-storage-note]");if(note)note.textContent=configSource==="个人版源码预置"?"个人版已直接预置百度凭据,无需填写,不依赖密钥保存成功。":sharedVerified?"已核对油猴私有存储;两站可共用此配置。本站还保留恢复镜像,阅读备份不含密钥。":"当前配置仅在本站确认可用;菠萝猫与搜索站的域名存储分开。使用私用预置版本时两站均可初始化,阅读备份不含密钥。"}}function addDiagnostics(container){var node=document.createElement("div");node.setAttribute("data-bm-captcha-diagnostics","");node.style.cssText="font-size:11px;line-height:1.45;opacity:.75;overflow-wrap:anywhere;margin:8px 0;";container.appendChild(node);updateDiagnostics()}function readConfig(){if(configReadPending)return configReadPending;configReadPending=readConfigOnce().finally(function(){configReadPending=null});return configReadPending}async function readConfigOnce(){if(!allowed)return emptyConfig();var list=adapters(),selected=cachedConfig,selectedSource=selected?configSource:"无配置";var selectedRead=selected?readVerified:false,foundShared=selected?sharedVerified:false;storageError="";var reads=await Promise.all(list.map(function(adapter){return bounded(adapter.get,"STORE_READ_"+adapter.id).then(parseConfig,function(){storageError="STORE_READ_"+adapter.id;return null})}));for(var index=0;indexselected.updatedAt){selected=parsed;selectedSource=adapter.id;selectedRead=true;foundShared=adapter.shared}else if(sameConfig(parsed,selected)){selectedRead=true;if(adapter.shared){selectedSource=adapter.id;foundShared=true}else if(!foundShared)selectedSource=adapter.id}}if(cachedConfig&&(!selected||cachedConfig.updatedAt>selected.updatedAt)){selected=cachedConfig;selectedSource=configSource;selectedRead=readVerified;foundShared=sharedVerified}if(!selected&&validKey(defaultPrivateBaiduApiKey)&&validKey(defaultPrivateBaiduSecretKey)){selected={version:3,provider:"baidu",enabled:true,key:defaultPrivateBaiduApiKey.trim(),secret:defaultPrivateBaiduSecretKey.trim(),deleted:false,updatedAt:0,transportMethod:"auto",ocrType:"numbers"};selectedSource="个人版源码预置";selectedRead=false;foundShared=false}cachedConfig=selected;configSource=selected?selectedSource:"无配置";readVerified=selectedRead;sharedVerified=foundShared;if(/^(?:初始化|读取配置|配置未启用或缺少密钥)$/.test(requestStage)){requestStage=selected&&selected.enabled&&selected.key&&selected.secret?"百度配置已就绪":"配置未启用或缺少密钥"}updateDiagnostics();return selected||emptyConfig()}async function verifyWrite(adapter,value){await bounded(function(){return adapter.set(JSON.stringify(value))},"STORE_WRITE_"+adapter.id);for(var attempt=0;attempt<3;attempt++){if(attempt)await new Promise(function(resolve){setTimeout(resolve,120)});var stored=parseConfig(await bounded(adapter.get,"STORE_VERIFY_"+adapter.id));if(sameConfig(stored,value))return true}return false}async function writeConfig(value){var checked=parseConfig(value);if(!checked)throw new Error("invalid-config");invalidateBaiduToken();saveAttempted=true;cachedConfig=checked;configSource="本页内存";readVerified=false;saveVerified=false;sharedVerified=false;var list=adapters(),goodShared="",goodLocal=false;storageError="";var results=await Promise.all(list.map(function(adapter){return verifyWrite(adapter,checked).then(function(verified){return{adapter:adapter,verified:verified}},function(){return{adapter:adapter,verified:false}})}));results.forEach(function(result){if(result.verified){if(result.adapter.shared)goodShared=goodShared||result.adapter.id;else goodLocal=true}else storageError="STORE_VERIFY_"+result.adapter.id});configSource=goodShared||(goodLocal?"localStorage":"本页内存");saveVerified=readVerified=!!(goodShared||goodLocal);sharedVerified=!!goodShared;if(goodShared)storageError="";updateDiagnostics();logEvent("config_saved",saveVerified?"配置写入后回读核对完成。":"配置只在本页内存确认,持久保存未核实。");return{verified:saveVerified,shared:sharedVerified,adapter:configSource}}function hash(value){var result=2166136261;for(var index=0;index>>0).toString(16)}function freshBudget(){return{v:2,at:Date.now(),cycle:newHistoryWriter(),revision:0,round:1,attempts:0,total:0,calls:0,submits:0,phase:"ready",paused:false,reason:""}}function validBudget(value){return!!value&&value.v===2&&Number.isFinite(value.at)&&value.at>=0&&value.at<=Date.now()&&typeof value.cycle==="string"&&/^[0-9a-f]{32}$/.test(value.cycle)&&Number.isInteger(value.revision)&&value.revision>=0&&value.revision<=1e6&&Number.isInteger(value.round)&&value.round>=1&&value.round<=maxRounds&&Number.isInteger(value.attempts)&&value.attempts>=0&&value.attempts<=maxAttempts&&value.total===(value.round-1)*maxAttempts+value.attempts&&value.calls===value.total&&Number.isInteger(value.submits)&&value.submits>=0&&value.submits<=value.total&&["ready","attempting","submitted","refreshing","stopped"].indexOf(value.phase)>=0&&typeof value.paused==="boolean"&&(value.phase!=="stopped"||value.paused)&&typeof value.reason==="string"&&/^(?:[A-Z0-9_]{1,64}|[a-z][a-z0-9-]{0,63}|)$/.test(value.reason)}function readBudget(){try{var raw=sessionStorage.getItem(budgetKey);if(raw===null){budgetSnapshot=freshBudget();return budgetSnapshot}if(typeof raw!=="string"||raw.length>4096)throw new Error("invalid-budget");var value=JSON.parse(raw);if(validBudget(value)){budgetSnapshot=value;return value}if(value&&typeof value.v==="undefined"&&Number.isFinite(value.at)&&value.at>=0&&value.at<=Date.now()&&Number.isInteger(value.calls)&&value.calls>=0&&value.calls<=maxAttempts&&Number.isInteger(value.submits)&&value.submits>=0&&value.submits<=maxAttempts&&Array.isArray(value.images)&&value.images.length<=maxAttempts&&value.images.every(function(item){return typeof item==="string"&&/^[0-9a-f]{1,8}$/.test(item)})&&typeof value.paused==="boolean"){var upgraded=freshBudget();upgraded.attempts=upgraded.total=upgraded.calls=Math.max(value.calls,value.submits);upgraded.submits=value.submits;upgraded.paused=value.paused;upgraded.phase=value.paused?"stopped":value.submits?"submitted":"ready";upgraded.reason=value.paused?"LEGACY_PAUSED":"";writeBudget(upgraded);return upgraded}throw new Error("invalid-budget")}catch(_){throw new Error("BUDGET_STORE_WRITE_FAILED")}}function writeBudget(value){try{value.at=Date.now();value.revision++;if(!validBudget(value))throw new Error("invalid-budget");var text=JSON.stringify(value);sessionStorage.setItem(budgetKey,text);if(sessionStorage.getItem(budgetKey)!==text)throw new Error("unverified-budget");budgetSnapshot=value}catch(_){throw new Error("BUDGET_STORE_WRITE_FAILED")}}function clearRetryTimer(clearAction){clearTimeout(retryTimer);retryTimer=null;retryDueAt=0;if(clearAction)retryAction=""}function storageBudgetStop(){stopped=true;sequence++;busy=false;submitted=false;clearRetryTimer(true);requestError="BUDGET_STORE_WRITE_FAILED";requestStage="尝试预算未核实";logEvent("budget_blocked",budgetMessage(requestError));setStatus(budgetMessage(requestError))}function pauseBudget(reason){var budget=readBudget();budget.paused=true;budget.phase="stopped";budget.reason=reason||"MANUAL_STOP";writeBudget(budget)}function reserveAttempt(){var budget=readBudget();if(budget.paused||budget.phase==="stopped")throw new Error("BUDGET_PAUSED");if(budget.attempts>=maxAttempts||budget.total>=maxAttempts*maxRounds)throw new Error("BUDGET_CALL_LIMIT");if(budget.phase!=="ready")throw new Error("BUDGET_PAUSED");budget.attempts++;budget.total++;budget.calls=budget.total;budget.phase="attempting";budget.reason="";writeBudget(budget);logEvent("attempt_reserved","第 "+budget.round+" / 3 轮,第 "+budget.attempts+" / 2 次尝试;次数已写入并回读核对。");return{cycle:budget.cycle,total:budget.total}}function armRetry(){clearRetryTimer(false);if(!retryAction||stopped||busy||submitted||document.hidden)return;var expected=sequence,action=retryAction;retryDueAt=Date.now()+retryDelay;retryTimer=setTimeout(function(){retryTimer=null;if(stopped||sequence!==expected||retryAction!==action||busy||submitted)return;if(document.hidden){retryDueAt=0;return}if(Date.now()-retryDueAt>Math.max(1e3,retryDelay)){armRetry();return}retryAction="";retryDueAt=0;if(action==="refresh")automaticRefresh();else begin()},retryDelay)}function scheduleRetry(action){retryAction=action;armRetry()}function automaticRefresh(){var current=findChallenge();if(current&¤t.code.value.trim()&¤t.code.value!==ownedCode){stop();setStatus("检测到已填写的验证码,已停止自动换图并保留你的输入;请在原表单提交。");return}if(stopped||busy||submitted||document.hidden){if(!stopped)scheduleRetry("refresh");return}try{var budget=readBudget();if(budget.paused||budget.phase!=="refreshing"||budget.round>maxRounds)throw new Error("BUDGET_PAUSED");writeBudget(budget)}catch(error){if(error.message==="BUDGET_STORE_WRITE_FAILED")storageBudgetStop();else terminalStop(error.message);return}stopped=true;sequence++;clearRetryTimer(true);logEvent("refresh","本次尝试未通过,正在刷新原验证码网页获取新图片。");requestStage="失败后换图";setStatus("正在刷新验证码网页,获取新图片后继续自动验证。");try{location.reload()}catch(_){refreshFailed();return}navigationTimer=setTimeout(refreshFailed,6e3)}function refreshFailed(){requestError="PAGE_REFRESH_FAILED";requestStage="保留手动验证";try{pauseBudget("PAGE_REFRESH_FAILED")}catch(_){storageBudgetStop();return}logEvent("refresh_failed","浏览器未完成验证码页刷新,自动流程已停止,可手动验证或重新自动验证。");setStatus("验证码页刷新未完成,自动流程已停止,可手动验证或重新自动验证。")}function terminalStop(reason){clearRetryTimer(true);stopped=true;busy=false;submitted=false;try{pauseBudget(reason)}catch(_){storageBudgetStop();return}requestStage="自动流程已停止";logEvent("cycle_stopped","本次自动流程已停止;停止状态会在验证码页重载后保留。");setStatus(lastMessage||"本次自动流程已停止,可手动验证或重新自动验证。")}function retryableFailure(code){return/^(?:not-six-digits|stale-image|BAIDU_OCR_216630|timeout|network|response-format|RESPONSE_BODY_FAILED|GM_REQUEST_[A-Z_]+|GM_NO_CALLBACK_TIMEOUT|FETCH_NETWORK_OR_CORS|SITE_WAF_RETURN|INTERRUPTED_ATTEMPT|http-(?:408|5\d\d))$/.test(code)}function failedAttempt(reason){try{var budget=readBudget();if(budget.paused||budget.phase==="stopped"){stopped=true;setStatus(budgetMessage("BUDGET_PAUSED"));return}budget.reason=reason;submitted=false;if(ownedCode&&challenge&&challenge.code.value===ownedCode)challenge.code.value="";ownedCode="";logEvent("attempt_failed","第 "+budget.round+" / 3 轮,第 "+budget.attempts+" / 2 次尝试未通过。");if(budget.attempts>=maxAttempts){if(budget.round>=maxRounds){budget.paused=true;budget.phase="stopped";budget.reason="ROUND_LIMIT";writeBudget(budget);stopped=true;clearRetryTimer(true);requestError="BUDGET_ROUND_LIMIT";requestStage="三轮尝试结束";logEvent("cycle_stopped","三轮共 6 次尝试仍未通过,自动识别与刷新已停止。");setStatus(budgetMessage("BUDGET_ROUND_LIMIT"));return}budget.round++;budget.attempts=0;budget.phase="refreshing";writeBudget(budget);logEvent("round_advance","前一轮两次均失败,下一轮为第 "+budget.round+" / 3 轮;刷新状态已保存。");requestStage="等待换图";setStatus("本轮两次失败,即将换图并开始第 "+budget.round+" / 3 轮。");scheduleRetry("refresh")}else{budget.phase="refreshing";writeBudget(budget);requestStage="等待换图";setStatus("本次未通过,即将刷新换图后重试;仍可停止或手动填写。");scheduleRetry("refresh")}}catch(_){storageBudgetStop()}}function resumeBudget(){if(budgetChecked)return false;budgetChecked=true;try{var budget=readBudget();if(budget.paused||budget.phase==="stopped"){stopped=true;requestStage=budget.reason==="ROUND_LIMIT"?"三轮尝试结束":"自动流程已停止";if(budget.reason&&/^(?:BAIDU_|http-)/.test(budget.reason))requestError=budget.reason;else if(budget.reason==="ROUND_LIMIT")requestError="BUDGET_ROUND_LIMIT";setStatus(budget.reason==="ROUND_LIMIT"?budgetMessage("BUDGET_ROUND_LIMIT"):"本次自动流程已停止,重载后仍保留;可手动验证或重新自动验证。");return true}if(budget.phase==="submitted"||budget.phase==="attempting"){logEvent("navigation_rejected",budget.phase==="submitted"?"网站提交后仍返回验证码页,本次提交未通过,继续沿用原尝试预算。":"上一尝试未返回目标页,已消耗次数继续保留。");failedAttempt(budget.phase==="submitted"?"SITE_WAF_RETURN":"INTERRUPTED_ATTEMPT");return true}if(budget.phase==="refreshing"){budget.phase="ready";writeBudget(budget);logEvent("round_ready","刷新后的验证码网页已就绪,继续第 "+budget.round+" / 3 轮。");setStatus("第 "+budget.round+" / 3 轮验证码网页已就绪,正在继续识别。");return false}if(budget.attempts>=maxAttempts){failedAttempt("INTERRUPTED_ATTEMPT");return true}return false}catch(_){storageBudgetStop();return true}}function findChallenge(){if(!isCaptchaPage)return null;var code=document.querySelector('input[name="GOEDGE_WAF_CAPTCHA_CODE"]');var id=document.querySelector('input[name="GOEDGE_WAF_CAPTCHA_ID"]');var image=document.querySelector("#ui-captcha-image");var form=code&&code.form;if(!code||!id||!image||!form||id.form!==form||id.type!=="hidden"||["text","tel","number"].indexOf(code.type)<0||!id.value||id.value.length>512||String(form.method).toLowerCase()!=="post"||!image.isConnected||!form.isConnected)return null;try{var action=new URL(form.getAttribute("action")||location.href,location.href);var imageUrl=new URL(image.currentSrc||image.src,location.href);if(action.origin!==location.origin||action.pathname!==captchaPath||action.username||action.password||imageUrl.origin!==location.origin||imageUrl.username||imageUrl.password||!(imageUrl.pathname===captchaPath||imageUrl.pathname.indexOf(captchaPath+"/")===0))return null;return{form:form,code:code,id:id,image:image,action:action.href,imageUrl:imageUrl.href}}catch(_){return null}}function snapshotImage(current){var image=current.image;if(!image.complete||!image.naturalWidth||!image.naturalHeight)throw new Error("image-pending");if(image.naturalWidth>1e3||image.naturalHeight>500)throw new Error("image-size");var canvas=document.createElement("canvas");var scale=Math.min(2,400/image.naturalWidth,200/image.naturalHeight);canvas.width=Math.max(1,Math.round(image.naturalWidth*scale));canvas.height=Math.max(1,Math.round(image.naturalHeight*scale));var context=canvas.getContext("2d");context.fillStyle="#fff";context.fillRect(0,0,canvas.width,canvas.height);context.imageSmoothingEnabled=true;context.drawImage(image,0,0,canvas.width,canvas.height);var pixels=canvas.toDataURL("image/png");if(!/^data:image\/png;base64,/.test(pixels)||pixels.length>1024*1024)throw new Error("image-size");return{form:current.form,code:current.code,id:current.id,image:image,idValue:current.id.value,codeValue:current.code.value,action:current.action,imageUrl:current.imageUrl,pixels:pixels,fingerprint:hash(current.id.value+"\n"+current.imageUrl+"\n"+pixels)}}function sameImage(before){var current=findChallenge();if(!current||current.form!==before.form||current.image!==before.image||current.code!==before.code||current.id!==before.id||current.id.value!==before.idValue||current.code.value!==before.codeValue||current.action!==before.action||current.imageUrl!==before.imageUrl)return false;try{return snapshotImage(current).pixels===before.pixels}catch(_){return false}}function setStatus(message){lastMessage=message||"";if(status)status.textContent=lastMessage;if(card){var stopButton=card.querySelector("[data-bm-captcha-stop]");if(stopButton)stopButton.disabled=stopped||!busy&&!submitted&&!retryAction}updateDiagnostics()}function button(label,attr,container){var node=document.createElement("button");node.type="button";node.textContent=label;node.setAttribute(attr,"");node.__bmCaptchaControlOwner=controlOwner;node.style.cssText="font:inherit;font-size:14px;padding:7px 11px;border:1px solid #9aafa5;border-radius:7px;background:#fff;color:#284f42;cursor:pointer;";if(container)container.appendChild(node);return node}function installCard(current){if(card)return;card=document.createElement("section");card.setAttribute("data-bm-captcha-panel","");card.__bmCaptchaControlOwner=controlOwner;card.style.cssText="box-sizing:border-box;width:min(100%,440px);margin:12px auto;padding:12px;border:1px solid #b3c9bd;border-radius:10px;background:#f2f8f4;color:#284336;font:15px/1.5 sans-serif;text-align:left;";var title=document.createElement("strong");title.textContent="在线验证码识别";card.appendChild(title);status=document.createElement("div");status.setAttribute("data-bm-captcha-status","");status.setAttribute("role","status");status.style.cssText="margin:5px 0 9px;overflow-wrap:anywhere;";card.appendChild(status);var actions=document.createElement("div");actions.style.cssText="display:flex;flex-wrap:wrap;gap:7px;";card.appendChild(actions);button("停止","data-bm-captcha-stop",actions);button("重新自动验证","data-bm-captcha-retry",actions);button("刷新验证码页","data-bm-captcha-refresh",actions);button("API 设置","data-bm-captcha-config",actions);addDiagnostics(card);addLogs(card);current.form.parentNode.insertBefore(card,current.form);logEvent("card","已找到站点验证码表单,识别状态与日志已显示。");setStatus(lastMessage||"正在检查验证码图片。")}function closeConfig(){if(apiProbePending)logEvent("api_probe_cancel","设置已关闭,连接测试已取消。");cancelApiProbe();if(configPanel)configPanel.remove();configPanel=null}function openConfig(){if(!allowed||!document.body)return;if(configPanel){configPanel.querySelector('input[type="password"]').focus();return}var panel=document.createElement("section");panel.setAttribute("data-bm-captcha-config-panel","");panel.__bmCaptchaControlOwner=controlOwner;panel.setAttribute("role","dialog");panel.setAttribute("aria-label","在线验证码识别设置");panel.style.cssText="position:fixed;inset:0;z-index:2147483647;background:rgba(0,0,0,.35);display:flex;align-items:center;justify-content:center;padding:16px;box-sizing:border-box;font:15px/1.5 sans-serif;color:#283a30;";var box=document.createElement("div");box.style.cssText="width:100%;max-width:400px;max-height:85vh;overflow:auto;border-radius:12px;background:#fafaf6;padding:18px;box-sizing:border-box;";panel.appendChild(box);var title=document.createElement("strong");title.textContent="百度智能云 · 在线识别";box.appendChild(title);var note=document.createElement("p");note.setAttribute("data-bm-captcha-storage-note","");note.textContent="正在核对当前浏览器存储能力;密钥不会进入阅读备份。";note.style.cssText="font-size:13px;margin:9px 0;";box.appendChild(note);var enableLabel=document.createElement("label");enableLabel.style.cssText="display:block;margin:9px 0;";var enable=document.createElement("input");enable.type="checkbox";enable.setAttribute("data-bm-captcha-enabled","");enable.checked=true;var enableTouched=false;enable.addEventListener("change",function(){enableTouched=true});enableLabel.appendChild(enable);enableLabel.appendChild(document.createTextNode(" 遇到这两个站点的验证码时自动识别并验证"));box.appendChild(enableLabel);var label=document.createElement("label");label.textContent="API Key";label.style.display="block";box.appendChild(label);var input=document.createElement("input");input.type="password";input.autocomplete="off";input.spellcheck=false;input.setAttribute("data-bm-captcha-key","");input.setAttribute("aria-label","百度 API Key");input.placeholder="百度应用的 API Key";input.style.cssText="box-sizing:border-box;width:100%;padding:10px;font:inherit;margin:5px 0 10px;";box.appendChild(input);var secretLabel=document.createElement("label");secretLabel.textContent="Secret Key";secretLabel.style.display="block";box.appendChild(secretLabel);var secret=document.createElement("input");secret.type="password";secret.autocomplete="off";secret.spellcheck=false;secret.setAttribute("data-bm-captcha-secret","");secret.setAttribute("aria-label","百度 Secret Key");secret.placeholder="同一应用的 Secret Key";secret.style.cssText=input.style.cssText;box.appendChild(secret);var typeLabel=document.createElement("label");typeLabel.textContent="识别接口";typeLabel.style.display="block";box.appendChild(typeLabel);var ocrType=document.createElement("select");ocrType.setAttribute("data-bm-captcha-ocr-type","");ocrType.style.cssText="display:block;box-sizing:border-box;width:100%;min-height:44px;padding:8px;font:inherit;margin:5px 0 10px;";[["numbers","数字识别(默认)"],["accurate_basic","通用文字高精度"]].forEach(function(item){var option=document.createElement("option");option.value=item[0];option.textContent=item[1];ocrType.appendChild(option)});box.appendChild(ocrType);var typeTouched=false;ocrType.addEventListener("change",function(){typeTouched=true});var methodLabel=document.createElement("label");methodLabel.textContent="请求方式";methodLabel.style.cssText="display:block;font-size:13px;";box.appendChild(methodLabel);var method=document.createElement("select");method.setAttribute("data-bm-captcha-transport","");method.setAttribute("aria-label","OCR 请求方式");method.style.cssText="display:block;box-sizing:border-box;width:100%;min-height:44px;padding:8px;font:inherit;margin:5px 0 10px;";[["auto","自动(推荐)"],["fetch","网页兼容"],["gm","油猴接口"]].forEach(function(item){var option=document.createElement("option");option.value=item[0];option.textContent=item[1];method.appendChild(option)});var methodTouched=false;method.addEventListener("change",function(){methodTouched=true});box.appendChild(method);var channelNote=document.createElement("p");channelNote.setAttribute("data-bm-captcha-transport-note","");channelNote.style.cssText="font-size:12px;line-height:1.5;margin:0 0 10px;";channelNote.textContent="自动优先使用 GM 跨域请求接口。网页兼容使用 fetch,需要百度接口允许跨域;仅在接口提前明确失败时顺序切换一次。鉴权与识别共用总期限,不并发请求;只调用选定的一个识别接口。";box.appendChild(channelNote);var message=document.createElement("div");message.setAttribute("data-bm-captcha-config-status","");message.style.cssText="font-size:13px;margin-bottom:9px;";box.appendChild(message);var actions=document.createElement("div");actions.style.cssText="display:flex;gap:8px;flex-wrap:wrap;";box.appendChild(actions);var probeBox=document.createElement("div");probeBox.style.cssText="margin:10px 0;padding:10px 0;border-top:1px solid #c4d3cb;";var probeButton=button("测试连接(不识别)","data-bm-captcha-test-connection",probeBox);if(probeButton)probeButton.style.width="100%";var probeResult=document.createElement("div");probeResult.setAttribute("data-bm-captcha-probe-result","");probeResult.setAttribute("role","status");probeResult.style.cssText="font-size:13px;line-height:1.6;margin:6px 0;overflow-wrap:anywhere;";probeResult.textContent="只测试已保存密钥和请求通道,不发送图片。";probeBox.appendChild(probeResult);box.insertBefore(probeBox,actions);button("保存","data-bm-captcha-save",actions);button("停用并清除配置","data-bm-captcha-delete",actions);button("关闭","data-bm-captcha-config-close",actions);addDiagnostics(box);addLogs(box);configPanel=panel;document.body.appendChild(panel);requestStage="读取配置";updateDiagnostics();readConfig().then(function(config){if(configPanel!==panel)return;if(!enableTouched)enable.checked=config.key?config.enabled:true;if(!methodTouched)method.value=config.transportMethod||"auto";if(!typeTouched)ocrType.value=config.ocrType||"numbers";secret.placeholder=config.secret?"已预置或已保存;留空保留":"同一应用的 Secret Key";input.placeholder=config.key?"已预置或已保存;留空保留":"百度应用的 API Key";message.textContent=config.key&&config.secret?"完整百度凭据已就绪,无需重新填写。":"请填写同一百度应用的 API Key 和 Secret Key。";logEvent("config_read",config.enabled&&config.key?"配置已读取,自动识别已启用,密钥存在。":"配置已读取,自动识别未启用或密钥缺失。");updateDiagnostics()})}function saveConfig(remove){if(!configPanel||savingConfig)return Promise.resolve();var panel=configPanel,field=panel.querySelector("[data-bm-captcha-key]"),secretField=panel.querySelector("[data-bm-captcha-secret]");var enable=panel.querySelector("[data-bm-captcha-enabled]"),method=panel.querySelector("[data-bm-captcha-transport]");var type=panel.querySelector("[data-bm-captcha-ocr-type]"),message=panel.querySelector("[data-bm-captcha-config-status]");var entered=field.value.trim(),enteredSecret=secretField.value.trim();if(!remove&&(entered&&!validKey(entered)||enteredSecret&&!validKey(enteredSecret))){message.textContent="百度凭据格式不正确,请检查完整值。";return Promise.resolve()}savingConfig=true;requestStage="保存并核对回读";requestError="";updateDiagnostics();return readConfig().then(function(existing){var stamp=Math.max(Date.now(),(cachedConfig&&cachedConfig.updatedAt||0)+1);var next=remove?{version:3,provider:"baidu",enabled:false,key:"",secret:"",deleted:true,updatedAt:stamp,transportMethod:"auto",ocrType:"numbers"}:{version:3,provider:"baidu",enabled:enable.checked,key:entered||existing.key,secret:enteredSecret||existing.secret,deleted:false,updatedAt:stamp,transportMethod:method.value||"auto",ocrType:type.value||"numbers"};if(!!next.key!==!!next.secret||next.enabled&&(!next.key||!next.secret))throw new Error("missing-key");return writeConfig(next).then(function(result){if(configPanel!==panel)return;field.value="";secretField.value="";enable.checked=next.enabled;requestStage=result.verified?"保存回读已验证":"仅本页配置未持久保存";message.textContent=result.verified?remove?"已停用并清除配置,源码预置不会自动重新启用。":result.shared?"已保存并回读验证,两站共用配置。":"已在本站保存并回读验证。":"当前修改仅本页可用,浏览器未确认持久保存;个人版源码预置仍可在新页面直接使用。";updateDiagnostics();if(remove||!next.enabled)stop();else if(isCaptchaPage&&result.verified){closeConfig();return retry()}else{if(isCaptchaPage)setStatus("当前配置可用,关闭设置后可主动重试。")}})}).catch(function(error){requestError=error.message==="missing-key"?"CONFIG_KEY_MISSING":"CONFIG_SAVE_FAILED";if(configPanel===panel)message.textContent=error.message==="missing-key"?"需要同一应用的完整 API Key 和 Secret Key。":"保存未完成,请检查浏览器存储权限。";updateDiagnostics()}).finally(function(){savingConfig=false})}function stop(){if(apiProbePending)logEvent("api_probe_cancel","按你的操作停止连接测试。");cancelApiProbe();lastElapsedMs=elapsedMs();clearTimeout(elapsedTimer);elapsedTimer=null;stopped=true;sequence++;busy=false;phasePending=false;requestStage=submitted?"已提交后停止":"已停止";logEvent("stop",submitted?"已提交原站表单;停止后续自动识别。":"已停止当前识别,原表单仍可手动填写。");clearTimeout(navigationTimer);clearRetryTimer(true);if(transport){try{transport.abort()}catch(_){}transport=null}try{pauseBudget("MANUAL_STOP")}catch(_){requestError="BUDGET_STORE_WRITE_FAILED"}setStatus(submitted?"验证码已提交;已停止后续自动识别,等待网站返回。":"自动识别已停止,可以直接在原表单手动验证。")}var baiduAuthCache=null;var apiProbePending=false,apiProbePromise=null,apiProbeCancel=null,apiProbeSequence=0;function invalidateBaiduToken(){baiduAuthCache=null}function baiduError(code){if(code==="BAIDU_AUTH_KEY")return"百度拒绝了 API Key,请核对同一应用中的凭据。";if(code==="BAIDU_AUTH_SECRET")return"百度拒绝了 Secret Key,请检查是否完整且与 API Key 配对。";if(code==="BAIDU_AUTH_OTHER")return"百度鉴权未返回有效令牌,未发送图片。";if(code==="BAIDU_OCR_6")return"百度错误码 6:应用没有此识别接口的调用权限。";if(code==="BAIDU_OCR_17"||code==="BAIDU_OCR_19")return"百度错误码 "+code.slice(10)+":调用额度受限,请检查已开通资源。";if(code==="BAIDU_OCR_18")return"百度错误码 18:请求频率受限,本次不自动重发。";if(code==="BAIDU_OCR_110"||code==="BAIDU_OCR_111")return"百度令牌仍无效或已过期,本次不再重复刷新。";if(code==="BAIDU_OCR_216100")return"百度报告识别请求参数错误。";if(/^BAIDU_OCR_21620[0-2]$/.test(code))return"百度报告图片为空、格式或尺寸不符合要求。";if(code==="BAIDU_OCR_216630")return"百度未能识别这张图片,可手动填写或主动重试。";if(/^BAIDU_OCR_/.test(code))return"百度识别接口返回业务错误,未填入或提交。";return""}function newBaiduContext(method,alive,probe){method=method||"auto";var canFetch=typeof fetch==="function"&&typeof AbortController==="function";var bridge=requestAdapter();var first=method==="fetch"?"fetch":method==="gm"?"gm":bridge?"gm":"fetch";return{method:method,kind:first,alive:alive,probe:!!probe,canFetch:canFetch,startedAt:Date.now(),deadline:Date.now()+(probe?1e4:first==="fetch"?45e3:3e4),switched:false,callbackSeen:false,httpSeen:false,abort:null}}function baiduJsonRequest(ctx,spec){function run(kind){return new Promise(function(resolve,reject){if(!ctx.alive()){reject(new Error("cancelled"));return}var adapter=requestAdapter(),settled=false,watchdog=null,nativeHandle=null,controller=null;var phase=++phaseFence,startedAt=Date.now(),callbackSeen=false,httpSeen=false,ignoredLogged=false;var lastNativeState=-1,malformedStateSeen=false,nativeStartSeen=false;var remaining=ctx.deadline-Date.now();if(remaining<=0){reject(new Error("timeout"));return}phaseTransport=kind==="fetch"?"fetch":adapter?adapter.id:"GM 不可用";phaseCallback=phaseHttp=phaseStarted=false;phaseNativeStarted=false;phaseReadyState=-1;phaseCallbackType="";phasePending=true;waitCheckpoint=0;requestStartedAt=startedAt;lastElapsedMs=0;requestError="";requestStage=spec.label+":准备 "+phaseTransport;if(!ctx.probe)armElapsed();function fresh(){return!settled&&phase===phaseFence}function abortNative(){try{if(controller)controller.abort()}catch(_){}try{if(nativeHandle&&typeof nativeHandle.abort==="function")nativeHandle.abort()}catch(_){}}function cleanup(){clearTimeout(watchdog);phasePending=false;lastElapsedMs=Math.max(0,Date.now()-startedAt);if(transport===handle)transport=null;if(ctx.abort===handle.abort)ctx.abort=null}function fail(code){if(!fresh())return;settled=true;cleanup();var error=new Error(code);error.ocrElapsedMs=Math.max(0,Date.now()-startedAt);error.ocrHttpSeen=httpSeen;requestError=code;requestStage=spec.label+"未完成";if(code!=="cancelled"&&ctx.alive()){var message=code==="GM_NO_CALLBACK_TIMEOUT"?"油猴接口到总期限仍无回调,无法确认请求是否发出。":code==="timeout"?"鉴权与识别共用的总等待期限已到,已中止本次请求。":code==="GM_REQUEST_BRIDGE_REJECTED"?"油猴桥返回空字符串,本次调用被拒绝。":code==="FETCH_NETWORK_OR_CORS"?"网页请求未收到响应头,可能受网络或跨域规则限制。":code==="RESPONSE_BODY_FAILED"?"已收到 HTTP 响应头,但读取回应正文失败。":/^http-[1-5][0-9]{2}$/.test(code)?"服务返回 HTTP "+code.slice(5)+",本阶段没有获得有效结果。":code==="network"?"油猴接口报告网络错误,未收到有效结果。":code==="REQUEST_UNSUPPORTED"?"所选请求接口不可用,本次没有发送。":code==="response-format"?"服务回应结构无法解析,未继续处理。":"当前请求接口未返回有效结果。";logEvent(code==="timeout"||code==="GM_NO_CALLBACK_TIMEOUT"?"timeout":"request_failed",spec.label+":"+message)}abortNative();reject(error)}function noteIgnored(){if(!ignoredLogged){ignoredLogged=true;logEvent("ignored_result","迟到或重复回应已丢弃,不发出后续识别或提交。")}}function callback(name){if(!fresh()){noteIgnored();return false}if(!ctx.alive()){fail("cancelled");return false}if(Date.now()>=ctx.deadline){fail("timeout");noteIgnored();return false}callbackSeen=phaseCallback=ctx.callbackSeen=true;phaseCallbackType=name;logEvent("callback",spec.label+"收到 "+phaseTransport+" "+name+" 回调。");return true}function accept(statusCode,text){if(!fresh()){noteIgnored();return}if(!ctx.alive()){fail("cancelled");return}if(Date.now()>=ctx.deadline){fail("timeout");noteIgnored();return}var code=Number(statusCode);if(!Number.isInteger(code)||code<100||code>599){fail("response-format");return}httpSeen=phaseHttp=ctx.httpSeen=true;ctx.lastStatus=code;logEvent("http",spec.label+"收到 HTTP "+code+"。");if(typeof text!=="string"||text.length>2e5){fail(code!==200?"http-"+code:"response-format");return}var value;try{value=JSON.parse(text)}catch(_){fail(code!==200?"http-"+code:"response-format");return}if(!value||typeof value!=="object"||Array.isArray(value)){fail("response-format");return}logEvent("body_received",spec.label+"回应正文已读取,正在检查受控结果字段。");settled=true;cleanup();requestError="";ctx.kind=kind;resolve({status:code,value:value,kind:kind})}function acceptResponse(response,name){if(!fresh()){noteIgnored();return}if(!ctx.alive()){fail("cancelled");return}if(Date.now()>=ctx.deadline){fail("timeout");noteIgnored();return}if(name==="onload"&&!callback(name))return;for(var depth=0;depth<2&&typeof response==="string";depth++){if(response.length>2e5){fail("response-format");return}try{response=JSON.parse(response)}catch(_){fail("response-format");return}}if(!response||typeof response.status==="undefined"){if(name==="onload"){fail("response-format");return}logEvent("bridge_wait","桥调用已返回,但没有 HTTP 数据,继续等待事件回调。");return}if(name!=="onload"&&!callback(name))return;var text=response.responseText;if(typeof text!=="string"&&typeof response.response==="string")text=response.response;if(typeof text!=="string"&&response.response&&typeof response.response==="object"){try{text=JSON.stringify(response.response)}catch(_){fail("response-format");return}}accept(response.status,text)}var handle={abort:function(){fail("cancelled")}};ctx.abort=handle.abort;transport=handle;watchdog=setTimeout(function(){fail(kind==="gm"&&!callbackSeen?"GM_NO_CALLBACK_TIMEOUT":"timeout")},remaining);if(kind==="gm"&&!adapter||kind==="fetch"&&!ctx.canFetch){fail("REQUEST_UNSUPPORTED");return}try{if(spec.beforeSend)spec.beforeSend()}catch(error){fail(error&&/^BUDGET_|^stale-image$/.test(error.message)?error.message:"cancelled");return}phaseStarted=true;requestStage=spec.label+":请求已调用";logEvent(ctx.probe?"api_probe_send":"send",spec.label+"调用 "+(kind==="fetch"?"网页 fetch":"油猴 GM")+" POST 接口,等待回应。");if(kind==="gm"){try{nativeHandle=adapter.send({method:"POST",url:spec.url,anonymous:true,headers:spec.headers,data:spec.body,timeout:remaining,onload:function(response){acceptResponse(response,"onload")},onloadstart:function(){if(nativeStartSeen)return;if(!callback("onloadstart"))return;nativeStartSeen=true;phaseNativeStarted=true;phaseCallbackType="onloadstart";requestStage=spec.label+":原生接口已启动,等待 HTTP";logEvent("network_state",spec.label+"收到原生开始请求回调,正在等待 HTTP 回应。");updateDiagnostics()},onreadystatechange:function(value){if(!fresh()){noteIgnored();return}if(!ctx.alive()){fail("cancelled");return}var state=-1,responseStatus=0;try{for(var depth=0;depth<2&&typeof value==="string";depth++){if(value.length>2e5)throw new Error("response-format");value=JSON.parse(value)}var raw=value&&value.readyState;if((typeof raw==="number"||typeof raw==="string"&&raw.trim()!=="")&&Number.isInteger(Number(raw))&&Number(raw)>=0&&Number(raw)<=4)state=Number(raw);responseStatus=Number(value&&value.status)}catch(_){}if(state===lastNativeState&&state>=0||state<0&&malformedStateSeen)return;if(!callback("onreadystatechange"))return;phaseCallbackType="onreadystatechange";if(state<0){malformedStateSeen=true;logEvent("network_state",spec.label+"收到请求状态回调,但没有有效状态字段,继续等待 HTTP。");return}lastNativeState=phaseReadyState=state;if(state>=1)phaseNativeStarted=true;if(state>=2&&Number.isInteger(responseStatus)&&responseStatus>=100&&responseStatus<=599){httpSeen=phaseHttp=ctx.httpSeen=true;ctx.lastStatus=responseStatus;requestStage=spec.label+":已收到 HTTP 状态,等待完整回应";logEvent("http_headers",spec.label+"状态回调提供 HTTP "+responseStatus+",继续等待完整回应。")}else{requestStage=spec.label+(state>=1?":原生已启动,等待 HTTP":":请求桥已回调,等待启动")}logEvent("network_state",spec.label+"收到油猴请求状态 "+state+";"+(httpSeen?"已确认 HTTP 状态,正在等待完整回应。":"尚未收到 HTTP 状态,不能确认服务是否收到请求。"));updateDiagnostics()},onerror:function(response){if(!callback("onerror"))return;var value=response,responseStatus=0;try{for(var depth=0;depth<2&&typeof value==="string";depth++){if(value.length>2e5)throw new Error("response-format");value=JSON.parse(value)}responseStatus=Number(value&&value.status)}catch(_){}if(Number.isInteger(responseStatus)&&responseStatus>=100&&responseStatus<=599){httpSeen=phaseHttp=ctx.httpSeen=true;ctx.lastStatus=responseStatus;logEvent("http",spec.label+"错误回调提供 HTTP "+responseStatus+"。");fail(responseStatus<200||responseStatus>=300?"http-"+responseStatus:"network")}else fail("network")},ontimeout:function(){if(callback("ontimeout"))fail("timeout")},onabort:function(){fail("cancelled")}});if(!fresh()){abortNative();return}if(nativeHandle===""){fail("GM_REQUEST_BRIDGE_REJECTED");return}if(nativeHandle&&typeof nativeHandle.then==="function"){nativeHandle.then(function(value){if(value==="")fail("GM_REQUEST_BRIDGE_REJECTED");else acceptResponse(value,"Promise")},function(){fail("GM_REQUEST_REJECTED")})}}catch(_){fail("GM_REQUEST_BRIDGE_THROW")}}else{try{controller=new AbortController;var options={method:"POST",headers:spec.headers,credentials:"omit",mode:"cors",redirect:"error",referrerPolicy:"no-referrer",signal:controller.signal};if(typeof spec.body==="string")options.body=spec.body;Promise.resolve(fetch(spec.url,options)).then(function(response){if(!callback("HTTP"))return;httpSeen=phaseHttp=ctx.httpSeen=Number(response.status)>0;ctx.lastStatus=Number(response.status);logEvent("http_headers",spec.label+"已收到 HTTP "+Number(response.status)+" 响应头,用时 "+(Date.now()-startedAt)+"ms。");requestStage=spec.label+":正在读取回应正文";updateDiagnostics();var bodyStartedAt=Date.now();if(typeof response.text!=="function"){fail("response-format");return}return Promise.resolve().then(function(){return response.text()}).then(function(text){if(!fresh()){noteIgnored();return}if(!ctx.alive()){fail("cancelled");return}logEvent("body",spec.label+"读取回应正文用时 "+(Date.now()-bodyStartedAt)+"ms。");accept(response.status,text)},function(){fail("RESPONSE_BODY_FAILED")})}).catch(function(){fail("FETCH_NETWORK_OR_CORS")})}catch(_){fail("FETCH_NETWORK_OR_CORS")}}})}return run(ctx.kind).catch(function(error){var other=ctx.kind==="fetch"?"gm":"fetch";var early=error&&error.ocrElapsedMs<=5e3&&!error.ocrHttpSeen;var compatible=ctx.kind==="fetch"?error.message==="FETCH_NETWORK_OR_CORS":/^(?:GM_REQUEST_BRIDGE_REJECTED|GM_REQUEST_BRIDGE_THROW|REQUEST_UNSUPPORTED)$/.test(error.message);if(ctx.method!=="auto"||ctx.switched||!ctx.alive()||!early||!compatible||spec.canFallback&&!spec.canFallback()||Date.now()>=ctx.deadline||(other==="gm"?!requestAdapter():!ctx.canFetch))throw error;ctx.switched=true;ctx.kind=other;logEvent(ctx.probe?"api_probe_fallback":"compatibility","当前接口提前明确失败,仅顺序切换兼容接口一次,不并发重发。");return run(other)})}async function baiduAccessToken(config,ctx,force){var identity=config.key+"\n"+config.secret;if(!force&&baiduAuthCache&&baiduAuthCache.identity===identity&&baiduAuthCache.expiresAt>Date.now()){logEvent("auth_cache","复用本页内存中仍有效的百度令牌,不重新鉴权。");return baiduAuthCache.token}if(!ctx.alive())throw new Error("cancelled");logEvent("auth_start","开始百度鉴权,不发送图片或站点 Cookie。");var query=new URLSearchParams({grant_type:"client_credentials",client_id:config.key,client_secret:config.secret});var result=await baiduJsonRequest(ctx,{label:"百度鉴权",url:"https://aip.baidubce.com/oauth/2.0/token?"+query.toString(),headers:{"Content-Type":"application/json","Accept":"application/json"},body:"{}"});if(!ctx.alive())throw new Error("cancelled");var value=result.value;if(typeof value.error==="string"){var reason=typeof value.error_description==="string"?value.error_description:"";var code=value.error==="invalid_client"&&/unknown client id/i.test(reason)?"BAIDU_AUTH_KEY":value.error==="invalid_client"&&/client authentication failed|client secret|client_secret/i.test(reason)?"BAIDU_AUTH_SECRET":"BAIDU_AUTH_OTHER";throw new Error(code)}if(result.status!==200)throw new Error("http-"+result.status);var expires=Number(value.expires_in);if(typeof value.access_token!=="string"||!/^[A-Za-z0-9._-]{8,4096}$/.test(value.access_token)||!Number.isFinite(expires)||expires<=0||expires>365*24*60*60)throw new Error("BAIDU_AUTH_OTHER");baiduAuthCache={identity:identity,token:value.access_token,expiresAt:Date.now()+expires*1e3-Math.min(6e4,expires*100)};requestStage="百度鉴权成功";logEvent("auth_done","百度鉴权成功,令牌仅缓存于本页内存。");return value.access_token}function baiduSixDigits(value){var rows=value&&value.words_result;if(!Array.isArray(rows)||!rows.length||rows.length>6)return"";if(typeof value.words_result_num!=="number"||value.words_result_num!==rows.length)return"";if(rows.some(function(row){return!row||typeof row.words!=="string"||!/^[0-9\s]{1,16}$/.test(row.words)}))return"";if(rows.length===1)return sixDigits(rows[0].words);if(rows.some(function(row){var p=row.location;return!p||![p.left,p.top,p.width,p.height].every(Number.isFinite)||p.left<0||p.top<0||p.width<=0||p.height<=0}))return"";var ordered=rows.slice().sort(function(a,b){return a.location.left-b.location.left});var center=ordered[0].location.top+ordered[0].location.height/2;for(var index=0;indexMath.min(box.height,ordered[0].location.height)/2)return"";if(index&&box.left=maxAttempts||budget.total>=maxAttempts*maxRounds)throw new Error("BUDGET_CALL_LIMIT");budget.attempts++;budget.total++;budget.calls=budget.total;reserved.total=budget.total}writeBudget(budget);if(imageSends)logEvent("attempt_reserved","第 "+budget.round+" / 3 轮,第 "+budget.attempts+" / 2 次用于兼容接口重试;次数已回读核对。");imageSends++}});if(!ctx.alive())throw new Error("cancelled");var businessCode=Number(result.value.error_code);if(Object.prototype.hasOwnProperty.call(result.value,"error_code")){if(businessCode===110||businessCode===111){invalidateBaiduToken();logEvent("auth_invalidate","百度报告令牌无效,自动流程直接停止,不重复鉴权与识别。")}throw new Error("BAIDU_OCR_"+(Number.isInteger(businessCode)&&businessCode>=0&&businessCode<=999999?businessCode:"OTHER"))}if(result.status!==200)throw new Error("http-"+result.status);var digits=baiduSixDigits(result.value);if(!digits){logEvent("parse_detail","百度返回的结果不符合单行完整六位数字,未填入或提交。");throw new Error("not-six-digits")}logEvent("parse","百度识别结果通过完整六位格式检查,日志不记录数字。");requestStage="百度已返回有效六位结果";requestError="";updateDiagnostics();return digits}throw new Error("BAIDU_AUTH_OTHER")}function cancelApiProbe(){if(!apiProbeCancel)return false;apiProbeCancel();return true}function probeApiConnection(methodOverride){if(apiProbePending)return apiProbePromise;function notice(message){var node=document.querySelector("[data-bm-captcha-probe-result]");if(node)node.textContent=message;else if(!busy)setStatus(message)}if(busy||!allowed){var message=busy?"请先停止正在进行的识别,再测试连接。":"当前页面不支持连接测试。";notice(message);return Promise.resolve({code:busy?"busy":"unsupported-page",status:0,elapsed:0})}var token=++apiProbeSequence,started=Date.now(),finished=false,timer=null,tick=null,ctx=null;apiProbePending=true;function alive(){return!finished&&apiProbePending&&apiProbeSequence===token}notice("正在读取百度配置,只测试鉴权,不发送图片。");logEvent("api_probe_start","开始百度鉴权连接测试,总期限 10 秒,不扣识别次数。");apiProbePromise=new Promise(function(resolve){function finish(code,message,quiet){if(!alive())return;finished=true;apiProbePending=false;apiProbeCancel=null;clearTimeout(timer);clearTimeout(tick);if(ctx&&ctx.abort)ctx.abort();if(!quiet){notice(message+"(用时 "+((Date.now()-started)/1e3).toFixed(1)+" 秒)");logEvent("api_probe_result","连接测试:"+message)}resolve({code:code,status:ctx&&ctx.lastStatus||0,elapsed:Date.now()-started})}apiProbeCancel=function(){finish("cancelled","",true)};timer=setTimeout(function(){var message=!ctx?"配置读取未完成,本次没有发送鉴权。":ctx.kind==="fetch"?"网页鉴权测试 10 秒未完成,已中止。":!ctx.callbackSeen?"油猴接口 10 秒没有任何回调,无法确认请求是否启动。":phaseHttp?"油猴接口已提供 HTTP 状态,但完整回应等待超时。":phaseNativeStarted?"油猴原生接口已报告启动,但 10 秒未提供 HTTP 状态。":"油猴桥已有回调,但 10 秒未提供有效 HTTP 状态。";finish("timeout",message,false)},1e4);tick=setTimeout(function update(){if(!alive())return;notice("百度鉴权连接测试仍在等待回应,已等待 "+((Date.now()-started)/1e3).toFixed(1)+" 秒。");tick=setTimeout(update,1e3)},1e3);Promise.resolve().then(readConfig).then(function(config){if(!alive())return;if(!config.key||!config.secret){finish("missing-key","未找到完整的百度 API Key 和 Secret Key。",false);return}var method=["auto","fetch","gm"].indexOf(methodOverride)>=0?methodOverride:config.transportMethod;ctx=newBaiduContext(method,alive,true);ctx.deadline=started+1e4;return baiduAccessToken(config,ctx,true).then(function(){finish("ok","百度鉴权成功;图片识别权限和准确率仍需实际识别确认。",false)},function(error){var code=error&&error.message||"response-format";finish(code,baiduError(code)||(code==="FETCH_NETWORK_OR_CORS"?"网页鉴权请求受网络或跨域规则限制,可检查油猴接口。":code==="GM_NO_CALLBACK_TIMEOUT"?"油猴鉴权接口到期限仍无回调。":code==="timeout"?"百度鉴权等待超时。":"百度鉴权连接测试未完成。"),false)})}).catch(function(){finish("config-error","读取百度配置失败,本次没有识别图片。",false)})});return apiProbePromise}function requestAdapter(){if(typeof GM_xmlhttpRequest==="function")return{id:"GM_legacy",send:function(options){return GM_xmlhttpRequest(options)}};if(typeof GM!=="undefined"&&GM&&typeof GM.xmlHttpRequest==="function"){return{id:"GM_promise",send:function(options){return GM.xmlHttpRequest(options)}}}return null}function submitChallenge(before,digits,token){if(stopped||sequence!==token||submitted||!sameImage(before))throw new Error("stale-image");var budget=readBudget();if(budget.paused||budget.phase!=="attempting")throw new Error("BUDGET_PAUSED");if(budget.submits>=maxAttempts*maxRounds)throw new Error("BUDGET_SUBMIT_LIMIT");ownedCode=digits;before.code.value=digits;logEvent("fill","识别结果已填入原表单,正在复核图片与输入状态。");before.code.dispatchEvent(new Event("input",{bubbles:true}));before.code.dispatchEvent(new Event("change",{bubbles:true}));before.codeValue=digits;if(stopped||sequence!==token||!sameImage(before)){if(before.code.value===digits)before.code.value="";ownedCode="";throw new Error("stale-image")}budget.submits++;budget.phase="submitted";writeBudget(budget);submitted=true;logEvent("submit","正在提交原站原表单,会话字段保持原样。");requestStage="提交原站表单";setStatus("已识别,正在通过原网页表单验证。");if(typeof nativeRequestSubmit==="function")nativeRequestSubmit.call(before.form);else if(typeof nativeSubmit==="function")nativeSubmit.call(before.form);else throw new Error("unsupported");logEvent("submit_called","原表单提交接口已调用,正在等待网站导航;是否通过由网站返回确认。");navigationTimer=setTimeout(function(){if(submitted&&!stopped){logEvent("navigation_wait","提交后 6 秒仍在当前验证页等待网站返回,继续等待原提交结果。");setStatus("已提交,等待网站返回;返回验证码页会继续预算,返回目标页才确认成功。")}},6e3)}function budgetMessage(code){if(code==="BUDGET_PAUSED")return"当前自动周期已停止,重载不会恢复;可手动填写或点击重新自动验证。";if(code==="BUDGET_ROUND_LIMIT")return"三轮共 6 次尝试仍未通过,已停止自动识别与刷新;可手动验证或点击重新自动验证。";if(code==="BUDGET_CALL_LIMIT")return"本轮两次尝试已用完,未发送额外请求。";if(code==="BUDGET_SUBMIT_LIMIT")return"本次六次提交预算已用完,未提交额外结果。";if(code==="BUDGET_STORE_WRITE_FAILED")return"浏览器无法保存尝试次数,未继续自动请求或提交;原表单仍可手动填写。";return"连续自动尝试已达上限,未继续请求;可手动验证或主动重试一次。"}function explain(error){var code=error&&error.message||"";if(code==="cancelled"||stopped)return;if(/^(?:BAIDU_AUTH_(?:KEY|SECRET|OTHER)|BAIDU_OCR_(?:\d{1,6}|OTHER)|http-\d+|not-six-digits|stale-image|budget|BUDGET_(?:PAUSED|CALL_LIMIT|SUBMIT_LIMIT|ROUND_LIMIT|STORE_WRITE_FAILED)|timeout|network|unsupported|response-format|RESPONSE_BODY_FAILED|image-[a-z-]+|GM_REQUEST_[A-Z_]+|GM_NO_CALLBACK_TIMEOUT|FETCH_NETWORK_OR_CORS|REQUEST_UNSUPPORTED)$/.test(code))requestError=code;else requestError="AUTOMATION_FAILED";requestStage="保留手动验证";var message="识别流程未完成,未提交;原验证码仍可手动填写。";if(baiduError(code))message=baiduError(code);else if(code==="not-six-digits")message="识别结果不是完整的 6 位数字,未提交;可手动填写或重试一次。";else if(code==="stale-image")message="验证码图片或表单已变化,已丢弃旧结果;可手动填写或主动重试一次。";else if(code==="budget"||/^BUDGET_/.test(code))message=budgetMessage(code);else if(code==="timeout")message=phaseHttp?"已收到 HTTP 响应头,但等待回应正文超时,本次未提交。":"等待 HTTP 响应头超时,无法确认服务是否收到请求,本次未提交。";else if(code==="GM_NO_CALLBACK_TIMEOUT")message="油猴接口到总期限仍无回调,无法确认请求是否发出;可主动测试连接或手动验证。";else if(/^http-\d+$/.test(code))message="OCR 服务返回 HTTP "+code.slice(5)+",未提交或自动重发;请检查 API 设置。";else if(code==="network"||/^GM_REQUEST_/.test(code))message="油猴请求桥未返回有效回应,未提交;请检查浏览器脚本权限或手动验证。";else if(code==="FETCH_NETWORK_OR_CORS")message="网页请求阶段失败,尚未收到 HTTP 响应头;可能是网络或跨域规则限制,不能据此判断密钥或识别结果。可检查请求通道或手动验证。";else if(code==="RESPONSE_BODY_FAILED")message="已收到 HTTP 响应头,但读取回应正文失败,未提交或重发;可手动验证或主动重试一次。";else if(code==="response-format")message="已返回的回应格式无法解析,未提交;可手动验证或主动重试一次。";else if(code==="REQUEST_UNSUPPORTED")message="所选请求通道在当前浏览器不可用,未发送;请检查请求方式或手动验证。";else if(code==="unsupported")message="当前浏览器不支持原表单提交接口,未继续操作;请在原表单手动提交。";else if(/^image-/.test(code))message="验证码图片尚未就绪或无法读取,未发送;请手动填写或主动重试一次。";logEvent("manual",message);setStatus(message)}function begin(){if(!isCaptchaPage||busy||apiProbePending||stopped||submitted)return Promise.resolve();var current=findChallenge();if(!current){setStatus("没有匹配到原站验证码表单,保留手动验证。");return Promise.resolve()}installCard(current);if(current.code.value.trim()){stop();setStatus("检测到已填写的验证码,自动流程已停止并保留你的输入;请在原表单提交。");return Promise.resolve()}if(resumeBudget()||retryAction)return Promise.resolve();if(document.hidden){scheduleRetry("attempt");setStatus("验证码页在后台,返回页面后再继续自动验证。");return Promise.resolve()}if(!current.image.complete||!current.image.naturalWidth){setStatus("等待验证码图片加载。");return Promise.resolve()}var token=++sequence,reserved=null;busy=true;flowNumber++;flowStartedAt=Date.now();requestStartedAt=0;lastElapsedMs=0;waitCheckpoint=0;phaseStarted=phaseCallback=phaseHttp=phasePending=false;phaseTransport="未发出";phaseNativeStarted=false;phaseReadyState=-1;phaseCallbackType="";logEvent("begin","开始新的识别流程,正在读取配置。");requestStage="读取配置";requestError="";updateDiagnostics();return readConfig().then(function(config){if(stopped||sequence!==token)return;if(!config.enabled||!config.key||!config.secret){requestStage="配置未启用或缺少密钥";logEvent("config_blocked",!config.key||!config.secret?"配置缺少完整百度凭据,未发送请求。":"配置中的自动识别开关已关闭,未发送请求。");setStatus("尚未启用自动识别,点击 API 设置检查配置;也可手动验证。");terminalStop(!config.key||!config.secret?"CONFIG_KEY_MISSING":"CONFIG_DISABLED");return}logEvent("config","配置已就绪,耗时 "+(Date.now()-flowStartedAt)+"ms;识别已启用。");var imageStartedAt=Date.now();var before=snapshotImage(current);logEvent("snapshot","图片准备耗时 "+(Date.now()-imageStartedAt)+"ms;日志不含图片与标识。");reserved=reserveAttempt();setStatus("正在通过百度智能云识别验证码,仍可停止并手动填写。");return ocrRequest(config,before.pixels,token,config.transportMethod,before,reserved).then(function(digits){submitChallenge(before,digits,token)})}).catch(function(error){if(stopped||sequence!==token||error.message==="cancelled")return;explain(error);if(error.message==="BUDGET_STORE_WRITE_FAILED"){storageBudgetStop();return}var currentChallenge=findChallenge();if(currentChallenge&¤tChallenge.code.value.trim()&¤tChallenge.code.value!==ownedCode){stop();return}if(reserved&&retryableFailure(error.message))failedAttempt(error.message);else terminalStop(requestError||"AUTOMATION_FAILED")}).finally(function(){if(sequence===token){busy=false;clearTimeout(elapsedTimer);elapsedTimer=null;armRetry();setStatus(lastMessage)}})}function retry(){if(!isCaptchaPage)return Promise.resolve();stop();stopped=false;submitted=false;if(ownedCode&&challenge&&challenge.code.value===ownedCode)challenge.code.value="";ownedCode="";try{writeBudget(freshBudget());budgetChecked=true}catch(_){storageBudgetStop();return Promise.resolve()}logEvent("cycle_restart","按你的操作开启新的自动周期,最多三轮,每轮两次尝试。");return begin()}function refreshPage(){if(!isCaptchaPage)return;stop();try{var budget=freshBudget();budget.phase="refreshing";writeBudget(budget)}catch(_){requestError="BUDGET_STORE_WRITE_FAILED";requestStage="保留手动验证";var message=budgetMessage("BUDGET_STORE_WRITE_FAILED");logEvent("refresh_failed",message);setStatus(message);return}logEvent("cycle_restart","按你的操作开启新的三轮自动周期并刷新验证码网页。");logEvent("refresh","按你的操作主动刷新原验证码网页,重新获取图片。");try{location.reload()}catch(_){requestError="PAGE_REFRESH_FAILED";requestStage="保留手动验证";logEvent("refresh_failed","浏览器未能刷新页面,可使用浏览器原有刷新按钮。");setStatus("浏览器未能刷新页面,可使用浏览器原有刷新按钮。")}}function activateControl(control){if(!control||control.disabled)return;if(control.hasAttribute("data-bm-captcha-history-entry"))openHistory();else if(control.hasAttribute("data-bm-captcha-config"))openConfig();else if(control.hasAttribute("data-bm-captcha-test-connection")){var selected=configPanel&&configPanel.querySelector("[data-bm-captcha-transport]");probeApiConnection(selected&&selected.value)}else if(control.hasAttribute("data-bm-captcha-stop"))stop();else if(control.hasAttribute("data-bm-captcha-retry"))retry();else if(control.hasAttribute("data-bm-captcha-refresh"))refreshPage();else if(control.hasAttribute("data-bm-captcha-config-close"))closeConfig();else if(control.hasAttribute("data-bm-captcha-save"))saveConfig(false);else if(control.hasAttribute("data-bm-captcha-delete"))saveConfig(true);else if(control.hasAttribute("data-bm-captcha-copy-log")||control.hasAttribute("data-bm-captcha-copy-history"))copyLog(control);else if(control.hasAttribute("data-bm-captcha-history-toggle"))toggleHistory(control);else if(control.hasAttribute("data-bm-captcha-export-history"))exportHistory(control);else if(control.hasAttribute("data-bm-captcha-clear-history"))clearHistory(control)}function touchPoint(list,id){if(!list)return null;for(var index=0;index12||Math.abs(point.clientY-controlTouch.y)>12)controlTouch.moved=true}else if(event.type==="touchend"&&controlTouch.control){point=touchPoint(event.changedTouches,controlTouch.id);var pressed=controlTouch.control;var smallTap=control===pressed&&!pressed.disabled&&point&&!controlTouch.moved&&event.changedTouches.length===1&&now>=controlTouch.at&&now-controlTouch.at<=800&&Math.abs(point.clientX-controlTouch.x)<=12&&Math.abs(point.clientY-controlTouch.y)<=12;controlTouch.control=null;controlTouch.last=pressed;controlTouch.lastAt=now;if(smallTap){event.preventDefault();activateControl(pressed)}}else if(event.type==="touchcancel"){if(controlTouch.control){controlTouch.last=controlTouch.control;controlTouch.lastAt=now}controlTouch.control=null}else if(event.type==="click"&&control){event.preventDefault();if(!(controlTouch.last===control&&now>=controlTouch.lastAt&&now-controlTouch.lastAt<700))activateControl(control)}event.stopImmediatePropagation();return true}function installConfigEntry(container){if(!allowed||!container||container.querySelector("[data-bm-captcha-config]"))return null;return button("在线验证","data-bm-captcha-config",container)}function discover(){if(challenge)return begin();challenge=findChallenge();if(!challenge)return;if(startupObserver)startupObserver.disconnect();clearTimeout(startupTimer);installCard(challenge);challenge.image.addEventListener("load",function(){if(!stopped&&!submitted)begin()});challenge.code.addEventListener("input",function(event){if(event.isTrusted)stop()});challenge.form.addEventListener("submit",function(){if(!submitted)stop()});return begin()}if(allowed){var boluomaoHost=/^(?:www\.)?boluomao1\.com$/.test(location.hostname);var returnedToContent=boluomaoHost&&/^\/(?:book\/\d+|read\/\d+\/\d+)\.html$/.test(location.pathname);var returnedToSearch=/^(?:www\.)?rrssk\.com$/.test(location.hostname)&&location.pathname==="/boluomao.html";var accountReturn=boluomaoHost&&/^\/(?:user\/center\/)?$/.test(location.pathname);function confirmReturnedTarget(){if(document.querySelector('input[name="GOEDGE_WAF_CAPTCHA_CODE"], input[name="GOEDGE_WAF_CAPTCHA_ID"]'))return;if(accountReturn){if(document.querySelector('#loginForm, form[action*="login"]')||/错误|失败|error|[45]\d\d/i.test(document.title))return;if(location.pathname==="/"){var paths=Array.from(document.querySelectorAll("a[href]"),function(node){try{var url=new URL(node.href);return url.origin===location.origin&&!url.username&&!url.password?url.pathname:""}catch(_){return""}});if(!/菠萝猫/.test(document.title)||paths.indexOf("/user/center/")<0||!paths.some(function(path){return/^\/book\/\d+\.html$/.test(path)}))return}else if(!document.querySelector(".mcCalendar .mcCalDay.today")||!Array.from(document.querySelectorAll("script")).some(function(node){return/\bcurrentUid\s*=\s*[1-9]\d*\s*;/.test(node.textContent)}))return}try{var previousBudget=readBudget();if(previousBudget.submits>0){writeBudget(freshBudget());logEvent("navigation_complete","网站已返回目标阅读或搜索页,本次验证导航已经完成,三轮预算已清零。")}}catch(_){requestError="BUDGET_STORE_WRITE_FAILED"}}if(returnedToContent||returnedToSearch||accountReturn){if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",confirmReturnedTarget,{once:true});else confirmReturnedTarget()}["click","auxclick","pointerdown","mousedown","touchstart","touchmove","touchend","touchcancel"].forEach(function(type){window.addEventListener(type,guard,{capture:true,passive:false})});window.addEventListener("pagehide",function(){if(apiProbePending)logEvent("api_probe_cancel","页面离开,连接测试已取消。");cancelApiProbe();if(isCaptchaPage)logEvent("navigation_departure",submitted?"验证页正在离开;是否通过将在目标页确认。":"验证页正在离开,未完成的结果不再提交。");historyExportUrls.forEach(function(url){try{URL.revokeObjectURL(url)}catch(_){}});historyExportUrls=[];sequence++;stopped=true;phasePending=false;clearTimeout(navigationTimer);clearTimeout(startupTimer);clearRetryTimer(true);clearTimeout(elapsedTimer);elapsedTimer=null;capabilityTimers.forEach(function(timer){clearTimeout(timer)});if(startupObserver)startupObserver.disconnect();if(transport){try{transport.abort()}catch(_){}transport=null}});document.addEventListener("visibilitychange",function(){if(document.hidden)clearRetryTimer(false);else if(retryAction)armRetry()});window.addEventListener("pageshow",function(event){if(!isCaptchaPage||!event.persisted)return;stopped=false;busy=false;submitted=false;budgetChecked=false;if(ownedCode&&challenge&&challenge.code.value===ownedCode)challenge.code.value="";ownedCode="";discover()})}if(isCaptchaPage){discover();if(!challenge){startupObserver=new MutationObserver(discover);startupObserver.observe(document.documentElement||document,{childList:true,subtree:true,attributes:true,attributeFilter:["src","action","method","value"]});startupTimer=setTimeout(function(){if(startupObserver)startupObserver.disconnect()},15e3)}[250,1e3,2500].forEach(function(delay){capabilityTimers.push(setTimeout(function(){if(!stopped&&!busy&&!submitted&&/^(?:初始化|配置未启用或缺少密钥)$/.test(requestStage))begin()},delay))})}else if(allowed){readConfig().catch(function(){storageError="CONFIG_INITIALIZE_FAILED";updateDiagnostics()})}return{isCaptchaPage:isCaptchaPage,isChallengePage:function(){return isCaptchaPage},bootChallenge:discover,openConfig:openConfig,installConfigEntry:installConfigEntry,installEntry:installConfigEntry,isOpen:function(){return!!configPanel},isEnabled:function(){return readConfig().then(function(value){return allowed&&value.enabled&&!!value.key&&!!value.secret})},getDiagnostics:diagnostics,getLogs:function(){return trace.map(function(entry){return{time:entry.time,flow:entry.flow,elapsedMs:entry.elapsedMs,stage:entry.stage,message:entry.message}})},getLogText:logText,getHistory:function(){return refreshHistory().map(function(entry){return safeLogEntry(entry,true)})},getHistoryText:historyText,probeApiConnection:probeApiConnection,cancelApiProbe:cancelApiProbe,guard:guard,stop:stop,retry:retry}})();captchaHost[captchaSlotKey]={version:"1.9.18",document:document,api:captchaAssistant};var captchaAutomation=captchaAssistant; // END PRIVATE ONLINE CAPTCHA ASSISTANT 1.9.18 // END PRIVATE ONLINE VERIFICATION 1.9.18 if (captchaAutomation.isChallengePage()) { captchaAutomation.bootChallenge(); return; } if (/^(?:www\.)?rrssk\.com$/.test(location.hostname)) { installSearchResultLinks(); return; } // BEGIN SITE ACCOUNT BOOTSTRAP 1.9.17 var libraryKey = 'bm-reader-library-v1'; var siteAccount = createSiteAccountBridge(); function createSiteAccountBridge() { if (location.protocol !== 'https:' || location.port || location.hostname !== 'www.boluomao1.com' || /^\/WAF(?:\/|$)/i.test(location.pathname) || /^\/user\/(?:login|register|forgot|reset)(?:\/|$)/i.test(location.pathname)) return null; var existing = pageWindow.__bmSiteAccountV1; if (existing && existing.document === document && existing.api) return existing.api; if (typeof createBoluomaoSiteAccount !== 'function' || typeof createBoluomaoSiteAccountAdapter !== 'function') return null; var readerRoute = /^\/(?:book|read)\//.test(location.pathname), dock = null, dockStatus = null; var hooks = { loadLibrary: loadLibrary, validateLibrary: validateBackupLibrary, storeLibrary: function (value) { localStorage.setItem(libraryKey, JSON.stringify(validateBackupLibrary(value))); }, libraryChanged: function () { if (!state) return; updateFavoriteButtons(); if (state.shelfOpen) renderShelf(); }, beforeOpen: function () { if (!state) return; flushShelfProgress(); if (state.progressOpen) closeProgressPanel(); if (state.tocOpen) closeCatalogue(); if (state.shelfOpen) closeShelfPanel(); if (bookProgress) bookProgress.stop(); cancelAutoRecovery(); stopCatalogueRequest(); if (state.progressTimer) { clearTimeout(state.progressTimer); state.progressTimer = null; } if (state.timer) { clearTimeout(state.timer); state.timer = null; } state.retryPending = false; updateStatus(); }, afterClose: function () { if (!state) return; schedule(); if (bookProgress) { bookProgress.update(); bookProgress.ensure(); } if (readerWidget) readerWidget.armIdle(); }, applyTheme: function (panel) { if (!state || !themes) return; var theme = themeFor(currentTheme); panel.style.setProperty('background-color', theme.bg, 'important'); panel.style.setProperty('color', theme.text, 'important'); panel.style.setProperty('background-image', themeBackground(theme), 'important'); }, statusChanged: function (status) { if (dockStatus) dockStatus.textContent = status.message; } }; var api; try { api = createBoluomaoSiteAccount({ window: window, document: document, storage: localStorage, origin: location.origin, adapter: createBoluomaoSiteAccountAdapter({ window: window }), hooks: hooks, taskLinks: [{ label: '网站个人中心与每日任务', url: '/user/center/' }] }); } catch (_) { return null; } pageWindow.__bmSiteAccountV1 = { document: document, version: '1.9.17', api: api }; function begin() { if (!readerRoute && document.body && !document.querySelector('[data-bm-site-account-entry]')) { dock = document.createElement('aside'); dock.setAttribute('data-bm-site-account-entry', ''); dock.style.cssText = 'position:fixed;left:10px;bottom:12px;z-index:2147483645;max-width:160px;padding:6px 8px;border:1px solid rgba(128,128,128,.3);border-radius:8px;background:rgba(250,247,240,.92);color:#463d32;font:12px/1.4 sans-serif;'; api.installEntry(dock); var button = dock.querySelector('button'); if (button) button.style.cssText = 'min-height:36px;border:0;background:transparent;color:inherit;font:14px/1.4 sans-serif;'; dockStatus = document.createElement('div'); dockStatus.setAttribute('role', 'status'); dockStatus.style.cssText = 'font-size:11px;opacity:.8;overflow-wrap:anywhere;'; dockStatus.textContent = api.getStatus().message; dock.appendChild(dockStatus); document.body.appendChild(dock); } Promise.resolve().then(function () { return api.start(); }).catch(function () {}); } if (!readerRoute) ['click', 'auxclick', 'pointerdown', 'mousedown', 'touchstart', 'touchend'].forEach(function (type) { window.addEventListener(type, function (event) { api.guard(event); }, { capture: true, passive: false }); }); if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', begin, { once: true }); else begin(); return api; } // END SITE ACCOUNT BOOTSTRAP 1.9.17 if (location.hostname !== 'www.boluomao1.com' || !/^\/(?:book|read)\//.test(location.pathname) || pageWindow.__bmNativePagerInstalled) return; pageWindow.__bmNativePagerInstalled = true; pageWindow.__bmReaderVersion = '1.9.19'; var origin = location.origin; var searchEntry = new URL('https://www.rrssk.com/boluomao.html'); var searchNavigationUntil = 0; var match = location.pathname.match(/^\/read\/(\d+)\/\d+\.html$/); var bookId = match && match[1]; var libraryMatch = location.pathname.match(/^\/(?:book\/(\d+)|read\/(\d+)\/\d+)\.html$/); var libraryBookId = libraryMatch && (libraryMatch[1] || libraryMatch[2]); var state = { source: null, first: null, next: null, pages: [], seen: new Set(), contentSeen: new Set(), reader: null, content: null, status: null, paper: null, toolbar: null, toolbarHandle: null, tocPanel: null, tocList: null, tocPages: null, tocStatus: null, tocOpen: false, tocPage: 1, tocMax: 1, tocSeq: 0, tocCache: new Map(), tocController: null, tocCurrentPath: '', progressPanel: null, progressList: null, progressStatus: null, progressOpen: false, progressSeq: 0, progressTimer: null, progressTouched: false, savedProgress: null, pendingResume: null, progressHistory: { furthest: null, recent: [] }, chapterOrders: new Map(), chapterTitles: new Map(), forwardLinks: new Map(), busy: false, blocked: false, readSeq: 0, autoEntryTimer: null, autoEntryDone: false, compactedPages: 0, compacting: false, backupDownloadAnchor: null, backupDownloadUrl: null, retryPending: false, recovering: false, recoveryMessage: '', recoveryTimer: null, recoveryResolve: null, readController: null, nextIssue: '', repeatMemory: { path: '', pages: [], size: 0 }, repeatLog: [], repeatLogArea: null, repeatLogList: null, repeatLogButton: null, shelfPanel: null, shelfList: null, shelfStatus: null, shelfOpen: false, shelfSeq: 0, shelfLimit: 30, shelfBodyOverflow: null, lastRestoreTarget: null, error: '', lastRequest: 0, timer: null }; // BEGIN READER CONTROLS 1.9.3 // BEGIN READER EXIT / RE-ENTRY 1.9.8 var readerEntry = (function () { var exited = false; var reopening = false; var quiet = false; var saved = null; var savedAtTop = false; var floatButton = null; var boundReader = null; var originalScroll = null; var positionKey = 'bm-reader-float-position-v1'; var floatPosition = null; var safeProbe = null; var drag = null; var suppressClickUntil = 0; var nativeReaderObserver = null; try { var storedPosition = JSON.parse(localStorage.getItem(positionKey) || 'null'); if (storedPosition && Number.isFinite(storedPosition.x) && Number.isFinite(storedPosition.y) && storedPosition.x >= 0 && storedPosition.x <= 1 && storedPosition.y >= 0 && storedPosition.y <= 1) { floatPosition = { x: storedPosition.x, y: storedPosition.y }; } } catch (_) {} function viewportBounds() { if (!safeProbe && document.body) { safeProbe = document.createElement('div'); safeProbe.setAttribute('data-bm-float-safe-area', ''); safeProbe.style.cssText = 'position:fixed;visibility:hidden;pointer-events:none;width:0;height:0;' + 'padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);'; document.body.appendChild(safeProbe); } var style = safeProbe ? window.getComputedStyle(safeProbe) : null; var inset = function (value) { return Math.max(0, parseFloat(value) || 0); }; var viewport = window.visualViewport; var width = viewport && viewport.width > 0 ? viewport.width : window.innerWidth; var height = viewport && viewport.height > 0 ? viewport.height : window.innerHeight; var left = (viewport && viewport.offsetLeft || 0) + 8 + inset(style && style.paddingLeft); var top = (viewport && viewport.offsetTop || 0) + 8 + inset(style && style.paddingTop); return { left: left, top: top, right: Math.max(left, (viewport && viewport.offsetLeft || 0) + width - 52 - inset(style && style.paddingRight)), bottom: Math.max(top, (viewport && viewport.offsetTop || 0) + height - 62 - inset(style && style.paddingBottom)) }; } function positionFloat(x, y, persist) { if (!floatButton) return; var bounds = viewportBounds(); var nextX = Math.max(bounds.left, Math.min(bounds.right, x)); var nextY = Math.max(bounds.top, Math.min(bounds.bottom, y)); floatButton.style.left = nextX + 'px'; floatButton.style.top = nextY + 'px'; floatButton.style.right = 'auto'; floatButton.style.bottom = 'auto'; floatPosition = { x: bounds.right > bounds.left ? (nextX - bounds.left) / (bounds.right - bounds.left) : 1, y: bounds.bottom > bounds.top ? (nextY - bounds.top) / (bounds.bottom - bounds.top) : 1 }; if (persist) try { localStorage.setItem(positionKey, JSON.stringify(floatPosition)); } catch (_) {} } function resetFloatPosition() { if (!floatButton) return; var bounds = viewportBounds(), position = floatPosition || { x: 1, y: 1 }; positionFloat(bounds.left + position.x * (bounds.right - bounds.left), bounds.top + position.y * (bounds.bottom - bounds.top), false); } function eventPoint(event) { var touch = event.touches && event.touches[0] || event.changedTouches && event.changedTouches[0]; return { x: touch ? touch.clientX : event.clientX, y: touch ? touch.clientY : event.clientY }; } function beginDrag(event) { if (drag || !floatButton || (event.button !== undefined && event.button !== 0)) return; if (window.PointerEvent && event.type !== 'pointerdown') return; if (!window.PointerEvent && event.type === 'pointerdown') return; var point = eventPoint(event), rect = floatButton.getBoundingClientRect(); if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) return; drag = { type: event.type, id: event.pointerId, startX: point.x, startY: point.y, x: rect.left, y: rect.top, moved: false }; if (event.pointerId !== undefined && floatButton.setPointerCapture) { try { floatButton.setPointerCapture(event.pointerId); } catch (_) {} } floatButton.style.opacity = '.9'; } function moveDrag(event) { if (!drag || (drag.id !== undefined && event.pointerId !== drag.id)) return; if (drag.type === 'pointerdown' && !/^pointer/.test(event.type)) return; if (drag.type === 'touchstart' && !/^touch/.test(event.type)) return; if (drag.type === 'mousedown' && !/^mouse/.test(event.type)) return; var point = eventPoint(event), dx = point.x - drag.startX, dy = point.y - drag.startY; if (!Number.isFinite(dx) || !Number.isFinite(dy)) return; if (Math.abs(dx) + Math.abs(dy) > 7) drag.moved = true; if (drag.moved) { if (event.cancelable) event.preventDefault(); positionFloat(drag.x + dx, drag.y + dy, false); } event.stopImmediatePropagation(); } function finishDrag(event) { if (!drag || (drag.id !== undefined && event.pointerId !== drag.id)) return; if (drag.type === 'pointerdown' && !/^pointer/.test(event.type)) return; if (drag.type === 'touchstart' && !/^touch/.test(event.type)) return; if (drag.type === 'mousedown' && !/^mouse/.test(event.type)) return; if (drag.moved || /cancel$/.test(event.type)) { suppressClickUntil = Date.now() + 750; if (event.cancelable) event.preventDefault(); var rect = floatButton.getBoundingClientRect(); positionFloat(rect.left, rect.top, true); } if (drag.id !== undefined && floatButton.releasePointerCapture) { try { floatButton.releasePointerCapture(drag.id); } catch (_) {} } drag = null; floatButton.style.opacity = '.62'; event.stopImmediatePropagation(); } ['pointermove', 'touchmove', 'mousemove'].forEach(function (type) { window.addEventListener(type, moveDrag, { capture: true, passive: false }); }); ['pointerup', 'pointercancel', 'touchend', 'touchcancel', 'mouseup'].forEach(function (type) { window.addEventListener(type, finishDrag, { capture: true, passive: false }); }); window.addEventListener('resize', resetFloatPosition, { passive: true }); if (window.visualViewport) { window.visualViewport.addEventListener('resize', resetFloatPosition, { passive: true }); window.visualViewport.addEventListener('scroll', resetFloatPosition, { passive: true }); } function eligible() { var current = readUrl(location.href); return !!current && !!state.first && !!state.source && state.source.isConnected && Array.isArray(state.first.paragraphs) && state.first.paragraphs.length > 0 && !document.querySelector('form[action*="/WAF/VERIFY/CAPTCHA"],#ui-captcha-image'); } function capture() { if (!activeReader() || !state.first) return normalizeProgress(state.savedProgress); var anchorY = state.reader.getBoundingClientRect().top + Math.min(120, state.reader.clientHeight * .22); var paragraphs = state.content.querySelectorAll('p[data-bm-progress-index]'); var selected = null, bounds = null; for (var i = 0; i < paragraphs.length; i++) { if (paragraphs[i].hidden || paragraphs[i].closest('[hidden],.readPage,.page2-chapter')) continue; var rect = paragraphs[i].getBoundingClientRect(); if (!(rect.height > 0)) continue; selected = paragraphs[i]; bounds = rect; if (rect.bottom > anchorY) break; } if (!selected || !bounds) return normalizeProgress(state.savedProgress); return normalizeProgress({ url: selected.getAttribute('data-bm-progress-url'), index: Number(selected.getAttribute('data-bm-progress-index')), ratio: Math.max(0, Math.min(1, (anchorY - bounds.top) / bounds.height)), time: Date.now(), title: titleForProgress(readUrl(selected.getAttribute('data-bm-progress-url'))) }); } function stopReaderRequests() { state.readSeq++; if (state.readController) { state.readController.abort(); state.readController = null; } if (state.timer) { clearTimeout(state.timer); state.timer = null; } if (state.progressTimer) { clearTimeout(state.progressTimer); state.progressTimer = null; } if (state.autoEntryTimer) { clearTimeout(state.autoEntryTimer); state.autoEntryTimer = null; } if (state.recoveryTimer) { clearTimeout(state.recoveryTimer); state.recoveryTimer = null; } if (state.recoveryResolve) { state.recoveryResolve(false); state.recoveryResolve = null; } state.busy = false; state.retryPending = false; state.recovering = false; state.recoveryMessage = ''; stopCatalogueRequest(); } function restoreSiteShell() { var key = 'via-reader-data'; if (document.body.hasAttribute(key)) { document.body.style.overflow = document.body.getAttribute(key); document.body.removeAttribute(key); } ['__via_reader_custom__', '__via_reader__'].forEach(function (id) { var style = document.getElementById(id); if (style) style.remove(); }); var viewport = document.querySelector('meta[name="viewport"]'); if (viewport && viewport.hasAttribute(key)) { viewport.setAttribute('content', viewport.getAttribute(key)); viewport.removeAttribute(key); } try { localStorage.__via_reader_enabled__ = null; } catch (_) {} } function updateFloat() { var present = document.querySelector('.via-reader-body'); var presentStyle = present && window.getComputedStyle(present); var presentVisible = present && present.isConnected && !present.hidden && presentStyle.display !== 'none' && presentStyle.visibility !== 'hidden' && presentStyle.visibility !== 'collapse'; var show = exited && eligible() && !presentVisible; if (!show) { if (floatButton) floatButton.remove(); return; } if (!floatButton) { floatButton = document.createElement('button'); floatButton.type = 'button'; floatButton.setAttribute('data-bm-reader-enter', ''); floatButton.setAttribute('aria-label', '返回阅读模式并恢复刚才的位置'); floatButton.title = '继续阅读'; floatButton.textContent = '阅'; floatButton.title = '点一下继续阅读;拖动可移动位置'; floatButton.style.cssText = 'position:fixed;right:10px;bottom:18px;' + 'z-index:2147483646;width:44px;height:44px;box-sizing:border-box;padding:0;' + 'border:2px solid #4b6258;border-radius:50%;background:#e5eee5;' + 'box-shadow:0 2px 8px rgba(0,0,0,.2);color:#233f33;opacity:.62;' + 'font:600 19px/1 sans-serif;cursor:pointer;touch-action:none;user-select:none;'; floatButton.addEventListener('focus', function () { floatButton.style.opacity = '.9'; }); floatButton.addEventListener('blur', function () { floatButton.style.opacity = '.62'; }); } if (!floatButton.isConnected) { document.body.appendChild(floatButton); resetFloatPosition(); } } function alignOriginalSource(progress) { if (!progress || !state.source) return; var target = readUrl(progress.url); if (!target) return; var model = pageModelForUrl(target); var heading = document.querySelector('.conC h1.title'); if (model && heading) heading.textContent = model.title; var paragraphs = state.source.querySelectorAll('p[data-bm-source-index]'); for (var i = 0; i < paragraphs.length; i++) { var section = paragraphs[i].closest('[data-bm-page]'); var url = section && readUrl(section.getAttribute('data-bm-page')); if (!url || pageKey(url) !== pageKey(target) || Number(paragraphs[i].getAttribute('data-bm-source-index')) !== progress.index) continue; var rect = paragraphs[i].getBoundingClientRect(); if (!(rect.height > 0)) return; window.scrollBy(0, rect.top + progress.ratio * rect.height - Math.min(120, window.innerHeight * .22)); return; } } function exit() { var reader = state.reader || document.querySelector('.via-reader-body'); if (!reader || !eligible()) return false; savedAtTop = reader.scrollTop <= 1; saved = capture(); originalScroll = { x: window.scrollX, y: window.scrollY }; if (state.tocOpen) closeCatalogue(); if (state.progressOpen) closeProgressPanel(); if (state.shelfOpen) closeShelfPanel(); if (saved) { recordProgress(saved); syncVisiblePage(readUrl(saved.url)); } exited = true; reopening = false; quiet = false; state.autoEntryDone = true; stopReaderRequests(); if (nativeReaderObserver) { nativeReaderObserver.disconnect(); nativeReaderObserver = null; } boundReader = null; document.querySelectorAll('.via-reader-body').forEach(function (node) { node.remove(); }); restoreSiteShell(); bindReader(); alignOriginalSource(saved); updateFloat(); return true; } function reenter() { if (!exited || reopening || !eligible()) return false; exited = false; reopening = true; quiet = true; if (floatButton) floatButton.remove(); state.autoEntryDone = false; installReaderStyle(); requestAutoReader(); return true; } function onBind(reader) { if (!reader || !eligible()) return false; exited = false; if (boundReader !== reader) { boundReader = reader; reader.addEventListener('wheel', function () { quiet = false; }, { passive: true }); reader.addEventListener('keydown', function () { quiet = false; }, { passive: true }); if (nativeReaderObserver) nativeReaderObserver.disconnect(); nativeReaderObserver = new MutationObserver(function () { var style = window.getComputedStyle(reader); if (reader.hidden || style.display === 'none' || style.visibility === 'hidden' || style.visibility === 'collapse') { onMissingReader(); } }); nativeReaderObserver.observe(reader, { attributes: true, attributeFilter: ['hidden', 'style', 'class'] }); } if (floatButton) floatButton.remove(); if (!reopening) { // Via's own menu can explicitly open a fresh native reader after a // close. A valid reader bind is that user's re-entry action too. exited = false; quiet = false; return false; } exited = false; reopening = false; markProgressParagraphs(); if (saved) { if (savedAtTop) { reader.scrollTop = 0; syncVisiblePage(readUrl(saved.url)); } else restoreProgressHere(saved); recordProgress(saved); } // Preserve the underlying page position when another close occurs. if (originalScroll && typeof window.scrollTo === 'function') window.scrollTo(originalScroll.x, originalScroll.y); return true; } function onMissingReader() { var style = boundReader && boundReader.isConnected && window.getComputedStyle(boundReader); if (boundReader && (!boundReader.isConnected || boundReader.hidden || style && (style.display === 'none' || style.visibility === 'hidden' || style.visibility === 'collapse'))) { saved = normalizeProgress(state.savedProgress); savedAtTop = boundReader.scrollTop <= 1; var removed = boundReader; boundReader = null; exited = true; reopening = false; quiet = false; if (nativeReaderObserver) { nativeReaderObserver.disconnect(); nativeReaderObserver = null; } state.autoEntryDone = true; stopReaderRequests(); restoreSiteShell(); if (removed.isConnected) { removed.remove(); bindReader(); } } updateFloat(); } function guard(event) { var target = event.target; if (!target || !target.closest) return false; var control = target.closest('[data-bm-reader-enter],[data-bm-reader-exit]'); if (control) { if (control.hasAttribute('data-bm-reader-enter') && /^(?:pointerdown|mousedown|touchstart)$/.test(event.type)) beginDrag(event); if (event.type === 'click') { event.preventDefault(); if (control.hasAttribute('data-bm-reader-enter')) { if (Date.now() >= suppressClickUntil) reenter(); } else exit(); } event.stopImmediatePropagation(); return true; } if (quiet && boundReader && boundReader.contains(target) && /^(?:pointerdown|mousedown|touchstart)$/.test(event.type)) quiet = false; return false; } return { exit: exit, reenter: reenter, guard: guard, onBind: onBind, onMissingReader: onMissingReader, updateFloat: updateFloat, isExited: function () { return exited; }, allowPaging: function () { return !exited && !quiet; }, getButton: function () { return floatButton; }, getFloatPosition: function () { return floatPosition && { x: floatPosition.x, y: floatPosition.y }; } }; })(); // END READER EXIT / RE-ENTRY 1.9.8 var bookProgress = (function () { var key = 'bm-reader-book-count-' + bookId + '-v1'; var lifetime = 6 * 60 * 60 * 1000; var metadata = null, line = null, panelLine = null, toolbarLine = null, controller = null; var generation = 0, pending = null, waitTimer = null, waitResolve = null; var lastDirectoryAt = 0, retryAt = 0, error = '', lastValue = null; var catalogueTimes = new WeakMap(); function validMetadata(value) { return !!value && value.v === 1 && value.book === String(bookId) && Number.isInteger(value.maxGroup) && value.maxGroup >= 1 && value.maxGroup <= 100 && Number.isInteger(value.finalCount) && value.finalCount >= 1 && value.finalCount <= 100 && value.total === (value.maxGroup - 1) * 100 + value.finalCount && Number.isFinite(value.time) && value.time > 0 && value.time <= Date.now(); } try { var stored = JSON.parse(localStorage.getItem(key) || 'null'); if (validMetadata(stored) && Date.now() - stored.time < lifetime) metadata = stored; } catch (_) {} function calculate(order, fraction, total) { if (!Number.isInteger(order) || !Number.isInteger(total) || order < 1 || total < 1 || order > total || !Number.isFinite(fraction) || fraction < 0 || fraction > 1) return null; return Math.max(0, Math.min(100, ((order - 1) + fraction) / total * 100)); } function observeCatalogue(data, cp) { if (!data || !Number.isInteger(cp) || cp < 1 || cp > 100 || !Number.isInteger(data.max) || data.max !== cp || !Array.isArray(data.chapters) || !data.chapters.length || data.chapters.length > 100) return false; var seen = new Set(); for (var index = 0; index < data.chapters.length; index++) { var chapter = data.chapters[index]; var url = chapter && chapter.url && readUrl(chapter.url.href); if (!url || url.search || seen.has(url.pathname) || chapter.order !== (cp - 1) * 100 + index + 1) return false; seen.add(url.pathname); } var observedAt = catalogueTimes.get(data) || Date.now(); catalogueTimes.set(data, observedAt); if (metadata && (metadata.maxGroup > cp || (metadata.maxGroup === cp && metadata.finalCount > data.chapters.length))) return false; if (metadata && metadata.time >= observedAt && metadata.maxGroup >= cp && (metadata.maxGroup > cp || metadata.finalCount === data.chapters.length)) return true; metadata = { v: 1, book: String(bookId), maxGroup: cp, finalCount: data.chapters.length, total: (cp - 1) * 100 + data.chapters.length, time: observedAt }; try { localStorage.setItem(key, JSON.stringify(metadata)); } catch (_) {} error = ''; return true; } function readCachedCatalogues() { var max = 0; state.tocCache.forEach(function (data) { if (data && Number.isInteger(data.max)) { if (!catalogueTimes.has(data)) catalogueTimes.set(data, Date.now()); max = Math.max(max, data.max); } }); if (max && state.tocCache.has(max)) observeCatalogue(state.tocCache.get(max), max); return max; } function currentPoint() { var url = currentReadingUrl(); if (!url || !activeReader()) return null; var all = state.content.querySelectorAll('p[data-bm-progress-url]'); var selected = null, selectedBounds = null; var viewport = state.reader.getBoundingClientRect(); var anchor = viewport.top + Math.min(120, state.reader.clientHeight * .22); var paragraphs = [], wantedKey=pageKey(url), addressMatches=new Map(); for (var index = 0; index < all.length; index++) { var paragraph = all[index]; if (paragraph.hidden || paragraph.closest('[hidden], .readPage, .page2-chapter')) continue; var address=paragraph.getAttribute('data-bm-progress-url'); if(address!==url.href){ if(!addressMatches.has(address)){var parsed=readUrl(address);addressMatches.set(address,!!parsed&&pageKey(parsed)===wantedKey);} if(!addressMatches.get(address))continue; } var bounds = paragraph.getBoundingClientRect(); if (!(bounds.height > 0)) continue; paragraphs.push(paragraph); if (!selected || (selectedBounds.bottom <= anchor)) { selected = paragraph; selectedBounds = bounds; } } var fractionOnPage = 0; if (selected && paragraphs.length) { var withinParagraph = Math.max(0, Math.min(1, (anchor - selectedBounds.top) / selectedBounds.height)); fractionOnPage = (paragraphs.indexOf(selected) + withinParagraph) / paragraphs.length; var finalParagraph = all.length && all[all.length - 1]; if (finalParagraph === paragraphs[paragraphs.length - 1] && state.reader.scrollHeight - state.reader.scrollTop - state.reader.clientHeight <= 2) fractionOnPage = 1; } var model = pageModelForUrl(url), number = progressPage(url), total = model && model.pageTotal || 0; var models = (state.first ? [state.first] : []).concat(state.pages || []); for (var part = 0; part < models.length; part++) { var candidate = models[part]; if (!candidate || !candidate.url || candidate.url.pathname !== url.pathname) continue; if (candidate.pageTotal >= number) total = Math.max(total, candidate.pageTotal); else if (candidate.next && candidate.next.pathname !== url.pathname && !candidate.nextIssue && progressPage(candidate.url) >= number) total = Math.max(total, progressPage(candidate.url)); else if (!candidate.next && !candidate.nextIssue && metadata && state.chapterOrders.get(url.pathname) === metadata.total && progressPage(candidate.url) >= number) total = Math.max(total, progressPage(candidate.url)); } var fraction = total >= number ? ((number - 1) + fractionOnPage) / total : 0; return { url: url.href, order: state.chapterOrders.get(url.pathname) || 0, fraction: fraction, pageFraction: fractionOnPage, partTotalKnown: total >= number }; } function update() { readCachedCatalogues(); var point = currentPoint(); var total = metadata && metadata.total || 0; var percent = point ? calculate(point.order, point.fraction, total) : null; var shown = percent === null ? '--.-' : Math.min(percent < 100 ? 99.9 : 100, Number(percent.toFixed(1))).toFixed(1); lastValue = { percent: percent, text: '整本进度 ' + shown + '%', order: point && point.order || 0, total: total, partTotalKnown: !!(point && point.partTotalKnown), pending: !!pending, error: error }; var detail = '按目录章节顺序与章内分页估算,并非字数百分比。' + (point && !point.partTotalKnown ? '章内总页数未确认,当前按章节起点计算。' : '') + (percent === null ? '等待确认当前章节在目录中的顺序和整本章数。' : '第 ' + point.order + ' / ' + total + ' 章。') + (error ? '目录信息暂不可用,阅读不受影响。' : ''); [line, panelLine, toolbarLine].forEach(function (element) { if (!element || !element.isConnected) return; var text=element.hasAttribute('data-bm-progress-bare') ? shown + '%' : lastValue.text + (percent === null ? '(待确认)' : ''); var label=lastValue.text+'。'+detail; if(element.textContent!==text)element.textContent=text; if(element.getAttribute('title')!==detail)element.setAttribute('title',detail); if(element.getAttribute('aria-label')!==label)element.setAttribute('aria-label',label); }); return lastValue; } function mount(container, isPanel, isToolbar) { if (!container) return null; var attribute = isToolbar ? 'data-bm-book-progress-toolbar' : isPanel ? 'data-bm-book-progress-panel' : 'data-bm-book-progress'; var element = container.querySelector('[' + attribute + ']'); if (!element) { element = document.createElement(isToolbar ? 'span' : 'div'); element.setAttribute(attribute, ''); element.style.cssText = 'font-size:12px;line-height:1.5;color:inherit;opacity:.75;padding:3px 0;text-align:center;'; container.appendChild(element); } if (isToolbar) toolbarLine = element; else if (isPanel) panelLine = element; else line = element; update(); return element; } function allowed() { return !!bookId && activeReader() && !document.hidden && !state.busy && !state.recovering && !state.blocked && !state.tocOpen && !state.progressOpen && !state.shelfOpen && !scriptDownloader.isOpen() && !scriptDownloader.isRequesting() && !(captchaAutomation.isOpen() || (siteAccount && siteAccount.isOpen())); } function cancelled() { var result = new Error('整本进度查询已取消'); result.bmBookProgressCancelled = true; return result; } function check(token, readSequence) { if (token !== generation || readSequence !== state.readSeq || !allowed()) throw cancelled(); } async function waitForGap(token, readSequence) { check(token, readSequence); var gap = Math.max(0, 2100 - (Date.now() - Math.max(lastDirectoryAt, state.lastRequest || 0))); if (gap) await new Promise(function (resolve) { waitResolve = resolve; waitTimer = setTimeout(function () { waitTimer = null; waitResolve = null; resolve(); }, gap); }); check(token, readSequence); } async function fetchGroup(cp, token, readSequence) { await waitForGap(token, readSequence); var url = new URL('/book/' + bookId + '.html', origin); if (cp > 1) url.searchParams.set('cp', String(cp)); var localController = typeof AbortController === 'function' ? new AbortController() : null; controller = localController; lastDirectoryAt = state.lastRequest = Date.now(); var timeout = null; try { var doc = await Promise.race([(async function () { var response = await fetch(url.href, { credentials: 'same-origin', redirect: 'manual', signal: localController ? localController.signal : undefined }); check(token, readSequence); if (response.type === 'opaqueredirect' || (response.status >= 300 && response.status < 400)) throw new Error('目录需要验证'); if (!response.ok) throw new Error('目录加载失败'); if (response.url) { var final = siteUrl(response.url); if (!final || final.pathname !== url.pathname || final.search !== url.search) throw new Error('目录响应地址异常'); } var text = await response.text(); check(token, readSequence); var template = document.createElement('template'); template.innerHTML = text; return template.content; })(), new Promise(function (_, reject) { timeout = setTimeout(function () { if (localController) localController.abort(); reject(new Error('目录查询超时')); }, 15000); })]); check(token, readSequence); var current = doc.querySelector('#chapters .page-range-list .cur'); var range = current && current.textContent.trim().match(/^(\d+)\s*[-—~]\s*(\d+)$/); var actualCp = range ? Math.floor((Number(range[1]) - 1) / 100) + 1 : cp; if (!(actualCp >= 1 && actualCp <= 100)) throw new Error('目录组号无效'); var data = parseCatalogue(doc, url, actualCp); state.tocCache.set(actualCp, data); observeCatalogue(data, actualCp); return { cp: actualCp, data: data }; } finally { clearTimeout(timeout); if (controller === localController) controller = null; } } async function ensure() { update(); if (!allowed() || pending || Date.now() < retryAt) return pending || false; var current = currentReadingUrl(); if (!current) return false; var order = state.chapterOrders.get(current.pathname); var cacheMax = readCachedCatalogues(); if (order && metadata && Date.now() - metadata.time < lifetime && order <= metadata.total) return true; var token = ++generation, readSequence = state.readSeq; var hint = order ? Math.floor((order - 1) / 100) + 1 : state.tocPage; if (!order && hint === 1) { var model = pageModelForUrl(current); var numberHint = String(model ? model.title : '').match(/^第\s*(\d+)\s*章/); var saved = state.savedProgress && readUrl(state.savedProgress.url); var savedOrder = saved && saved.pathname === current.pathname && Number(state.savedProgress.order); if (Number.isInteger(savedOrder) && savedOrder > 0 && savedOrder <= 10000) hint = Math.floor((savedOrder - 1) / 100) + 1; else if (numberHint && Number(numberHint[1]) >= 1 && Number(numberHint[1]) <= 10000) hint = Math.floor((Number(numberHint[1]) - 1) / 100) + 1; } var queue = [hint, 1], visited = new Set(), fetched = 0; if (cacheMax > 1) queue.push(cacheMax); if (metadata && metadata.maxGroup > 1) queue.push(metadata.maxGroup); pending = Promise.resolve().then(async function () { try { while (queue.length && fetched < 3) { check(token, readSequence); var cp = queue.shift(); if (!Number.isInteger(cp) || cp < 1 || cp > 100 || visited.has(cp)) continue; visited.add(cp); var cached = state.tocCache.get(cp), result; if (cached && Date.now() - (catalogueTimes.get(cached) || 0) >= lifetime) cached = null; if (cached) { result = { cp: cp, data: cached }; observeCatalogue(cached, cp); } else { fetched++; result = await fetchGroup(cp, token, readSequence); } visited.add(result.cp); var max = Math.max(result.data.max || 0, metadata && metadata.maxGroup || 0); if (Number.isInteger(max) && max >= 1 && max <= 100 && !visited.has(max)) queue.unshift(max); var knownOrder = state.chapterOrders.get(current.pathname); if (knownOrder && metadata && Date.now() - metadata.time < lifetime && knownOrder <= metadata.total) break; } retryAt = Date.now() + 60000; return !!metadata; } catch (failure) { if (!failure.bmBookProgressCancelled && token === generation) { error = '目录信息暂未确认'; retryAt = Date.now() + 60000; } return false; } finally { if (token === generation) { pending = null; update(); } } }); update(); return pending; } function stop() { generation++; if (controller) { controller.abort(); controller = null; } if (waitTimer) { clearTimeout(waitTimer); waitTimer = null; } if (waitResolve) { var resolve = waitResolve; waitResolve = null; resolve(); } pending = null; } return { mount: mount, mountPanel: function (container) { return mount(container, true); }, mountToolbar: function (container) { return mount(container, false, true); }, update: update, ensure: ensure, stop: stop, observeCatalogue: observeCatalogue, calculate: calculate, snapshot: function () { return lastValue || update(); } }; })(); // BEGIN READER WIDGET 1.9.18 var readerWidget = (function () { var node=null, button=null, drag=null, idle=null, dragTimer=null, position=null; var docked=false, edge='right', clickFence=0, touchFence=0, uiHoldUntil=0, wakeUntil=0, readingIntentUntil=0; var key='bm-reader-widget-position-v1', lastSaved='', lastSkin='', paintFrame=null; try { lastSaved=localStorage.getItem(key)||'';var saved=JSON.parse(lastSaved||'null'); if(saved&&Number.isFinite(saved.x)&&Number.isFinite(saved.y)&&saved.x>=0&&saved.x<=1&&saved.y>=0&&saved.y<=1){ position={x:saved.x,y:saved.y};docked=!!saved.docked;edge=saved.edge==='left'?'left':'right'; } } catch (_) {} function bounds(){var v=window.visualViewport;return{left:v&&v.offsetLeft||0,top:v&&v.offsetTop||0,width:v&&v.width||innerWidth,height:v&&v.height||innerHeight};} function remember(){if(!position)return;var value=JSON.stringify({x:position.x,y:position.y,docked:docked,edge:edge});if(value===lastSaved)return;try{localStorage.setItem(key,value);lastSaved=value;}catch(_) {}} function paint(){ if(!node||!node.isConnected)return; var b=bounds(),p=position||{x:1,y:1},width=docked?48:80; var x=docked?(edge==='left'?b.left+8:b.left+b.width-width-8):b.left+8+p.x*Math.max(0,b.width-width-16); var y=b.top+8+p.y*Math.max(0,b.height-64); if(node.style.left!==x+'px')node.style.left=x+'px';if(node.style.top!==y+'px')node.style.top=y+'px'; node.style.right=node.style.bottom='auto'; var skin=(docked?'edge:'+edge:'chip')+':'+currentTheme;if(skin===lastSkin)return;lastSkin=skin; node.setAttribute('data-bm-widget-docked',String(docked)); button.style.width=width+'px';button.style.minWidth=width+'px';button.style.height='48px'; button.style.padding=docked?'0':'0 10px'; button.style.borderRadius=docked?(edge==='left'?'0 15px 15px 0':'15px 0 0 15px'):'999px'; button.style.opacity=docked?'1':'.65'; button.style.setProperty('background-color',docked?'transparent':themeFor(currentTheme).bar,'important'); button.style.border=docked?'0':'1px solid rgba(95,85,70,.35)';button.style.boxShadow=docked?'none':'0 2px 6px rgba(0,0,0,.1)'; button.style.justifyContent=docked?(edge==='left'?'flex-start':'flex-end'):'center'; button.setAttribute('aria-label','点一下打开阅读设置;可拖动'); button.title='阅读设置、书架与工具'; var icon=button.querySelector('[data-bm-widget-icon]'); if(icon){icon.textContent=docked?(edge==='left'?'›':'‹'):'☰'; icon.style.cssText=docked?'display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:14px;height:24px;border:1px solid currentColor;border-radius:7px;font:16px/1 sans-serif;opacity:.22;pointer-events:none;':'opacity:1;pointer-events:none;'; } var percent=button.querySelector('[data-bm-book-progress]');if(percent)percent.style.display=docked?'none':''; } function requestPaint(){if(paintFrame!==null)return;paintFrame=requestAnimationFrame(function(){paintFrame=null;paint();});} function cancelPaint(){if(paintFrame!==null){cancelAnimationFrame(paintFrame);paintFrame=null;}} function readingNow(){return activeReader()&&!state.tocOpen&&!state.progressOpen&&!state.shelfOpen&&!scriptDownloader.isOpen()&&!(captchaAutomation.isOpen() || (siteAccount && siteAccount.isOpen()));} function canIdle(){return node&&node.isConnected&&toolbarCollapsed&&readingNow();} function armIdle(){ clearTimeout(idle);idle=null; if(canIdle()&&!docked&&!drag)idle=setTimeout(function(){idle=null;if(canIdle()&&!drag)hide();},Math.max(2500,wakeUntil-Date.now())); } function readingScroll(){ if(!node||!node.isConnected||drag||(docked&&toolbarCollapsed)||Date.now()readingIntentUntil)return; if(Date.now()readingIntentUntil)return; if(!docked||!toolbarCollapsed)hide(); } function readingGesture(event){ var target=event.target; if(docked&&toolbarCollapsed||drag||!target||!target.closest||!target.closest('.via-reader-content')||target.closest('button,a,input,textarea,select'))return; if(!readingNow())return; if(event.type==='keydown'&&!/^(ArrowDown|ArrowUp|PageDown|PageUp|Home|End| )$/.test(event.key))return; readingIntentUntil=Date.now()+1200;readingScroll(); } function hide(){ if(!button||!node.isConnected||docked&&toolbarCollapsed)return; wakeUntil=0;if(!toolbarCollapsed)setToolbarCollapsed(true); var b=bounds(),r=node.getBoundingClientRect();edge=r.left+r.width/2144)drag.moved=true; if(drag.moved){ if(e.cancelable)e.preventDefault();docked=false;wakeUntil=0; var b=bounds(),x=Math.max(8,Math.min(b.width-88,drag.left-b.left+dx)),y=Math.max(8,Math.min(b.height-56,drag.top-b.top+dy)); position={x:Math.max(0,Math.min(1,(x-8)/Math.max(1,b.width-96))),y:Math.max(0,Math.min(1,(y-8)/Math.max(1,b.height-64)))};paint(); } e.stopImmediatePropagation(); } function finish(e){ if(!matching(e))return; var current=drag,p=point(e,current.touchId),cancelled=/cancel$/.test(e.type); var dx=p?p.x-current.x:Infinity,dy=p?p.y-current.y:Infinity; var moved=current.moved||dx*dx+dy*dy>144; var tap=!cancelled&&!moved&&Date.now()-current.at<=800; resetDrag();clickFence=Date.now()+750; if(current.type==='touchstart'||e.pointerType==='touch')touchFence=Date.now()+800; if(e.cancelable)e.preventDefault(); if(tap)activate();else {paint();remember();armIdle();} e.stopImmediatePropagation(); } function guard(e){ var target=e.target;if(!target||!target.closest)return false; var hideControl=target.closest('[data-bm-widget-hide]'),handle=target.closest('[data-bm-toolbar-expand]'); if(!hideControl&&!handle){if(e.type==='touchmove')readingGesture(e);return false;} if(handle&&handle!==button)return false; if(handle&&/^(pointerdown|touchstart|mousedown)$/.test(e.type))begin(e); if(e.type==='click'){ e.preventDefault(); if(hideControl)hide(); else if(Date.now()>=clickFence)activate(); } e.stopImmediatePropagation();return true; } ['pointermove','touchmove','mousemove'].forEach(function(type){window.addEventListener(type,move,{capture:true,passive:false});}); ['pointerup','pointercancel','touchend','touchcancel','mouseup'].forEach(function(type){window.addEventListener(type,finish,{capture:true,passive:false});}); window.addEventListener('lostpointercapture',function(e){if(drag&&drag.id===e.pointerId){resetDrag();clickFence=Date.now()+750;armIdle();}},{capture:true}); window.addEventListener('blur',resetDrag); document.addEventListener('visibilitychange',function(){if(document.hidden)resetDrag();}); window.addEventListener('pagehide',function(){resetDrag();cancelPaint();clearTimeout(idle);idle=null;}); window.addEventListener('wheel',readingGesture,{capture:true,passive:true}); window.addEventListener('touchmove',readingGesture,{capture:true,passive:true}); window.addEventListener('keydown',function(e){if(button&&e.target===button&&/^(Enter| )$/.test(e.key)){resetDrag();clickFence=0;}},{capture:true}); window.addEventListener('keydown',readingGesture,{passive:true}); window.addEventListener('resize',requestPaint,{passive:true}); if(window.visualViewport){window.visualViewport.addEventListener('resize',requestPaint,{passive:true});window.visualViewport.addEventListener('scroll',requestPaint,{passive:true});} return{mount:mount,guard:guard,toolbarChanged:toolbarChanged,armIdle:armIdle,hide:hide,isDocked:function(){return docked;}}; })(); // END READER WIDGET 1.9.18 // END READER CONTROLS 1.9.3 function installSearchResultLinks() { if (pageWindow.__bmSearchLinksInstalled) return; pageWindow.__bmSearchLinksInstalled = true; window.addEventListener('click', function (event) { if (!event.target || !event.target.closest) return; var card = event.target.closest('.book-card'); if (!card) return; var raw = ''; var handler = card.getAttribute('onclick') || ''; var opened = handler.match(/^\s*window\.open\(\s*(['"])([^'"]*)\1\s*(?:,\s*(['"])_blank\3\s*)?\)\s*;?\s*$/); if (handler && !opened) return; if (opened) raw = opened[2]; else { var link = event.target.closest('a[href]') || card.querySelector('a[href]'); if (link) raw = link.getAttribute('href'); } var searchTarget = currentSearchBook(raw); if (!searchTarget) return; event.preventDefault(); event.stopImmediatePropagation(); location.assign(searchTarget.href); }, true); } function currentSearchBook(value) { try { var wrapper = new URL(value, location.href); var raw = value; if (wrapper.origin === location.origin && wrapper.searchParams.get('action') === 'go') { var encoded = wrapper.searchParams.get('t'); if (!encoded || encoded.length > 2048) return null; raw = atob(encoded); } var logo = document.querySelector('.logo-text'); var boluomaoSource = !!logo && /^菠萝猫$/.test(logo.textContent.trim()); if (/^\/\d+\.html$/.test(raw)) { if (!boluomaoSource) return null; raw = 'https://www.boluomao1.com/book' + raw; } else if (/^\/(?:book|read)\//.test(raw)) { if (!boluomaoSource) return null; raw = 'https://www.boluomao1.com' + raw; } var target = new URL(raw); if (target.protocol !== 'https:' || target.port || target.username || target.password || !/^(?:www\.)?boluomao(?:1)?\.com$/.test(target.hostname) || !/^\/(?:book\/\d+|read\/\d+\/\d+)\.html$/.test(target.pathname)) return null; var valid = true; target.searchParams.forEach(function (item, key) { if (!/^(?:p|cp)$/.test(key) || !/^[1-9]\d*$/.test(item)) valid = false; }); if (!valid) return null; target.hostname = 'www.boluomao1.com'; return target; } catch (_) { return null; } } function openReaderSearch() { if (activeReader()) saveProgressNow(true); searchNavigationUntil = Date.now() + 5000; location.assign(searchEntry.href); } async function openReadingVerification(url, sequence, recovery) { if (!activeReader() || !url || !/^\/(?:book|read)\//.test(url.pathname) || url.origin !== location.origin || !(await captchaAutomation.isEnabled())) return false; checkReadSequence(sequence, recovery); if (!activeReader() || state.tocOpen || state.progressOpen || state.shelfOpen || (captchaAutomation.isOpen() || (siteAccount && siteAccount.isOpen()))) return false; checkReadSequence(sequence, recovery); if (!activeReader() || state.tocOpen || state.progressOpen || state.shelfOpen || (captchaAutomation.isOpen() || (siteAccount && siteAccount.isOpen()))) return false; checkReadSequence(sequence, recovery); if (!activeReader() || state.tocOpen || state.progressOpen || state.shelfOpen || (captchaAutomation.isOpen() || (siteAccount && siteAccount.isOpen()))) return false; checkReadSequence(sequence, recovery); if (!activeReader() || state.tocOpen || state.progressOpen || state.shelfOpen || (captchaAutomation.isOpen() || (siteAccount && siteAccount.isOpen()))) return false; checkReadSequence(sequence, recovery); if (!activeReader() || state.tocOpen || state.progressOpen || state.shelfOpen || (captchaAutomation.isOpen() || (siteAccount && siteAccount.isOpen()))) return false; checkReadSequence(sequence, recovery); if (!activeReader() || state.tocOpen || state.progressOpen || state.shelfOpen || (captchaAutomation.isOpen() || (siteAccount && siteAccount.isOpen()))) return false; saveProgressNow(true); location.assign(url.href); return true; } function intentionalSearchNavigation(value) { try { var target = new URL(value); return Date.now() <= searchNavigationUntil && target.href === searchEntry.href; } catch (_) { return false; } } var paperImages = { amber: 'data:image/jpeg;base64,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', sage: 'data:image/jpeg;base64,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' }; var themes = [ { id: 'ivory', label: '米白', bg: '#faf6eb', text: '#333333', bar: 'rgba(250,246,235,.96)' }, { id: 'amber', label: '仿 Alook 暖纸', bg: '#e0c5a2', text: '#333333', bar: 'rgba(224,197,162,.96)', image: paperImages.amber, tint: 'rgba(221,196,165,.8)' }, { id: 'sage', label: '浅绿纸', bg: '#d1efd7', text: '#2e3931', bar: 'rgba(209,239,215,.96)', image: paperImages.sage, tint: 'rgba(209,239,215,.45)' }, { id: 'night', label: '夜间', bg: '#141517', text: '#dededc', bar: 'rgba(20,21,23,.96)' } ]; var purifier=(function(){"use strict";var key="bm-reader-purify-rules-v1",rules=[],selected="",selectedParagraph="",panel=null;var originals=new WeakMap,sequence=0,worker=null,watchdog=null,workerUrl=null;var workerScript="onmessage=function(e){try{var d=e.data,r=d.rules,o=[],hits=0;for(var i=0;i=0&&typeof r.pattern==="string"&&r.pattern.length>0&&r.pattern.length<=300&&typeof r.enabled==="boolean"&&typeof r.book==="string"}).slice(0,50)}catch(_){rules=[]}}load();function save(){try{localStorage.setItem(key,JSON.stringify(rules));return true}catch(_){notice("规则无法保存,现有正文未更改。");return false}}function active(){return rules.filter(function(r){return r.enabled&&(r.book===bookId||r.book==="*")})}function capture(){var s=window.getSelection&&window.getSelection();if(!s||s.isCollapsed||!s.rangeCount||!state.content)return;var range=s.getRangeAt(0),a=range.startContainer,b=range.endContainer;if(!state.content.contains(a)||!state.content.contains(b))return;var text=String(s).trim();if(!text||text.length>300)return;selected=text;var el=a.nodeType===1?a:a.parentElement,p=el&&el.closest("p[data-bm-progress-index]");selectedParagraph=p&&p.contains(b)?(originals.get(p)||{}).text||p.textContent.trim():""}document.addEventListener("selectionchange",capture);function notice(text){if(panel){var n=panel.querySelector("[data-purify-status]");if(n)n.textContent=text}}function cancel(){sequence++;clearTimeout(watchdog);watchdog=null;if(worker){worker.terminate();worker=null}if(workerUrl){URL.revokeObjectURL(workerUrl);workerUrl=null}}function calculate(texts,inputRules,done){cancel();var token=sequence;if(!inputRules.length){done(null,{texts:texts,hits:0});return}if(!inputRules.some(function(r){return r.enabled&&r.type==="regex"})){var hits=0,plain=texts.map(function(value){var result=value;inputRules.forEach(function(r){if(!r.enabled)return;if(r.type==="paragraph"){if(result.trim()===r.pattern.trim())result=""}else result=result.split(r.pattern).join("")});if(result!==value)hits++;return result});done(null,{texts:plain,hits:hits});return}if(!window.Worker||!window.Blob||!window.URL||!URL.createObjectURL){done("当前浏览器无法安全运行规则,正文保持原样。");return}try{workerUrl=URL.createObjectURL(new Blob([workerScript],{type:"text/javascript"}));worker=new Worker(workerUrl)}catch(_){cancel();done("无法启动规则计算,正文保持原样。");return}var current=worker;function finish(error,result){if(sequence!==token)return;cancel();done(error,result)}watchdog=setTimeout(function(){finish("规则运行超时,请缩小范围或修改正则。")},1e3);current.onerror=function(){finish("规则运行失败,正文保持原样。")};current.onmessage=function(e){var d=e.data;if(!d||d.error||!Array.isArray(d.texts)||d.texts.length!==texts.length){finish(d&&d.error||"规则结果无效。");return}finish(null,d)};current.postMessage({texts:texts,rules:inputRules})}function paragraphs(){return state.content?Array.from(state.content.querySelectorAll("p[data-bm-progress-index]")):[]}function original(p){var x=originals.get(p);if(!x){x={html:p.innerHTML,text:p.textContent,display:p.style.getPropertyValue("display"),priority:p.style.getPropertyPriority("display"),changed:false};originals.set(p,x)}return x}function restore(p){var x=originals.get(p);if(!x||!x.changed)return;p.innerHTML=x.html;if(x.display)p.style.setProperty("display",x.display,x.priority);else p.style.removeProperty("display");x.changed=false}function refresh(){var ps=paragraphs(),rs=active(),text=ps.map(function(p){return original(p).text});if(!ps.length)return;calculate(text,rs,function(err,result){if(err){notice(err);return}ps.forEach(function(p,i){if(!p.isConnected||!state.content||!state.content.contains(p))return;var x=original(p),value=result.texts[i];if(value===x.text){restore(p);return}if(x.changed&&p.textContent===value)return;p.textContent=value;x.changed=true;if(!value.trim())p.style.setProperty("display","none","important");else if(x.display)p.style.setProperty("display",x.display,x.priority);else p.style.removeProperty("display")})})}function button(text,attr){var b=document.createElement("button");b.type="button";b.textContent=text;b.setAttribute(attr,"");b.style.cssText="padding:9px 12px;border:1px solid currentColor;border-radius:8px;background:transparent;color:inherit;font:inherit;margin:3px;";return b}function field(label,name,tag){var l=document.createElement("label");l.textContent=label;l.style.cssText="display:block;margin:10px 0 4px;font-size:14px;";var x=document.createElement(tag||"textarea");x.setAttribute(name,"");x.style.cssText="display:block;box-sizing:border-box;width:100%;padding:8px;font:16px/1.4 sans-serif;color:#222;background:#fff;";l.appendChild(x);panel.firstChild.appendChild(l);return x}function makePanel(){panel=document.createElement("div");panel.setAttribute("data-purify-panel","");panel.hidden=true;panel.style.cssText="position:fixed;inset:0;z-index:2147483000;overflow:auto;background:#f8f4e9;color:#292929;padding:16px;box-sizing:border-box;font:16px/1.5 sans-serif;";var box=document.createElement("div");box.style.cssText="max-width:560px;margin:auto;";panel.appendChild(box);var title=document.createElement("h2");title.textContent="正文净化";box.appendChild(title);var tips=document.createElement("p");tips.textContent="长按选中正文,再打开本页;先预览,确认后保存。规则默认只对这本书生效。";tips.style.fontSize="14px";box.appendChild(tips);var type=field("匹配方式","data-purify-type","select");[["literal","选中文字"],["paragraph","整段文字"],["regex","正则表达式"]].forEach(function(x){var o=document.createElement("option");o.value=x[0];o.textContent=x[1];type.appendChild(o)});var input=field("需要屏蔽的内容或正则","data-purify-input");input.rows=3;input.maxLength=300;var scope=field("作用范围","data-purify-scope","select");[["book","当前书"],["all","本站所有书"]].forEach(function(x){var o=document.createElement("option");o.value=x[0];o.textContent=x[1];scope.appendChild(o)});box.appendChild(button("使用刚才选中的文字","data-purify-selection"));box.appendChild(button("使用选中段落","data-purify-paragraph"));box.appendChild(button("预览","data-purify-preview"));box.appendChild(button("保存规则","data-purify-save"));box.appendChild(button("返回阅读","data-purify-close"));var status=document.createElement("p");status.setAttribute("data-purify-status","");status.setAttribute("role","status");box.appendChild(status);var pre=document.createElement("pre");pre.setAttribute("data-purify-result","");pre.style.cssText="white-space:pre-wrap;overflow-wrap:anywhere;font:14px/1.6 sans-serif;";box.appendChild(pre);var transfer=document.createElement("details"),summary=document.createElement("summary");summary.textContent="导入/导出规则";transfer.appendChild(summary);var help=document.createElement("p");help.textContent="支持本功能导出的 JSON,以及无替换内容、无指定范围的阅读(Legado)净化规则。导入后默认停用,请先测试。";help.style.fontSize="13px";transfer.appendChild(help);var area=document.createElement("textarea");area.setAttribute("data-purify-transfer","");area.rows=4;area.placeholder="粘贴规则 JSON,或点导出后复制";area.style.cssText="box-sizing:border-box;width:100%;padding:8px;font:14px/1.4 monospace;color:#222;background:#fff;";transfer.appendChild(area);transfer.appendChild(button("导出规则文本","data-purify-export"));transfer.appendChild(button("导入规则文本","data-purify-import"));box.appendChild(transfer);var list=document.createElement("div");list.setAttribute("data-purify-list","");box.appendChild(list);state.reader.appendChild(panel)}function render(){var list=panel.querySelector("[data-purify-list]");list.textContent="";var heading=document.createElement("h3");heading.textContent="已保存的规则";list.appendChild(heading);rules.forEach(function(r,i){if(r.book!=="*"&&r.book!==bookId)return;var row=document.createElement("div");row.style.cssText="border-top:1px solid #ccc;padding:8px 0;overflow-wrap:anywhere;";var text=document.createElement("span");text.textContent=(r.enabled?"启用":"已停用")+" · "+(r.book==="*"?"所有书":"本书")+" · "+r.type+":"+r.pattern;row.appendChild(text);var edit=button("测试","data-purify-edit");edit.setAttribute("data-purify-index",String(i));row.appendChild(edit);var toggle=button(r.enabled?"停用":"启用","data-purify-toggle");toggle.setAttribute("data-purify-index",String(i));row.appendChild(toggle);var remove=button("删除","data-purify-delete");remove.setAttribute("data-purify-index",String(i));row.appendChild(remove);list.appendChild(row)})}function open(){if(!state.reader)return;if(!panel||!panel.isConnected)makePanel();panel.hidden=false;panel.style.display="block";if(selected)panel.querySelector("[data-purify-input]").value=selected;render();notice("可先预览当前已加载正文中的匹配结果。")}function installEntry(grid){var b=document.createElement("button");b.type="button";b.textContent="正文净化";b.setAttribute("data-purify-open","");grid.appendChild(b)}function candidate(){var type=panel.querySelector("[data-purify-type]").value,pattern=panel.querySelector("[data-purify-input]").value;if(!pattern.trim()||pattern.length>300)throw Error("请输入不超过 300 字的内容。");return{type:type,pattern:pattern,book:panel.querySelector("[data-purify-scope]").value==="all"?"*":bookId,enabled:true}}var previewKey="";function preview(){var r;try{r=candidate()}catch(e){notice(e.message);return}var ps=paragraphs(),texts=ps.map(function(p){return original(p).text});if(!texts.length){notice("当前没有可预览的正文。");return}notice("正在预览…");calculate(texts,[r],function(err,res){if(err){previewKey="";notice(err);return}previewKey=JSON.stringify(r);var i=res.texts.findIndex(function(t,j){return t!==texts[j]});var output=panel.querySelector("[data-purify-result]");output.textContent=i<0?"当前已加载正文没有匹配。":"原文:"+texts[i].slice(0,400)+"\n\n净化后:"+(res.texts[i].slice(0,400)||"(整段隐藏)");notice("匹配到 "+res.hits+" 段;确认无误后点保存。")})}function guard(event){var target=event.target;if(!target||!target.closest)return false;var openButton=target.closest("[data-purify-open]"),inside=target.closest("[data-purify-panel]");if(!openButton&&!inside)return false;event.stopImmediatePropagation();if(event.type==="click"){var b=target.closest("button");if(openButton){event.preventDefault();open()}else if(b){event.preventDefault();if(b.hasAttribute("data-purify-close")){panel.hidden=true;panel.style.display="none";closeProgressPanel()}else if(b.hasAttribute("data-purify-selection")||b.hasAttribute("data-purify-paragraph")){var para=b.hasAttribute("data-purify-paragraph");panel.querySelector("[data-purify-type]").value=para?"paragraph":"literal";panel.querySelector("[data-purify-input]").value=para?selectedParagraph:selected;previewKey="";notice((para?selectedParagraph:selected)?"已载入,请先预览。":"请先在正文中长按选中文字。")}else if(b.hasAttribute("data-purify-preview"))preview();else if(b.hasAttribute("data-purify-export")){var area=panel.querySelector("[data-purify-transfer]");area.value=JSON.stringify(rules.filter(function(r){return r.book==="*"||r.book===bookId}),null,2);area.focus();area.select();notice("已生成规则文本,可长按复制保存;不包含阅读进度。")}else if(b.hasAttribute("data-purify-import")){try{var raw=panel.querySelector("[data-purify-transfer]").value;if(raw.length>5e4)throw Error("规则文本不能超过 5 万字。");var data=JSON.parse(raw);if(!Array.isArray(data)||data.length>50)throw Error("需要不超过 50 条规则的 JSON 数组。");var added=0,skipped=0,book=panel.querySelector("[data-purify-scope]").value==="all"?"*":bookId,previous=rules.slice();data.forEach(function(item){if(!item||typeof item!=="object"||typeof item.pattern!=="string"||!item.pattern.trim()||item.pattern.length>300||rules.length>=50||item.replacement!=null&&item.replacement!==""||item.scope||item.excludeScope||item.scopeContent===false){skipped++;return}var type=item.type;if(["literal","paragraph","regex"].indexOf(type)<0){if(typeof item.isRegex!=="boolean"||item.replacement!=null&&item.replacement!==""||item.scope||item.excludeScope||item.scopeContent===false){skipped++;return}type=item.isRegex?"regex":"literal"}if(rules.some(function(r){return r.type===type&&r.pattern===item.pattern&&r.book===book})){skipped++;return}rules.push({type:type,pattern:item.pattern,book:book,enabled:false});added++});if(!save()){rules=previous;return true}render();notice("导入 "+added+" 条,跳过 "+skipped+" 条;新规则已停用,请测试后启用。")}catch(_){notice("导入失败:请检查 JSON 格式和规则数量。")}}else if(b.hasAttribute("data-purify-save")){var r;try{r=candidate();if(previewKey!==JSON.stringify(r))throw Error("请先预览当前规则。");if(rules.length>=50)throw Error("最多保存 50 条规则。");rules.push(r);if(!save()){rules.pop();return true}previewKey="";render();refresh();notice("已保存,当前书的正文会重新净化。")}catch(e){notice(e.message)}}else{var index=Number(b.getAttribute("data-purify-index"));if(Number.isInteger(index)&&rules[index]){var previous=rules.slice(),old=Object.assign({},rules[index]);if(b.hasAttribute("data-purify-edit")){panel.querySelector("[data-purify-type]").value=old.type;panel.querySelector("[data-purify-input]").value=old.pattern;panel.querySelector("[data-purify-scope]").value=old.book==="*"?"all":"book";preview();return true}if(b.hasAttribute("data-purify-toggle"))rules[index]=Object.assign({},old,{enabled:!old.enabled});else if(b.hasAttribute("data-purify-delete"))rules.splice(index,1);if(!save()){rules=previous;return true}render();refresh();notice("规则已更新,正文可恢复。")}}}}event.stopImmediatePropagation();return true}window.addEventListener("pagehide",cancel);return{installEntry:installEntry,guard:guard,refresh:refresh,isOpen:function(){return!!panel&&!panel.hidden}}})(); var themeKey = 'bm-reader-theme-v1'; var currentTheme = 'amber'; try { currentTheme = localStorage.getItem(themeKey) || 'amber'; } catch (_) {} if (!themes.some(function (theme) { return theme.id === currentTheme; })) currentTheme = 'amber'; var toolbarKey = 'bm-reader-toolbar-collapsed-v1'; var toolbarCollapsed = false; try { toolbarCollapsed = localStorage.getItem(toolbarKey) === '1'; } catch (_) {} var tocPageKey = 'bm-reader-catalog-page-' + bookId; var progressKey = 'bm-reader-progress-' + bookId; var historyKey = 'bm-reader-progress-history-' + bookId + '-v1'; var chapterOrderKey = 'bm-reader-chapter-order-' + bookId + '-v1'; var chapterTitleKey = 'bm-reader-chapter-title-' + bookId + '-v1'; var pendingKey = 'bm-reader-pending-resume-' + bookId; try { var savedCp = Number(localStorage.getItem(tocPageKey)); if (savedCp >= 1 && savedCp <= 100 && savedCp % 1 === 0) state.tocPage = savedCp; } catch (_) {} if (bookId) { try { var storedOrders = JSON.parse(localStorage.getItem(chapterOrderKey) || '[]'); if (Array.isArray(storedOrders)) storedOrders.forEach(function (item) { if (Array.isArray(item) && /^\/read\/\d+\/\d+\.html$/.test(item[0]) && Number.isInteger(item[1]) && item[1] > 0 && item[1] <= 100000) { state.chapterOrders.set(item[0], item[1]); } }); } catch (_) {} try { var storedTitles = JSON.parse(localStorage.getItem(chapterTitleKey) || '[]'); if (Array.isArray(storedTitles)) storedTitles.forEach(function (item) { if (Array.isArray(item) && typeof item[0] === 'string' && typeof item[1] === 'string') { state.chapterTitles.set(item[0], cleanChapterTitle(item[1])); } }); } catch (_) {} } try { state.savedProgress = JSON.parse(localStorage.getItem(progressKey) || 'null'); } catch (_) {} if (bookId) loadProgressHistory(); try { state.pendingResume = JSON.parse(sessionStorage.getItem(pendingKey) || 'null'); } catch (_) {} var fontKey = 'bm-reader-font-size-v1'; var fontSize = 22; var fontTouch = { button: null, at: 0 }; var themeTouch = { button: null, at: 0 }; try { var savedFont = Number(localStorage.getItem(fontKey)); if (savedFont >= 16 && savedFont <= 34 && savedFont % 1 === 0) fontSize = savedFont; } catch (_) {} function applyFontSize(size) { var anchor = captureFontAnchor(); fontSize = Math.max(16, Math.min(34, size)); try { persistTextIfChanged(fontKey, String(fontSize)); } catch (_) {} if (state.content) state.content.style.setProperty('font-size', fontSize + 'px', 'important'); restoreFontAnchor(anchor); if (!state.toolbar) return; var value = state.toolbar.querySelector('[data-bm-font-value]'); if (value) { value.textContent = String(fontSize); value.setAttribute('aria-label', '当前字号 ' + fontSize + ' 像素'); } var buttons = state.toolbar.querySelectorAll('[data-bm-font-step]'); for (var i = 0; i < buttons.length; i++) { var step = Number(buttons[i].getAttribute('data-bm-font-step')); buttons[i].disabled = step < 0 ? fontSize <= 16 : fontSize >= 34; buttons[i].style.setProperty('opacity', buttons[i].disabled ? '.4' : '1', 'important'); } } function captureFontAnchor() { if (!activeReader() || !state.reader.scrollTop) return null; var anchorY = state.reader.getBoundingClientRect().top + Math.min(120, state.reader.clientHeight * .22); var paragraphs = state.content.querySelectorAll('p[data-bm-progress-index]'); for (var i = 0; i < paragraphs.length; i++) { var rect = paragraphs[i].getBoundingClientRect(); if (!(rect.height > 0) || rect.bottom <= anchorY) continue; return { node: paragraphs[i], y: anchorY, ratio: Math.max(0, Math.min(1, (anchorY - rect.top) / rect.height)) }; } return null; } function restoreFontAnchor(anchor) { if (!anchor || !anchor.node.isConnected || !activeReader()) return; var rect = anchor.node.getBoundingClientRect(); if (!(rect.height > 0)) return; var delta = rect.top + rect.height * anchor.ratio - anchor.y; if (Number.isFinite(delta)) state.reader.scrollTop += delta; } function setToolbarCollapsed(collapsed) { var visibleAnchor=captureFontAnchor(); var previous = toolbarCollapsed; toolbarCollapsed = !!collapsed; try { persistTextIfChanged(toolbarKey, toolbarCollapsed ? '1' : '0'); } catch (_) {} if (!state.toolbar || !state.toolbarHandle) return; var top = state.reader ? state.reader.scrollTop : 0; var height = state.toolbar.getBoundingClientRect().height || state.toolbar.offsetHeight || 48; state.toolbar.hidden = toolbarCollapsed; state.toolbar.style.setProperty('display', toolbarCollapsed ? 'none' : 'flex', 'important'); state.toolbarHandle.hidden = !toolbarCollapsed; state.toolbarHandle.style.setProperty('display', toolbarCollapsed ? 'flex' : 'none', 'important'); readerWidget.toolbarChanged(); if (state.reader && previous !== toolbarCollapsed) { if (visibleAnchor) { restoreFontAnchor(visibleAnchor); } else if (toolbarCollapsed) state.reader.scrollTop = Math.max(0, top - height); else if (top > 0) state.reader.scrollTop = top + (state.toolbar.getBoundingClientRect().height || state.toolbar.offsetHeight || height); } if (previous && !toolbarCollapsed) bookProgress.update(); } function themeFor(id) { for (var i = 0; i < themes.length; i++) if (themes[i].id === id) return themes[i]; return themes[1]; } function themeBackground(theme) { if (!theme.image) return 'none'; var image = 'url("' + theme.image + '")'; return theme.tint ? 'linear-gradient(' + theme.tint + ',' + theme.tint + '),' + image : image; } function applyTheme(id){var theme=themeFor(id);currentTheme=theme.id;try{persistTextIfChanged(themeKey,currentTheme)}catch(_){}if(!state.reader||!state.paper)return;state.reader.setAttribute("data-bm-reader-theme",theme.id);state.reader.style.setProperty("background-color",theme.bg,"important");state.reader.style.setProperty("color",theme.text,"important");state.paper.style.setProperty("background-color",theme.bg,"important");state.paper.style.setProperty("background-image",themeBackground(theme),"important");state.toolbar.style.setProperty("background-color",theme.bar,"important");var buttons=state.toolbar.querySelectorAll("[data-bm-theme-choice]");for(var i=0;i= 1000) return; library.books.push({ id: libraryBookId, name: name, favorite: false, createdAt: Date.now(), updatedAt: Date.now() }); } localStorage.setItem(libraryKey, JSON.stringify(library)); } catch (_) { /* Reading remains available if local metadata cannot be saved. */ } } function latestForShelf(id) { if (!/^\d{1,18}$/.test(id)) throw new Error('书籍编号不正确。'); var value = backupStoredJson('bm-reader-progress-' + id, null); return backupPosition(value, id, new Map(), new Map()); } function shelfBooks() { var library = loadLibrary(); var entries = new Map(); library.books.forEach(function (book) { entries.set(book.id, Object.assign({}, book)); }); for (var i = 0; i < localStorage.length; i++) { var match = /^bm-reader-progress-(\d{1,18})$/.exec(localStorage.key(i)); if (match && !entries.has(match[1])) entries.set(match[1], { id: match[1], name: '', favorite: false, createdAt: 0, updatedAt: 0 }); } if (siteAccount) siteAccount.decorateShelf(entries); var books = []; entries.forEach(function (entry) { try { entry.latest = latestForShelf(entry.id); } catch (_) { entry.latest = null; entry.error = '阅读记录暂不可读取,原记录已保留。'; } if (entry.favorite || entry.latest || entry.siteRecent || entry.error) books.push(entry); }); return books.sort(function (a, b) { return (b.latest ? b.latest.time : b.siteRecent ? b.siteRecent.time : 0) - (a.latest ? a.latest.time : a.siteRecent ? a.siteRecent.time : 0) || b.updatedAt - a.updatedAt; }); } function flushShelfProgress() { if (activeReader() && (state.progressTouched || !normalizeProgress(state.savedProgress))) saveProgressNow(true); } function favoriteShelfBook(id, force) { try { if (!/^\d{1,18}$/.test(id || '')) throw new Error('当前页面没有可收藏的书籍。'); flushShelfProgress(); var library = loadLibrary(); var entry = library.books.find(function (book) { return book.id === id; }); if (!entry) { if (library.books.length >= 1000) throw new Error('书架记录超过 1000 本上限,现有记录已保留。'); entry = { id: id, name: id === libraryBookId ? currentBookName() : '', favorite: false, createdAt: Date.now(), updatedAt: Date.now() }; library.books.push(entry); } entry.favorite = typeof force === 'boolean' ? force : !entry.favorite; if (id === libraryBookId && currentBookName()) entry.name = currentBookName(); entry.updatedAt = Date.now(); localStorage.setItem(libraryKey, JSON.stringify(validateBackupLibrary(library))); if (siteAccount) siteAccount.favoriteChanged(id, entry.favorite, entry.updatedAt); updateFavoriteButtons(); if (state.shelfOpen) { renderShelf(); setShelfStatus(entry.favorite ? '已收藏,可从书架回到最后阅读位置。' : '已取消收藏,阅读进度仍保留。'); } } catch (error) { openShelfPanel(); setShelfStatus(error.message || '收藏保存失败,现有记录已保留。'); } } function updateFavoriteButtons() { var favorite = false; try { favorite = loadLibrary().books.some(function (entry) { return entry.id === libraryBookId && entry.favorite; }); } catch (_) {} var buttons = document.querySelectorAll('[data-bm-shelf-add]'); for (var i = 0; i < buttons.length; i++) { var button = buttons[i]; var icon = button.hasAttribute('data-bm-shelf-add-icon'); button.textContent = icon ? (favorite ? '★' : '☆') : (favorite ? '★ 已收藏' : '☆ 收藏本书'); button.setAttribute('aria-pressed', String(favorite)); button.setAttribute('aria-label', favorite ? '取消收藏本书' : '收藏本书'); button.title = favorite ? '已收藏,点击取消收藏' : '收藏本书'; } } function applyShelfTheme() { if (!state.shelfPanel) return; var theme = themes.find(function (entry) { return entry.id === currentTheme; }) || themes[0]; state.shelfPanel.style.setProperty('background-color', theme.bg, 'important'); state.shelfPanel.style.setProperty('background-image', themeBackground(theme), 'important'); state.shelfPanel.style.setProperty('color', theme.text, 'important'); var header = state.shelfPanel.querySelector('[data-bm-shelf-header]'); if (header) header.style.setProperty('background-color', theme.bar, 'important'); } function installShelfPanel(parent){if(state.shelfPanel&&state.shelfPanel.isConnected&&state.shelfPanel.parentElement===parent)return;if(state.shelfPanel)state.shelfPanel.remove();var panel=document.createElement("div");panel.setAttribute("data-bm-shelf-panel","");panel.setAttribute("role","dialog");panel.setAttribute("aria-label","我的书架");panel.hidden=true;panel.style.cssText="position:fixed;inset:0;z-index:2147483646;width:100%;height:100%;box-sizing:border-box;"+"overflow-y:auto;overscroll-behavior:contain;background-position:center;background-size:cover;"+"background-repeat:no-repeat;font-size:14px;line-height:1.6;text-align:left;";panel.style.setProperty("display","none","important");var header=document.createElement("div");header.setAttribute("data-bm-shelf-header","");header.style.cssText="position:sticky;top:0;z-index:1;display:flex;justify-content:space-between;align-items:center;"+"padding:8px 12px;min-height:52px;box-sizing:border-box;border-bottom:1px solid rgba(128,128,128,.25);";var title=document.createElement("strong");title.textContent="我的书架";title.style.fontSize="16px";var close=document.createElement("button");close.type="button";close.setAttribute("data-bm-shelf-close","");close.textContent=activeReader()?"返回阅读 ×":"返回页面 ×";close.style.cssText="padding:6px 8px;border:1px solid currentColor;border-radius:5px;background:none;color:inherit;font-size:13px;";header.appendChild(title);header.appendChild(close);panel.appendChild(header);var accountRow=document.createElement("div");accountRow.style.cssText="padding:10px 12px 0;";if(siteAccount)siteAccount.installEntry(accountRow);panel.appendChild(accountRow);var current=document.createElement("div");current.style.cssText="padding:12px 12px 0;";var label=document.createElement("div");label.setAttribute("data-bm-shelf-current-name","");label.textContent="当前:"+(currentBookName()||"书籍 "+libraryBookId);current.appendChild(label);var add=document.createElement("button");add.type="button";add.setAttribute("data-bm-shelf-add","");add.style.cssText="padding:6px 10px;margin-top:6px;border:1px solid currentColor;border-radius:5px;background:none;color:inherit;font-size:14px;";current.appendChild(add);panel.appendChild(current);var status=document.createElement("div");status.setAttribute("data-bm-shelf-status","");status.setAttribute("role","status");status.style.cssText="padding:8px 12px;font-size:12px;";panel.appendChild(status);var list=document.createElement("div");list.setAttribute("data-bm-shelf-list","");list.style.cssText="padding:0 12px 24px;";panel.appendChild(list);parent.appendChild(panel);state.shelfPanel=panel;state.shelfStatus=status;state.shelfList=list;applyShelfTheme();updateFavoriteButtons()} function setShelfStatus(message) { if (state.shelfStatus) state.shelfStatus.textContent = message || ''; } function openShelfPanel() { bookProgress.stop(); if (!state.shelfPanel) installShelfPanel(state.reader || document.body); if (!state.shelfPanel || state.shelfOpen) return; flushShelfProgress(); cancelAutoRecovery(); if (state.tocOpen) closeCatalogue(); if (state.progressOpen) closeProgressPanel(); if (state.progressTimer) { clearTimeout(state.progressTimer); state.progressTimer = null; } if (state.timer) { clearTimeout(state.timer); state.timer = null; } state.retryPending = false; updateStatus(); state.shelfOpen = true; state.shelfSeq++; state.shelfLimit = 30; state.shelfBodyOverflow = document.body.style.overflow; document.body.style.overflow = 'hidden'; state.shelfPanel.hidden = false; state.shelfPanel.style.setProperty('display', 'block', 'important'); state.shelfPanel.scrollTop = 0; renderShelf(); applyShelfTheme(); } function closeShelfPanel() { setTimeout(function(){bookProgress.update();bookProgress.ensure();readerWidget.armIdle();},0); if (!state.shelfPanel) return; state.shelfOpen = false; state.shelfSeq++; stopCatalogueRequest(); state.shelfPanel.hidden = true; state.shelfPanel.style.setProperty('display', 'none', 'important'); if (state.shelfBodyOverflow !== null) document.body.style.overflow = state.shelfBodyOverflow; state.shelfBodyOverflow = null; setShelfStatus(''); schedule(); } function renderShelf(){if(!state.shelfList)return;state.shelfList.textContent="";try{var books=shelfBooks();var more=false;[["已收藏",true],["最近阅读",false]].forEach(function(group){var selected=books.filter(function(entry){return entry.favorite===group[1]});var heading=document.createElement("h3");heading.textContent=group[0]+"("+selected.length+")";heading.style.cssText="font-size:16px;margin:14px 0 8px;";state.shelfList.appendChild(heading);if(!selected.length){var empty=document.createElement("div");empty.style.opacity=".75";empty.textContent=group[1]?"点工具栏的 ☆,即可收藏正在读的书。":"读过的书会在这里显示最后阅读位置。";state.shelfList.appendChild(empty);return}if(selected.length>state.shelfLimit)more=true;selected.slice(0,state.shelfLimit).forEach(function(entry){var card=document.createElement("div");card.setAttribute("data-bm-shelf-book",entry.id);card.style.cssText="padding:12px 0;border-top:1px solid rgba(128,128,128,.25);overflow-wrap:anywhere;";var name=document.createElement("strong");name.textContent=entry.name||"书籍 "+entry.id;name.style.cssText="display:block;font-size:16px;";card.appendChild(name);var point=document.createElement("div");point.style.cssText="font-size:13px;opacity:.8;padding:4px 0 8px;";point.textContent=entry.error||(entry.latest?"最后读到:"+(entry.latest.title||"已保存章节")+" · 第"+progressPage(new URL(entry.latest.url))+"页 · 第"+(entry.latest.index+1)+"段":entry.siteRecent?"网站最近阅读:"+(entry.siteRecent.title||"已保存章节")+"(章节级,本机暂无段落位置)":"还没有阅读位置,点击打开目录。");card.appendChild(point);var row=document.createElement("div");row.style.cssText="display:flex;gap:8px;";var read=document.createElement("button");read.type="button";read.setAttribute("data-bm-shelf-read",entry.id);read.textContent=entry.latest?"继续阅读":entry.siteRecent?"继续网站章节":"打开目录";read.disabled=!!entry.error;var favorite=document.createElement("button");favorite.type="button";favorite.setAttribute("data-bm-shelf-favorite",entry.id);favorite.textContent=entry.favorite?"取消收藏":"收藏";[read,favorite].forEach(function(button){button.style.cssText="padding:6px 10px;border:1px solid currentColor;border-radius:5px;background:none;color:inherit;font-size:14px;";row.appendChild(button)});card.appendChild(row);state.shelfList.appendChild(card)})});if(more){var button=document.createElement("button");button.type="button";button.textContent="显示更多";button.setAttribute("data-bm-shelf-more","");button.style.cssText="padding:8px 12px;margin-top:12px;";state.shelfList.appendChild(button)}setShelfStatus("本机段落位置优先续读;网站最近阅读按章节续读。网站账号可查看签到与同步,本机数据仍可用阅读备份保存。");updateFavoriteButtons()}catch(error){setShelfStatus(error.message||"暂时无法读取书架,原记录已保留。")}} function navigateShelf(value) { location.assign(value); } async function readShelfBook(id) { if (!state.shelfOpen) return; var token = ++state.shelfSeq; stopCatalogueRequest(); try { var latest = latestForShelf(id); if (bookId === id && activeReader() && latest) { if (restoreProgressHere(latest)) { var position = state.lastRestoreTarget || latest; closeShelfPanel(); position.time = Date.now(); recordProgress(position); syncVisiblePage(readUrl(position.url)); return; } setShelfStatus('正在恢复最后阅读位置…'); var url = readUrl(latest.url); var doc = await fetchInertPage(url, '书架续读'); if (!state.shelfOpen || token !== state.shelfSeq) return; var page = extract(doc, url); if (!state.shelfOpen || token !== state.shelfSeq) return; showCatalogueChapter(page, latest); return; } if (!/^\d{1,18}$/.test(id || '')) throw new Error('书籍编号不正确。'); var remote = !latest && siteAccount ? siteAccount.getRemoteResume(id) : null; var target = latest ? backupReadUrl(latest.url, id) : remote ? backupReadUrl(remote.url, id) : new URL('/book/' + id + '.html#chapters', origin); if (!target) throw new Error('保存的阅读地址不正确,当前阅读已保留。'); if (latest) sessionStorage.setItem('bm-reader-pending-resume-' + id, JSON.stringify(latest)); navigateShelf(target.href); } catch (error) { if (state.shelfOpen && token === state.shelfSeq) setShelfStatus(error.message || '无法恢复阅读位置,当前阅读已保留。'); } } function installBookShelfActions() { rememberLibraryBook(); var target = document.querySelector('.bookDetail .book > .btnW') || document.querySelector('#chapters'); if (!target || target.querySelector('[data-bm-library-entry]')) return; var row = document.createElement('div'); row.setAttribute('data-bm-library-entry', ''); row.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;padding:8px 0;justify-content:center;'; [['add', '☆ 收藏本书'], ['open', '我的书架']].forEach(function (item) { var button = document.createElement('button'); button.type = 'button'; button.textContent = item[1]; button.setAttribute('data-bm-shelf-' + item[0], ''); button.style.cssText = 'padding:8px 12px;border:1px solid currentColor;border-radius:5px;font-size:14px;'; row.appendChild(button); }); scriptDownloader.installEntry(row); if (siteAccount) siteAccount.installEntry(row); target.appendChild(row); installShelfPanel(document.body); updateFavoriteButtons(); } var backupLibraryKey = 'bm-reader-library-v1'; function validateBackupLibrary(value) { if (!backupObject(value) || value.version !== 1 || !Array.isArray(value.books) || value.books.length > 1000) { backupError('书架备份格式不正确;现有书架和阅读进度未修改。'); } var ids = new Set(); var books = value.books.map(function (entry) { if (!backupObject(entry) || typeof entry.id !== 'string' || !/^\d{1,18}$/.test(entry.id) || ids.has(entry.id) || typeof entry.name !== 'string' || entry.name.length > 120 || typeof entry.favorite !== 'boolean' || typeof entry.createdAt !== 'number' || !Number.isFinite(entry.createdAt) || entry.createdAt < 0 || entry.createdAt > 8640000000000000 || typeof entry.updatedAt !== 'number' || !Number.isFinite(entry.updatedAt) || entry.updatedAt < 0 || entry.updatedAt > 8640000000000000) { backupError('书架备份内容不正确;现有书架和阅读进度未修改。'); } ids.add(entry.id); return { id: entry.id, name: entry.name, favorite: entry.favorite, createdAt: entry.createdAt, updatedAt: entry.updatedAt }; }); return { version: 1, books: books }; } function collectBackupLibrary() { return validateBackupLibrary(backupStoredJson(backupLibraryKey, { version: 1, books: [] })); } function mergeBackupLibrary(local, incoming) { local = validateBackupLibrary(local); incoming = validateBackupLibrary(incoming); var books = new Map(); local.books.forEach(function (entry) { books.set(entry.id, entry); }); incoming.books.forEach(function (entry) { var previous = books.get(entry.id); if (!previous) { books.set(entry.id, entry); return; } var winner = previous.updatedAt >= entry.updatedAt ? previous : entry; books.set(entry.id, { id: entry.id, name: winner.name, favorite: winner.favorite, createdAt: Math.min(previous.createdAt, entry.createdAt), updatedAt: winner.updatedAt }); }); if (books.size > 1000) backupError('合并后的书架超过 1000 本上限;现有书架和进度未修改。'); return { version: 1, books: Array.from(books.values()).sort(function (a, b) { return b.updatedAt - a.updatedAt || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0); }) }; } function attachValidatedBackupLibrary(source, checked) { if (Object.prototype.hasOwnProperty.call(source, 'library')) checked.library = validateBackupLibrary(source.library); return checked; } function appendBackupLibraryWrites(checked, writes) { if (!Object.prototype.hasOwnProperty.call(checked, 'library')) return null; var merged = mergeBackupLibrary(collectBackupLibrary(), checked.library); writes.set(backupLibraryKey, JSON.stringify(merged)); return merged; } var backupMaxBytes = 5 * 1024 * 1024; var backupFormat = 'boluomao-via-reader-backup'; function backupObject(value) { return !!value && typeof value === 'object' && !Array.isArray(value); } function backupInteger(value, minimum, maximum) { return typeof value === 'number' && Number.isInteger(value) && value >= minimum && value <= maximum; } function backupError(message) { throw new Error(message || '备份内容格式不正确,未修改现有进度。'); } function backupReadUrl(value, id) { if (typeof value !== 'string' || value.length > 500) return null; try { var url = new URL(value); if (url.protocol !== 'https:' || url.origin !== origin || url.username || url.password || url.hash || !new RegExp('^/read/' + id + '/\\d+\\.html$').test(url.pathname)) return null; var valid = true; var pages = 0; url.searchParams.forEach(function (item, key) { if (key !== 'p' || !/^[1-9]\d*$/.test(item) || Number(item) > 10000 || ++pages > 1) valid = false; }); if (!valid) return null; if (url.searchParams.get('p') === '1') url.searchParams.delete('p'); return url; } catch (_) { return null; } } function backupPosition(value, id, orders, titles) { if (value === null || typeof value === 'undefined') return null; if (!backupObject(value)) backupError(); var url = backupReadUrl(value.url, id); if (!url || !backupInteger(value.index, 0, 100000) || typeof value.ratio !== 'number' || !Number.isFinite(value.ratio) || value.ratio < 0 || value.ratio > 1 || typeof value.time !== 'number' || !Number.isFinite(value.time) || value.time < 0 || value.time > 8640000000000000 || (typeof value.order !== 'undefined' && !backupInteger(value.order, 0, 100000)) || (typeof value.title !== 'undefined' && (typeof value.title !== 'string' || value.title.length > 120))) backupError(); return { url: url.href, index: value.index, ratio: value.ratio, time: value.time, order: orders.get(url.pathname) || value.order || 0, title: titles.get(url.pathname) || cleanChapterTitle(value.title || '').slice(0, 120) }; } function backupMap(value, id, kind) { if (!Array.isArray(value) || value.length > 100000) backupError(); var map = new Map(); value.forEach(function (entry) { if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== 'string' || !new RegExp('^/read/' + id + '/\\d+\\.html$').test(entry[0]) || map.has(entry[0])) backupError(); if (kind === 'orders') { if (!backupInteger(entry[1], 1, 100000)) backupError(); map.set(entry[0], entry[1]); } else { if (typeof entry[1] !== 'string' || entry[1].length > 120) backupError(); map.set(entry[0], cleanChapterTitle(entry[1])); } }); return map; } function validateBackupBook(value) { if (!backupObject(value) || typeof value.id !== 'string' || !/^\d{1,18}$/.test(value.id) || !backupObject(value.history) || !Array.isArray(value.history.recent) || value.history.recent.length > 1000 || !backupInteger(value.catalogPage, 1, 100)) backupError(); var orders = backupMap(value.chapterOrders, value.id, 'orders'); var titles = backupMap(value.chapterTitles, value.id, 'titles'); return { id: value.id, latest: backupPosition(value.latest, value.id, orders, titles), history: { furthest: backupPosition(value.history.furthest, value.id, orders, titles), recent: value.history.recent.map(function (entry) { var position = backupPosition(entry, value.id, orders, titles); if (!position) backupError(); return position; }) }, chapterOrders: Array.from(orders), chapterTitles: Array.from(titles), catalogPage: value.catalogPage }; } function validateBackup(value) { if (!backupObject(value) || value.format !== backupFormat || value.schemaVersion !== 1 || !backupObject(value.settings) || !Array.isArray(value.books) || value.books.length > 1000 || !themes.some(function (theme) { return theme.id === value.settings.theme; }) || !backupInteger(value.settings.fontSize, 16, 34) || typeof value.settings.toolbarCollapsed !== 'boolean') { backupError('这不是支持的阅读备份,或备份版本不兼容;现有进度未修改。'); } var ids = new Set(); var books = value.books.map(function (book) { var checked = validateBackupBook(book); if (ids.has(checked.id)) backupError(); ids.add(checked.id); return checked; }); return attachValidatedBackupLibrary(value, { format: backupFormat, schemaVersion: 1, scriptVersion: '1.9.19', exportedAt: typeof value.exportedAt === 'string' && value.exportedAt.length <= 40 ? value.exportedAt : '', settings: { theme: value.settings.theme, fontSize: value.settings.fontSize, toolbarCollapsed: value.settings.toolbarCollapsed }, books: books }); } function backupStoredJson(key, fallback) { var text = localStorage.getItem(key); if (text === null) return fallback; try { return JSON.parse(text); } catch (_) { backupError('已有阅读记录损坏,备份操作已停止;现有记录未修改。'); } } function collectBackupBook(id) { var prefix = 'bm-reader-'; var latest = backupStoredJson(prefix + 'progress-' + id, null); var history = backupStoredJson(prefix + 'progress-history-' + id + '-v1', { furthest: null, recent: [] }); var orders = backupStoredJson(prefix + 'chapter-order-' + id + '-v1', []); var titles = backupStoredJson(prefix + 'chapter-title-' + id + '-v1', []); var pageText = localStorage.getItem(prefix + 'catalog-page-' + id); var page = pageText === null ? 1 : Number(pageText); return validateBackupBook({ id: id, latest: latest, history: history, chapterOrders: orders, chapterTitles: titles, catalogPage: page }); } function collectBackup() { var ids = new Set(); var library = collectBackupLibrary(); library.books.forEach(function (book) { ids.add(book.id); }); for (var i = 0; i < localStorage.length; i++) { var key = localStorage.key(i); var found; if ((found = /^bm-reader-progress-(\d{1,18})$/.exec(key)) || (found = /^bm-reader-progress-history-(\d{1,18})-v1$/.exec(key)) || (found = /^bm-reader-chapter-(?:order|title)-(\d{1,18})-v1$/.exec(key)) || (found = /^bm-reader-catalog-page-(\d{1,18})$/.exec(key))) ids.add(found[1]); } if (ids.size > 1000) backupError('书籍记录超过备份上限,未生成文件。'); return validateBackup({ format: backupFormat, schemaVersion: 1, scriptVersion: '1.9.19', library: library, exportedAt: new Date().toISOString(), settings: { theme: currentTheme, fontSize: fontSize, toolbarCollapsed: toolbarCollapsed }, books: Array.from(ids).sort().map(collectBackupBook) }); } function backupWithin(first, second, id) { var a = backupReadUrl(first.url, id); var b = backupReadUrl(second.url, id); if (a.pathname !== b.pathname) return null; return progressPage(a) - progressPage(b) || first.index - second.index || first.ratio - second.ratio; } function mergeBackupBook(local, incoming) { var id = incoming.id; var orders = new Map(incoming.chapterOrders); var titles = new Map(incoming.chapterTitles); local.chapterOrders.forEach(function (entry) { orders.set(entry[0], entry[1]); }); local.chapterTitles.forEach(function (entry) { if (entry[1]) titles.set(entry[0], entry[1]); }); function normalize(entry) { return backupPosition(entry, id, orders, titles); } var latest = !local.latest ? incoming.latest : !incoming.latest || local.latest.time >= incoming.latest.time ? local.latest : incoming.latest; var recent = new Map(); [local.history.furthest, incoming.history.furthest, local.latest, incoming.latest] .concat(local.history.recent, incoming.history.recent).forEach(function (entry) { if (!entry) return; var position = normalize(entry); var path = backupReadUrl(position.url, id).pathname; var old = recent.get(path); if (!old) { recent.set(path, position); return; } var winner = backupWithin(position, old, id) > 0 ? position : old; winner.time = Math.max(position.time, old.time); recent.set(path, winner); }); var furthest = normalize(local.history.furthest || local.latest || incoming.history.furthest || incoming.latest); recent.forEach(function (entry) { if (!furthest) { furthest = entry; return; } var within = backupWithin(entry, furthest, id); if ((within !== null && within > 0) || (within === null && entry.order && furthest.order && entry.order > furthest.order)) { furthest = entry; } }); return { id: id, latest: normalize(latest), history: { furthest: furthest, recent: Array.from(recent.values()).sort(function (a, b) { return b.time - a.time; }).slice(0, 1000) }, chapterOrders: Array.from(orders), chapterTitles: Array.from(titles), catalogPage: local.catalogPage || incoming.catalogPage }; } function backupWrites(book, writes) { var id = book.id; writes.set('bm-reader-progress-' + id, JSON.stringify(book.latest)); writes.set('bm-reader-progress-history-' + id + '-v1', JSON.stringify(book.history)); writes.set('bm-reader-chapter-order-' + id + '-v1', JSON.stringify(book.chapterOrders)); writes.set('bm-reader-chapter-title-' + id + '-v1', JSON.stringify(book.chapterTitles)); writes.set('bm-reader-catalog-page-' + id, String(book.catalogPage)); } function applyBackupData(data){var checked=validateBackup(data);var writes=new Map;var merged=[];checked.books.forEach(function(incoming){var local=collectBackupBook(incoming.id);var result=mergeBackupBook(local,incoming);if(localStorage.getItem("bm-reader-catalog-page-"+incoming.id)===null)result.catalogPage=incoming.catalogPage;backupWrites(result,writes);merged.push(result)});writes.set(themeKey,checked.settings.theme);writes.set(fontKey,String(checked.settings.fontSize));writes.set(toolbarKey,checked.settings.toolbarCollapsed?"1":"0");var restoredLibrary=appendBackupLibraryWrites(checked,writes);var before=new Map;writes.forEach(function(_,key){before.set(key,localStorage.getItem(key))});var applied=[];try{writes.forEach(function(value,key){localStorage.setItem(key,value);applied.push(key)})}catch(_){var rollbackFailed=false;applied.reverse().forEach(function(key){try{var old=before.get(key);if(old===null)localStorage.removeItem(key);else localStorage.setItem(key,old)}catch(ignored){rollbackFailed=true}});backupError(rollbackFailed?"存储空间不足,部分记录无法回滚;请保留备份文件并释放浏览器存储后重试。":"存储空间不足,恢复已撤销;现有记录未修改。")}for(var i=0;i backupMaxBytes || new Blob([text]).size > backupMaxBytes) backupError('备份超过 5 MB 上限,未生成文件。'); if (state.backupText) state.backupText.value = text; if (!window.URL || !URL.createObjectURL) { toggleBackupText(true); setBackupStatus('此浏览器不支持文件下载,请复制下面的备份文本保存。'); return; } if (state.backupDownloadUrl) URL.revokeObjectURL(state.backupDownloadUrl); if (state.backupDownloadAnchor) state.backupDownloadAnchor.remove(); var url = URL.createObjectURL(new Blob([text], { type: 'application/json;charset=utf-8' })); var anchor = document.createElement('a'); anchor.href = url; anchor.download = '菠萝猫阅读备份-' + new Date().toISOString().slice(0, 10) + '.json'; anchor.style.display = 'none'; state.backupDownloadAnchor = anchor; state.backupDownloadUrl = url; document.body.appendChild(anchor); anchor.click(); setBackupStatus('已生成 ' + data.books.length + ' 本书的备份。若手机未下载文件,点“备份文本”复制保存。'); setTimeout(function () { if (state.backupDownloadAnchor === anchor) { state.backupDownloadAnchor = null; state.backupDownloadUrl = null; } anchor.remove(); URL.revokeObjectURL(url); }, 60000); } catch (error) { setBackupStatus(error.message || '备份生成失败。'); } } function restoreBackupText() { try { var text = state.backupText ? state.backupText.value.trim() : ''; if (!text || text.length > backupMaxBytes || new Blob([text]).size > backupMaxBytes) backupError('请选择或粘贴不超过 5 MB 的阅读备份。'); var data; try { data = JSON.parse(text); } catch (_) { backupError('备份不是有效 JSON;现有进度未修改。'); } applyBackupData(data); } catch (error) { setBackupStatus(error.message || '备份恢复失败,现有记录未修改。'); } } function importBackupFile(file) { if (!file) return; if (!backupInteger(file.size, 1, backupMaxBytes)) { setBackupStatus('请选择不超过 5 MB 的阅读备份文件。'); return; } var reader = new FileReader(); setBackupStatus('正在读取备份文件…'); reader.onload = function () { if (state.backupText) state.backupText.value = String(reader.result || ''); restoreBackupText(); }; reader.onerror = function () { setBackupStatus('文件读取失败,请用“备份文本”粘贴恢复。'); }; try { reader.readAsText(file, 'UTF-8'); } catch (_) { setBackupStatus('无法打开此文件,请用“备份文本”粘贴恢复。'); } } function copyBackupText() { var text = state.backupText && state.backupText.value; if (!text) { setBackupStatus('先点“导出备份”生成文本,或粘贴已有备份。'); return; } function manualCopy() { toggleBackupText(true); state.backupText.focus(); state.backupText.select(); var copied = false; try { copied = document.execCommand('copy'); } catch (_) {} setBackupStatus(copied ? '备份文本已复制,请保存到手机文件或笔记中。' : '请长按下面的文本,全选并复制保存。'); } if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).then(function () { setBackupStatus('备份文本已复制,请保存到手机文件或笔记中。'); }, manualCopy); } else manualCopy(); } function installBackupControls(panel){var area=document.createElement("div");area.setAttribute("data-bm-backup-area","");area.style.cssText="padding:10px 12px 4px;box-sizing:border-box;font-size:14px;line-height:1.5;";var row=document.createElement("div");row.style.cssText="display:flex;flex-wrap:wrap;gap:7px;";[["export","导出备份"],["import","导入恢复"],["toggle-text","备份文本"]].forEach(function(item){var button=document.createElement("button");button.type="button";button.setAttribute("data-bm-backup-"+item[0],"");button.textContent=item[1];button.style.cssText="padding:6px 7px;border:1px solid currentColor;border-radius:5px;background:none;font-size:13px;white-space:nowrap;";row.appendChild(button)});var logButton=document.createElement("button");logButton.type="button";logButton.textContent="重复日志";logButton.setAttribute("data-bm-repeat-log-toggle","");logButton.setAttribute("aria-expanded","false");logButton.style.cssText="padding:6px 7px;border:1px solid currentColor;border-radius:5px;background:none;"+"color:inherit;font-size:13px;white-space:nowrap;";row.appendChild(logButton);state.repeatLogButton=logButton;area.appendChild(row);var logArea=document.createElement("section");logArea.setAttribute("data-bm-repeat-log","");logArea.setAttribute("aria-label","重复处理日志");logArea.hidden=true;logArea.style.cssText="padding-top:10px;font-size:13px;line-height:1.6;";logArea.style.setProperty("display","none","important");var logList=document.createElement("div");logList.setAttribute("data-bm-repeat-log-list","");logArea.appendChild(logList);area.appendChild(logArea);state.repeatLogArea=logArea;state.repeatLogList=logList;var status=document.createElement("div");status.setAttribute("data-bm-backup-status","");status.setAttribute("role","status");status.style.cssText="font-size:12px;line-height:1.5;padding-top:6px;";status.textContent="备份全部书的阅读进度和外观设置。";area.appendChild(status);state.backupStatus=status;var file=document.createElement("input");file.type="file";file.accept=".json,application/json,text/plain";file.setAttribute("data-bm-backup-file","");file.style.display="none";file.addEventListener("change",function(){var selected=file.files&&file.files[0];file.value="";importBackupFile(selected)});area.appendChild(file);state.backupFile=file;var fallback=document.createElement("div");fallback.setAttribute("data-bm-backup-text-panel","");fallback.hidden=true;fallback.style.setProperty("display","none","important");var help=document.createElement("div");help.textContent="文件下载或选择失败时,可复制备份文本保存;以后粘贴到这里恢复。";help.style.cssText="padding:8px 0;font-size:12px;";fallback.appendChild(help);var text=document.createElement("textarea");text.setAttribute("data-bm-backup-text","");text.setAttribute("aria-label","阅读备份 JSON 文本");text.placeholder="粘贴阅读备份 JSON";text.spellcheck=false;text.style.cssText="display:block;width:100%;height:120px;box-sizing:border-box;border:1px solid currentColor;border-radius:5px;"+"padding:8px;background:none;color:inherit;font-size:12px;font-family:monospace;line-height:1.5;";fallback.appendChild(text);state.backupText=text;var actions=document.createElement("div");actions.style.cssText="display:flex;gap:8px;padding-top:8px;";[["copy","复制文本"],["restore-text","从文本恢复"]].forEach(function(item){var button=document.createElement("button");button.type="button";button.setAttribute("data-bm-backup-"+item[0],"");button.textContent=item[1];button.style.cssText="padding:6px 10px;border:1px solid currentColor;border-radius:5px;background:none;font-size:14px;";actions.appendChild(button)});fallback.appendChild(actions);area.appendChild(fallback);state.backupTextPanel=fallback;panel.insertBefore(area,panel.querySelector("[data-bm-progress-status]")||panel.children[1]||null)} function recordRepeatPage(page, action) { if (!page || (action !== 'whole' && !page.removedRepeated)) return; var url = readUrl(page.url.href); if (!url) return; var kept = action === 'whole' ? 0 : page.paragraphs.length; var original = page.originalParagraphCount || page.paragraphs.length; var entry = { url: url.href, title: cleanChapterTitle(page.title).slice(0, 120), page: progressPage(url), total: page.pageTotal || 0, original: original, kept: kept, removed: original - kept, action: action, time: Date.now(), attempts: 1 }; var last = state.repeatLog[state.repeatLog.length - 1]; if (last && last.url === entry.url && last.action === entry.action && last.removed === entry.removed) { entry.attempts = last.attempts + 1; state.repeatLog[state.repeatLog.length - 1] = entry; } else { state.repeatLog.push(entry); if (state.repeatLog.length > 80) state.repeatLog.shift(); } if (state.repeatLogButton) state.repeatLogButton.title = state.repeatLog.length + ' 条重复处理记录'; if (state.repeatLogArea && !state.repeatLogArea.hidden) renderRepeatLog(); } function renderRepeatLog() { if (!state.repeatLogList) return; state.repeatLogList.textContent = ''; var help = document.createElement('div'); help.style.cssText = 'padding:0 0 8px;opacity:.8;'; help.textContent = state.repeatLog.length ? '本次阅读的重复处理记录,最新在前;最多保留 80 条,不保存正文。' : '本次阅读还没有检测到重复正文。发现后会显示章名、页码、清理数量和处理方式。'; state.repeatLogList.appendChild(help); state.repeatLog.slice().reverse().forEach(function (entry) { var card = document.createElement('div'); card.setAttribute('data-bm-repeat-log-url', entry.url); card.setAttribute('data-bm-repeat-log-removed', String(entry.removed)); card.setAttribute('data-bm-repeat-log-kept', String(entry.kept)); card.style.cssText = 'border-top:1px solid rgba(128,128,128,.25);padding:8px 0;overflow-wrap:anywhere;'; var title = document.createElement('strong'); title.textContent = (entry.title || '当前章节') + ' · 第' + entry.page + '页' + (entry.total ? '/' + entry.total : ''); card.appendChild(title); var counts = document.createElement('div'); counts.textContent = entry.original + '段 → ' + entry.kept + '段;处理 ' + entry.removed + ' 段重复正文'; card.appendChild(counts); var description = document.createElement('div'); description.textContent = entry.action === 'skip' ? '略过整页,沿原站下一页接续' : entry.action === 'whole' ? '整页正文与已读内容一致,正在自动校正' : entry.action === 'initial' ? '删除本页重复长段' : '删除重复长段,保留其余正文'; if (entry.attempts > 1) description.textContent += '(检测 ' + entry.attempts + ' 次)'; card.appendChild(description); var date = new Date(entry.time); var note = document.createElement('small'); note.style.cssText = 'display:block;opacity:.7;'; note.textContent = [date.getHours(), date.getMinutes(), date.getSeconds()].map(function (number) { return ('0' + number).slice(-2); }).join(':') + ' · ' + new URL(entry.url).pathname + (new URL(entry.url).search || ''); card.appendChild(note); state.repeatLogList.appendChild(card); }); } function toggleRepeatLog() { if (!state.repeatLogArea || !state.repeatLogButton) return; var show = state.repeatLogArea.hidden; state.repeatLogArea.hidden = !show; state.repeatLogArea.style.setProperty('display', show ? 'block' : 'none', 'important'); state.repeatLogButton.setAttribute('aria-expanded', String(show)); if (show) renderRepeatLog(); } function setProgressStatus(message) { if (!state.progressStatus) return; state.progressStatus.textContent = message || ''; state.progressStatus.hidden = !message; } function closeProgressPanel() { setTimeout(function(){bookProgress.update();bookProgress.ensure();readerWidget.armIdle();},0); if (!state.progressPanel) return; state.progressOpen = false; state.progressRows=null;state.progressMore=null; state.progressSeq++; stopCatalogueRequest(); state.progressPanel.hidden = true; state.progressPanel.style.setProperty('display', 'none', 'important'); setProgressStatus(''); schedule(); } function setProgressPanelView(view) { var panel=state.progressPanel;if(!panel)return; var toolsMode=view==='tools';panel.setAttribute('data-bm-panel-view',toolsMode?'tools':'progress'); var title=toolsMode?'阅读工具':'阅读进度';panel.setAttribute('aria-label',title);panel.querySelector('[data-bm-panel-title]').textContent=title; var tools=panel.querySelector('[data-bm-progress-tools]');tools.hidden=!toolsMode;tools.style.setProperty('display',toolsMode?'block':'none','important'); [panel.querySelector('[data-bm-progress-summary]'),state.progressList].forEach(function(node){node.hidden=toolsMode;node.style.setProperty('display',toolsMode?'none':'block','important');}); if(toolsMode){state.progressRows=null;state.progressMore=null;state.progressList.textContent='';} } function openToolsPanel() { openProgressPanel('tools'); } function openProgressPanel(view) { bookProgress.stop(); if(!state.progressPanel)return; if(state.progressOpen){if(state.progressPanel.getAttribute('data-bm-panel-view')!==(view==='tools'?'tools':'progress')){state.progressSeq++;stopCatalogueRequest();setProgressStatus('');}setProgressPanelView(view);state.progressPanel.scrollTop=0;if(view!=='tools')renderProgressHistory();return;} cancelAutoRecovery(); if (state.tocOpen) closeCatalogue(); if (state.progressTimer) { clearTimeout(state.progressTimer); state.progressTimer = null; } if (state.progressTouched || !normalizeProgress(state.savedProgress)) saveProgressNow(); state.progressOpen = true; if (state.timer) { clearTimeout(state.timer); state.timer = null; } state.retryPending = false; updateStatus(); setProgressPanelView(view); state.progressPanel.scrollTop=0; state.progressPanel.hidden = false; state.progressPanel.style.setProperty('display', 'block', 'important'); setProgressStatus(''); renderProgressHistory(); } function progressLabel(progress) { return progress.title || '章节名称待补全'; } function addProgressChoice(label, progress, kind) { kind = kind || 'recent'; var button = document.createElement('button'); button.type = 'button'; button.setAttribute('data-bm-progress-card', kind); button.setAttribute('data-bm-progress-url', progress.url); button.setAttribute('data-bm-progress-index', String(progress.index)); button.setAttribute('data-bm-progress-ratio', String(progress.ratio)); button.setAttribute('data-bm-progress-order', String(progress.order || 0)); button.setAttribute('aria-label', label + ',' + progressLabel(progress) + ',第' + progressPage(readUrl(progress.url)) + '页,第' + (progress.index + 1) + '段'); button.style.cssText = 'position:relative;display:block;width:100%;min-height:76px;box-sizing:border-box;padding:15px 40px 15px 15px;' + 'margin:0 0 10px;border:1px solid rgba(110,101,90,.18);border-radius:14px;text-align:left;color:inherit;' + 'font-size:16px;line-height:1.5;touch-action:manipulation;cursor:pointer;' + 'background:' + (currentTheme === 'night' ? 'rgba(255,255,255,.045)' : 'rgba(255,255,255,.24)') + ';'; var caption = document.createElement('strong'); caption.textContent = label; caption.setAttribute('data-bm-progress-caption', ''); caption.style.cssText = 'display:block;margin-bottom:4px;font-size:11px;line-height:1.5;opacity:.6;letter-spacing:.5px;'; caption.hidden = kind === 'recent'; if (caption.hidden) caption.style.display = 'none'; var chapter = document.createElement('span'); chapter.setAttribute('data-bm-progress-title', ''); chapter.textContent = progressLabel(progress); chapter.style.cssText = 'display:block;font-size:16px;line-height:1.5;font-weight:600;overflow-wrap:anywhere;'; var detail = document.createElement('span'); detail.setAttribute('data-bm-progress-detail', ''); detail.textContent = '第' + progressPage(readUrl(progress.url)) + '页 · 第' + (progress.index + 1) + '段'; detail.style.cssText = 'display:block;margin-top:4px;font-size:12px;line-height:1.5;opacity:.6;'; var arrow = document.createElement('span'); arrow.textContent = '›'; arrow.setAttribute('aria-hidden', 'true'); arrow.style.cssText = 'position:absolute;right:16px;top:50%;transform:translateY(-50%);font:24px/1 sans-serif;opacity:.4;'; button.appendChild(caption); button.appendChild(chapter); button.appendChild(detail); button.appendChild(arrow); state.progressList.appendChild(button); return button; } function renderProgressHistory(){if(!state.progressList||state.progressPanel.getAttribute("data-bm-panel-view")==="tools")return;state.progressList.textContent="";var value=bookProgress.update(),currentUrl=currentReadingUrl();var visibleTitle=state.progressPanel.querySelector("[data-bm-progress-current-title]");if(visibleTitle)visibleTitle.textContent=currentUrl?titleForProgress(currentUrl):"";var meter=state.progressPanel.querySelector("[data-bm-progress-meter]"),fill=state.progressPanel.querySelector("[data-bm-progress-meter-fill]");if(meter&&fill){var number=value&&value.percent;if(Number.isFinite(number)){var shown=Math.min(number<100?99.9:100,Number(number.toFixed(1)));meter.setAttribute("aria-valuenow",String(shown));meter.setAttribute("aria-valuetext",shown.toFixed(1)+"%");fill.style.width=shown+"%"}else{meter.removeAttribute("aria-valuenow");meter.setAttribute("aria-valuetext","进度待确认");fill.style.width="0%"}}var furthest=normalizeProgress(state.progressHistory.furthest);var recent=state.progressHistory.recent.slice().sort(function(a,b){return b.time-a.time}).map(normalizeProgress).filter(Boolean);var saved=normalizeProgress(state.savedProgress),current=null;if(saved&¤tUrl&&pageKey(readUrl(saved.url))===pageKey(currentUrl)){current=saved}var rendered=[];var positions=new Map;function remember(entry){var key=entry.url+"|"+entry.index;var bucket=positions.get(key)||[];bucket.push(entry.ratio);positions.set(key,bucket);rendered.push(entry)}function exists(entry){var bucket=positions.get(entry.url+"|"+entry.index);return!!bucket&&bucket.some(function(ratio){return Math.abs(ratio-entry.ratio)<.001})}if(furthest){var first=addProgressChoice("最远已读",furthest,"furthest");remember(furthest);if(current&&sameProgressPosition(current,furthest))first.setAttribute("aria-current","location")}if(current&&!exists(current)){addProgressChoice("当前在读",current,"current").setAttribute("aria-current","location");remember(current)}var others=[];recent.forEach(function(entry){if(!exists(entry)){others.push(entry);remember(entry)}});state.progressRows={items:others,shown:0};state.progressMore=null;if(others.length){var heading=document.createElement("h3");heading.textContent="最近阅读";heading.setAttribute("data-bm-progress-recent-heading","");heading.style.cssText="margin:20px 2px 10px;font-size:13px;opacity:.65;";state.progressList.appendChild(heading);appendRecentProgressRows()}if(!rendered.length){var empty=document.createElement("div");empty.setAttribute("data-bm-progress-empty","");empty.textContent="还没有保存的阅读进度";empty.style.cssText="padding:24px 2px;font-size:14px;line-height:1.6;opacity:.65;";state.progressList.appendChild(empty)}} function setCatalogueStatus(message) { if (!state.tocStatus) return; state.tocStatus.textContent = message || ''; state.tocStatus.hidden = !message; } function closeCatalogue() { setTimeout(function(){bookProgress.update();bookProgress.ensure();readerWidget.armIdle();},0); if (!state.tocPanel) return; state.tocOpen = false; state.tocSeq++; stopCatalogueRequest(); state.tocPanel.hidden = true; state.tocPanel.style.setProperty('display', 'none', 'important'); setCatalogueStatus(''); schedule(); } function openCatalogue() { bookProgress.stop(); if (!state.tocPanel || state.tocOpen) return; if(state.progressOpen)closeProgressPanel(); var current = currentReadingUrl(); cancelAutoRecovery(); if (state.progressTimer) { clearTimeout(state.progressTimer); state.progressTimer = null; } if (state.progressTouched || !normalizeProgress(state.savedProgress)) saveProgressNow(); state.tocOpen = true; if (state.timer) { clearTimeout(state.timer); state.timer = null; } state.retryPending = false; updateStatus(); state.tocPanel.hidden = false; state.tocPanel.style.setProperty('display', 'block', 'important'); state.tocCurrentPath = current ? current.pathname : ''; state.tocPanel.scrollTop = 0; openCurrentCatalogue(current); } function currentReadingUrl() { if (activeReader() && state.content) { var viewport = state.reader.getBoundingClientRect(); var height = state.reader.clientHeight; var anchor = viewport.top + Math.min(120, height * .22); var visible = null; var paragraphs = state.content.querySelectorAll('p[data-bm-progress-url]'); for (var i = 0; i < paragraphs.length; i++) { var paragraph = paragraphs[i]; if (paragraph.hidden || paragraph.closest('[hidden], .readPage, .page2-chapter')) continue; var rect = paragraph.getBoundingClientRect(); if (!(rect.height > 0) || rect.bottom <= viewport.top || rect.top >= viewport.top + height) continue; var url = readUrl(paragraph.getAttribute('data-bm-progress-url')); if (!url) continue; if (!visible) visible = url; if (rect.bottom > anchor) return url; } if (visible) return visible; } return readUrl(location.href) || (state.first && state.first.url) || null; } var catalogueLookup = { timer: null, resolve: null, inFlight: null }; function catalogueContains(data, pathname) { return !!pathname && !!data && Array.isArray(data.chapters) && data.chapters.some(function (chapter) { return chapter && chapter.url && chapter.url.pathname === pathname; }); } function catalogueCached(cp) { if (state.tocCache.has(cp)) return state.tocCache.get(cp); var count = null; try { count = JSON.parse(localStorage.getItem('bm-reader-book-count-' + bookId + '-v1') || 'null'); } catch (_) {} if (!count || count.v !== 1 || count.book !== String(bookId) || !Number.isInteger(count.maxGroup) || count.maxGroup < cp || count.maxGroup > 100 || !Number.isInteger(count.finalCount) || count.finalCount < 1 || count.finalCount > 100 || count.total !== (count.maxGroup - 1) * 100 + count.finalCount || !Number.isFinite(count.time) || count.time <= 0 || count.time > Date.now() || Date.now() - count.time >= 21600000) return null; var expected = cp === count.maxGroup ? count.finalCount : 100; var rows = new Array(expected), corrupt = false; state.chapterOrders.forEach(function (order, pathname) { var index = order - (cp - 1) * 100 - 1; if (!Number.isInteger(order) || index < 0 || index >= expected) return; var url = readUrl(pathname), label = state.chapterTitles.get(pathname); if (!url || url.search || typeof label !== 'string' || !label.trim() || rows[index]) { corrupt = true; return; } rows[index] = { url: url, label: label.trim(), order: order }; }); if (corrupt) return null; for (var index = 0; index < expected; index++) if (!rows[index]) return null; var ranges = {}; for (var group = 1; group <= count.maxGroup; group++) ranges[group] = ((group - 1) * 100 + 1) + '-' + Math.min(group * 100, count.total); var data = { chapters: rows, ranges: ranges, max: count.maxGroup }; state.tocCache.set(cp, data); return data; } function catalogueTokenActive(token) { return state.tocOpen && token === state.tocSeq; } async function catalogueRequestGap(token) { if (!catalogueTokenActive(token)) return false; var gap = Math.max(0, 2000 - (Date.now() - (state.lastRequest || 0))); if (gap) { var allowed = await new Promise(function (resolve) { catalogueLookup.resolve = resolve; catalogueLookup.timer = setTimeout(function () { catalogueLookup.timer = null; catalogueLookup.resolve = null; resolve(true); }, gap); }); if (!allowed) return false; } return catalogueTokenActive(token); } async function catalogueData(cp, token, force) { if (!catalogueTokenActive(token)) return null; var cached = force === true ? null : catalogueCached(cp); if (cached) return { cp: cp, data: cached }; var running = catalogueLookup.inFlight; if (running && running.cp === cp && running.token === token) return running.promise; var entry = { cp: cp, token: token, promise: null, cancelled: false }; catalogueLookup.inFlight = entry; entry.promise = Promise.resolve().then(async function () { try { if (!(await catalogueRequestGap(token)) || entry.cancelled) return null; state.lastRequest = Date.now(); var url = new URL('/book/' + bookId + '.html?cp=' + cp, origin); var doc = await fetchInertPage(url, '目录'); if (!catalogueTokenActive(token) || entry.cancelled) return null; var current = doc.querySelector('#chapters .page-range-list .cur'); var range = current && current.textContent.trim().match(/^(\d+)\s*[-—~]\s*(\d+)$/); var actualCp = range ? Math.floor((Number(range[1]) - 1) / 100) + 1 : cp; if (!(actualCp >= 1 && actualCp <= 100)) throw new Error('无法确认目录区间,当前阅读已保留'); var data = parseCatalogue(doc, url, actualCp); state.tocCache.set(actualCp, data); return { cp: actualCp, data: data }; } finally { if (catalogueLookup.inFlight === entry) catalogueLookup.inFlight = null; } }); return entry.promise; } function catalogueGroupHint(current) { var path = current && current.pathname; var order = path && state.chapterOrders.get(path); if (!order) { var savedRows = [state.savedProgress].concat(state.progressHistory && state.progressHistory.recent || []); if (state.progressHistory && state.progressHistory.furthest) savedRows.push(state.progressHistory.furthest); for (var index = 0; index < savedRows.length && !order; index++) { var saved = savedRows[index], savedUrl = saved && readUrl(saved.url); if (savedUrl && savedUrl.pathname === path && Number.isInteger(saved.order) && saved.order > 0 && saved.order <= 10000) order = saved.order; } } if (Number.isInteger(order) && order > 0 && order <= 10000) return Math.floor((order - 1) / 100) + 1; var model = current && pageModelForUrl(current); var number = String(model ? model.title : '').match(/^第\s*(\d+)\s*章/); return number && Number(number[1]) >= 1 && Number(number[1]) <= 10000 ? Math.floor((Number(number[1]) - 1) / 100) + 1 : (Number.isInteger(state.tocPage) && state.tocPage >= 1 && state.tocPage <= 100 ? state.tocPage : 1); } function catalogueLoading(message) { if (!state.tocList) return; state.tocList.textContent = ''; var note = document.createElement('div'); note.setAttribute('data-bm-toc-loading', ''); note.style.cssText = 'padding:22px 14px;font-size:14px;line-height:1.7;opacity:.8;'; note.textContent = message || '正在打开当前章节的目录…'; state.tocList.appendChild(note); setCatalogueStatus(message || '正在打开当前章节的目录…'); } function presentCatalogue(result, focus) { state.tocPage = result.cp; try { localStorage.setItem(tocPageKey, String(result.cp)); } catch (_) {} renderCatalogue(result.data, result.cp, focus); } async function openCurrentCatalogue(current){stopCatalogueRequest();var token=++state.tocSeq,pathname=current?current.pathname:"";var cached=null;state.tocCache.forEach(function(data,cp){if(!cached&&catalogueContains(data,pathname))cached={cp:cp,data:data}});var hint=catalogueGroupHint(current);var reused=!cached&&catalogueCached(hint);if(reused&&catalogueContains(reused,pathname))cached={cp:hint,data:reused};if(cached){presentCatalogue(cached,true);return}if(state.tocPages)state.tocPages.textContent="";catalogueLoading();var queue=[hint],visited=new Set,requests=0,lastResult=null;var model=current&&pageModelForUrl(current);var titleNumber=String(model?model.title:"").match(/^第\s*(\d+)\s*章/);var titleGroup=titleNumber?Math.floor((Number(titleNumber[1])-1)/100)+1:0;try{while(queue.length&&requests<3){if(!catalogueTokenActive(token))return;var cp=queue.shift();if(!Number.isInteger(cp)||cp<1||cp>100||visited.has(cp))continue;visited.add(cp);requests++;setCatalogueStatus("正在打开当前章节的目录(第"+cp+"组)…");var result=await catalogueData(cp,token);if(!result||!catalogueTokenActive(token))return;visited.add(result.cp);lastResult=result;if(catalogueContains(result.data,pathname)){presentCatalogue(result,true);return}renderCatalogueRanges(result.data,0);var max=result.data.max;[titleGroup,result.cp+1,result.cp-1,state.tocPage,1].forEach(function(candidate){if(Number.isInteger(candidate)&&candidate>=1&&candidate<=max&&!visited.has(candidate)&&queue.indexOf(candidate)<0)queue.push(candidate)})}if(catalogueTokenActive(token)){if(lastResult)renderCatalogueRanges(lastResult.data,0);catalogueLoading("暂未确认当前章节所在的目录组,请点上方组号选择。")}}catch(error){if(catalogueTokenActive(token))catalogueLoading(error.message||"目录暂未打开,当前阅读已保留。")}} function focusCatalogueChapter() { if (!state.tocOpen || !state.tocPanel || !state.tocList) return; var selected = state.tocList.querySelector('[data-bm-toc-current]'); if (!selected) return; state.tocPanel.style.setProperty('scroll-behavior', 'auto', 'important'); if (state.tocPages) state.tocPages.style.setProperty('scroll-behavior', 'auto', 'important'); var panel = state.tocPanel.getBoundingClientRect(), bounds = selected.getBoundingClientRect(); var header = state.tocPanel.querySelector('[data-bm-toc-header]'); var reserved = (header ? header.getBoundingClientRect().height : 52) + (state.tocPages ? state.tocPages.getBoundingClientRect().height : 0); var height = state.tocPanel.clientHeight || window.innerHeight; if (bounds.height > 0 && height > reserved) state.tocPanel.scrollTop = Math.max(0, state.tocPanel.scrollTop + bounds.top - panel.top - reserved - (height - reserved - bounds.height) / 2); var group = state.tocPages && state.tocPages.querySelector('[aria-current="page"]'); if (group) { var groupBounds = group.getBoundingClientRect(), bar = state.tocPages.getBoundingClientRect(); if (bar.width > 0) state.tocPages.scrollLeft = Math.max(0, state.tocPages.scrollLeft + groupBounds.left - bar.left - (bar.width - groupBounds.width) / 2); } } function stopCatalogueRequest() { if (catalogueLookup.timer) { clearTimeout(catalogueLookup.timer); catalogueLookup.timer = null; } if (catalogueLookup.resolve) { var resolve = catalogueLookup.resolve; catalogueLookup.resolve = null; resolve(false); } if (catalogueLookup.inFlight) { catalogueLookup.inFlight.cancelled = true; catalogueLookup.inFlight = null; } if (state.tocController) { state.tocController.abort(); state.tocController = null; } } async function fetchInertPage(url, kind) { var controller = typeof AbortController === 'function' ? new AbortController() : null; state.tocController = controller; var timeout; try { return await Promise.race([(async function () { var response = await fetch(url.href, { credentials: 'same-origin', redirect: 'manual', signal: controller ? controller.signal : undefined }); if (response.type === 'opaqueredirect' || (response.status >= 300 && response.status < 400)) { throw new Error(kind + '需要登录或验证,当前阅读已保留'); } if (!response.ok) throw new Error(kind + '加载失败(' + response.status + '),当前阅读已保留'); if (response.url) { var finalUrl = siteUrl(response.url); if (!finalUrl || finalUrl.pathname !== url.pathname || finalUrl.search !== url.search) { throw new Error(kind + '响应地址异常,当前阅读已保留'); } } var template = document.createElement('template'); template.innerHTML = await response.text(); return template.content; })(), new Promise(function (_, reject) { timeout = setTimeout(function () { if (controller) controller.abort(); reject(new Error(kind + '加载超时,当前阅读已保留')); }, 20000); })]); } finally { clearTimeout(timeout); if (state.tocController === controller) state.tocController = null; } } function parseCatalogue(doc,pageUrl,cp){var title=doc.querySelector("title");if(doc.querySelector('input[name="GOEDGE_WAF_CAPTCHA_CODE"], #loginForm')||title&&/^(?:身份验证|安全验证|访问验证|人机验证|登录\s*[-—]|Just a moment)/i.test(title.textContent.trim())){throw new Error("目录需要登录或验证,当前阅读已保留")}var chapterBox=doc.querySelector("#chapters .direList");if(!chapterBox)throw new Error("未找到原站章节目录,当前阅读已保留");var anchors=chapterBox.querySelectorAll("li .name > a[href]");var chapters=[];var seen=new Set;for(var i=0;i=1&&page<=100&&page%1===0))continue;ranges[page]=rangeLinks[j].textContent.trim()||String(page);max=Math.max(max,page)}var currentRange=doc.querySelector("#chapters .page-range-list .cur");if(currentRange)ranges[cp]=currentRange.textContent.trim()||ranges[cp];var data={chapters:chapters,ranges:ranges,max:max};bookProgress.observeCatalogue(data,cp);return data} function renderCatalogueRanges(data, cp) { if (!state.tocOpen || !state.tocPages) return; state.tocMax = data.max; state.tocPages.textContent = ''; var fragment = document.createDocumentFragment(); for (var page = 1; page <= data.max; page++) { var button = document.createElement('button'); button.type = 'button'; button.setAttribute('data-bm-toc-page', String(page)); button.setAttribute('aria-label', '目录 ' + (data.ranges[page] || page)); button.textContent = data.ranges[page] || String(page); button.style.cssText = 'flex:0 0 auto;padding:6px 8px;border:1px solid currentColor;' + 'border-radius:5px;background:none;font-size:12px;'; if (page === cp) { button.setAttribute('aria-current', 'page'); button.style.setProperty('font-weight', '700'); } fragment.appendChild(button); } state.tocPages.appendChild(fragment); } function renderCatalogue(data, cp, focusCurrent) { if (!state.tocOpen || !state.tocPages || !state.tocList) return; renderCatalogueRanges(data, cp); state.tocList.textContent = ''; var fragment = document.createDocumentFragment(); data.chapters.forEach(function (chapter) { var button = document.createElement('button'); button.type = 'button'; button.setAttribute('data-bm-toc-url', chapter.url.href); button.textContent = chapter.label; button.style.cssText = 'display:block;width:100%;box-sizing:border-box;padding:11px 10px;' + 'border:0;border-bottom:1px solid rgba(90,80,70,.18);' + 'background:none;text-align:left;font-size:16px;line-height:1.5;'; if (chapter.url.pathname === state.tocCurrentPath) { button.setAttribute('aria-current', 'page'); button.setAttribute('data-bm-toc-current', ''); button.title = '当前正在阅读的章节'; button.style.setProperty('font-weight', '700'); button.style.setProperty('border-left', '3px solid currentColor'); button.style.setProperty('background-color', 'rgba(78,128,92,.13)'); } fragment.appendChild(button); }); state.tocList.appendChild(fragment); setCatalogueStatus(''); applyTheme(currentTheme); state.tocPanel.scrollTop = 0; if (focusCurrent) focusCatalogueChapter(); } async function loadCatalogue(cp, force) { if (!state.tocOpen || !(cp >= 1 && cp <= 100 && cp % 1 === 0)) return; stopCatalogueRequest(); var token = ++state.tocSeq; state.tocPage = cp; try { localStorage.setItem(tocPageKey, String(cp)); } catch (_) {} var cached = force === true ? null : catalogueCached(cp); if (cached) { presentCatalogue({ cp: cp, data: cached }, false); return; } catalogueLoading('正在打开第' + cp + '组目录…'); try { var result = await catalogueData(cp, token, force === true); if (!result || !catalogueTokenActive(token)) return; presentCatalogue(result, false); } catch (error) { if (catalogueTokenActive(token)) catalogueLoading(error.message || '目录暂未打开,当前阅读已保留。'); } } async function chooseCatalogueChapter(value) { var url = readUrl(value); if (!state.tocOpen || !url || url.search) return; stopCatalogueRequest(); if (state.first && pageKey(state.first.url) === pageKey(url)) { state.reader.scrollTop = 0; closeCatalogue(); saveChapterStart(url); return; } var token = ++state.tocSeq; setCatalogueStatus('正在读取章节…'); try { var doc = await fetchInertPage(url, '本章'); var page = extract(doc, url); if (!state.tocOpen || token !== state.tocSeq) return; showCatalogueChapter(page); } catch (error) { if (state.tocOpen && token === state.tocSeq) setCatalogueStatus(error.message || '本章暂不能打开,当前阅读已保留'); } } function saveChapterStart(url) { state.progressTouched = false; recordProgress({ url: url.href, index: 0, ratio: 0, time: Date.now(), title: titleForProgress(url) }); } function showCatalogueChapter(page, checkpoint) { if (!activeReader() || !state.source) throw new Error('阅读模式已关闭,当前章节未切换'); var target = readUrl(page.url.href); if (!target) throw new Error('章节地址不正确,当前阅读已保留'); cleanPageParagraphs(page, []); recordRepeatPage(page, 'initial'); try { history.replaceState(history.state, '', target.href); } catch (_) { throw new Error('浏览器无法原位切换地址,当前阅读已保留'); } rememberChapterTitle(page); state.readSeq++; if (state.timer) { clearTimeout(state.timer); state.timer = null; } if (state.progressTimer) { clearTimeout(state.progressTimer); state.progressTimer = null; } state.pendingResume = null; try { sessionStorage.removeItem(pendingKey); } catch (_) {} replaceReadingPages(page, []); delete page.content; state.first = page; state.next = page.next; state.pages = []; state.nextIssue = page.nextIssue || ''; state.seen = new Set([pageKey(target)]); state.contentSeen = new Set([page.fingerprint]); state.repeatMemory = { path: '', pages: [], size: 0 }; rememberParagraphDigests(page); state.retryPending = false; state.busy = false; state.blocked = false; state.error = ''; markProgressParagraphs(); state.reader.scrollTop = 0; if (state.tocOpen) closeCatalogue(); if (state.progressOpen) closeProgressPanel(); if (state.shelfOpen) closeShelfPanel(); if (checkpoint) { var selected = normalizeProgress(checkpoint); selected.time = Date.now(); selected.title = page.title; recordProgress(selected); state.pendingResume = selected; restorePendingProgress(); } else saveChapterStart(target); updateStatus(); } function replaceReadingPages(page, remaining) { state.source.textContent = ''; state.content.textContent = ''; for (var i = 0; i < page.paragraphs.length; i++) { var paragraph = document.createElement('p'); paragraph.textContent = page.paragraphs[i]; paragraph.setAttribute('data-bm-source-index', String(page.paragraphIndexes ? page.paragraphIndexes[i] : i)); state.source.appendChild(paragraph); state.content.appendChild(paragraph.cloneNode(true)); } state.source.setAttribute('data-bm-page', pageKey(page.url)); state.content.setAttribute('data-bm-page', pageKey(page.url)); remaining.forEach(function (item) { appendPage(state.source, item); appendPage(state.content, item); }); var heading = document.querySelector('.conC h1.title'); if (heading) heading.textContent = page.title; var readerHeading = state.reader.querySelector('.via-reader-header h1'); if (readerHeading) readerHeading.textContent = page.title; document.title = page.docTitle || page.title; var breadcrumb = document.querySelector('.conC .position > span:last-child'); if (breadcrumb) breadcrumb.textContent = page.title; var chapterInfo = document.querySelector('.conC .toolBox > .font14'); if (chapterInfo) chapterInfo.textContent = page.chapterInfo || ''; var staleChapterActions = document.querySelector('.conC .btnErrorW'); if (staleChapterActions) staleChapterActions.remove(); var navigation = document.querySelector('.readPage'); if (navigation) { navigation.textContent = ''; if (page.next) { var next = document.createElement('a'); next.className = 'btnGreen'; next.href = page.next.href; next.textContent = page.next.pathname === page.url.pathname ? '下一页 >' : '下一章 >'; navigation.appendChild(next); } var catalogue = document.createElement('a'); catalogue.className = 'btnBlue'; catalogue.href = '/book/' + bookId + '.html'; catalogue.textContent = '目录'; navigation.appendChild(catalogue); } } function siteUrl(value, base) { try { var url = new URL(value, base || location.href); if (url.protocol === 'https:' && url.origin === origin && !url.username && !url.password) return url; } catch (_) {} return null; } function readUrl(value, base) { var url = siteUrl(value, base); if (!url || !bookId || !new RegExp('^/read/' + bookId + '/\\d+\\.html$').test(url.pathname)) return null; url.hash = ''; var pageNumbers = url.searchParams.getAll('p'); if (pageNumbers.length > 1 || (pageNumbers.length && (!/^\d+$/.test(pageNumbers[0]) || !Number.isSafeInteger(Number(pageNumbers[0])) || Number(pageNumbers[0]) < 1 || Number(pageNumbers[0]) > 10000))) return null; var pageNumber = pageNumbers.length ? Number(pageNumbers[0]) : 1; url.search = ''; if (pageNumber > 1) url.searchParams.set('p', String(pageNumber)); return url; } function pageKey(url) { var copy = new URL(url.href); copy.hash = ''; if (copy.searchParams.get('p') === '1') copy.searchParams.delete('p'); if (copy.searchParams.sort) copy.searchParams.sort(); return copy.href; } function normalizeProgress(value) { if (!value || typeof value !== 'object') return null; var url = readUrl(value.url); var index = Number(value.index); var ratio = Number(value.ratio); if (!url || !(index >= 0 && index % 1 === 0) || !(ratio >= 0 && ratio <= 1)) return null; var order = state.chapterOrders.get(url.pathname) || Number(value.order) || 0; return { url: url.href, index: index, ratio: ratio, time: Number(value.time) || 0, title: state.chapterTitles.get(url.pathname) || (typeof value.title === 'string' ? cleanChapterTitle(value.title).slice(0, 120) : '') || titleForProgress(url), order: Number.isInteger(order) && order > 0 && order <= 100000 ? order : 0 }; } function progressPage(url) { var page = Number(url.searchParams.get('p')) || 1; return Number.isInteger(page) && page >= 1 ? page : 1; } function compareWithinChapter(a, b) { var first = readUrl(a.url); var second = readUrl(b.url); if (!first || !second || first.pathname !== second.pathname) return null; var pageDifference = progressPage(first) - progressPage(second); return pageDifference || a.index - b.index || a.ratio - b.ratio; } function sameProgressPosition(a, b) { var first = readUrl(a.url); var second = readUrl(b.url); return !!first && !!second && pageKey(first) === pageKey(second) && a.index === b.index && Math.abs(a.ratio - b.ratio) < .001; } function followsKnownNext(previous, current) { var from = readUrl(previous.url); var to = readUrl(current.url); if (!from || !to) return false; var key = pageKey(from); var wanted = pageKey(to); var visited = new Set(); while (!visited.has(key)) { visited.add(key); key = state.forwardLinks.get(key); if (!key) return false; if (key === wanted) return true; } return false; } function isFurtherProgress(candidate, previous) { if (!previous) return true; var within = compareWithinChapter(candidate, previous); if (within !== null) return within > 0; if (candidate.order && previous.order) return candidate.order > previous.order; return followsKnownNext(previous, candidate); } function persistTextIfChanged(key, value) { if (localStorage.getItem(key) === value) return false; localStorage.setItem(key, value); return true; } function persistProgressHistory() { try { persistTextIfChanged(historyKey, JSON.stringify(state.progressHistory)); } catch (_) {} } function recordProgress(value, writeLegacy, promote) { var progress = normalizeProgress(value); if (!progress) return false; var persisted = false; if (writeLegacy !== false) { try { localStorage.setItem(progressKey, JSON.stringify(progress)); state.savedProgress = progress; persisted = true; } catch (_) {} } var recent = state.progressHistory.recent; var path = readUrl(progress.url).pathname; for (var i = 0; i < recent.length; i++) { var old = normalizeProgress(recent[i]); if (!old || readUrl(old.url).pathname !== path) continue; if (compareWithinChapter(old, progress) > 0) { old.time = progress.time; if (!old.order) old.order = progress.order; progress = old; } recent.splice(i, 1); break; } recent.unshift(progress); if (recent.length > 1000) recent.length = 1000; var furthest = normalizeProgress(state.progressHistory.furthest); if (promote !== false && isFurtherProgress(progress, furthest)) state.progressHistory.furthest = progress; persistProgressHistory(); updateResumeButton(); if (state.progressOpen) renderProgressHistory(); return persisted; } function loadProgressHistory() { try { var stored = JSON.parse(localStorage.getItem(historyKey) || 'null'); if (stored && typeof stored === 'object') { state.progressHistory.furthest = normalizeProgress(stored.furthest); if (Array.isArray(stored.recent)) { state.progressHistory.recent = stored.recent.map(normalizeProgress).filter(Boolean).slice(0, 1000); } } } catch (_) {} var legacy = normalizeProgress(state.savedProgress); if (legacy && !state.progressHistory.recent.some(function (entry) { return sameProgressPosition(entry, legacy) && entry.time >= legacy.time; })) recordProgress(legacy, false); } function reconcileProgressOrders() { var history = state.progressHistory; history.furthest = normalizeProgress(history.furthest); history.recent = history.recent.map(normalizeProgress).filter(Boolean); for (var i = 0; i < history.recent.length; i++) { if ((history.furthest || history.recent[i].order) && isFurtherProgress(history.recent[i], history.furthest)) history.furthest = history.recent[i]; } persistProgressHistory(); if (state.progressOpen) renderProgressHistory(); } function cleanChapterTitle(value) { return String(value || '').trim().replace(/\s*[((](?:第\s*\d+\s*页|\d+\s*\/\s*\d+(?:页)?)[))]\s*$/, ''); } function rememberChapterTitle(page) { if (!page || !page.url || !page.title) return; var title = cleanChapterTitle(page.title); if (title && !state.chapterTitles.get(page.url.pathname)) { state.chapterTitles.set(page.url.pathname, title); try { persistTextIfChanged(chapterTitleKey, JSON.stringify(Array.from(state.chapterTitles))); } catch (_) {} } reconcileProgressOrders(); } function titleForProgress(url) { if (state.chapterTitles.has(url.pathname)) return state.chapterTitles.get(url.pathname); if (state.first && state.first.url.pathname === url.pathname) return cleanChapterTitle(state.first.title); for (var i = 0; i < state.pages.length; i++) { if (state.pages[i].url.pathname === url.pathname) return cleanChapterTitle(state.pages[i].title); } return ''; } function updateResumeButton() { if (!state.toolbar) return; var button = state.toolbar.querySelector('[data-bm-resume]'); if (!button) return; button.disabled = !normalizeProgress(state.progressHistory.furthest) && !state.progressHistory.recent.length; button.style.setProperty('opacity', button.disabled ? '.45' : '1', 'important'); } function markProgressParagraphs() { if (!state.content || !state.first) return; var counts = Object.create(null); var paragraphs = state.content.querySelectorAll('p'); for (var i = 0; i < paragraphs.length; i++) { var paragraph = paragraphs[i]; if (paragraph.hasAttribute('data-bm-native-nav') || paragraph.closest('.readPage, .page2-chapter')) continue; var page = paragraph.closest('[data-bm-page]'); var url = page ? readUrl(page.getAttribute('data-bm-page')) : state.first.url; if (!url) continue; var key = pageKey(url); var index = counts[key] || 0; counts[key] = index + 1; var model = pageModelForUrl(url); var original = index; if (model && model.paragraphIndexes && Number.isInteger(model.paragraphIndexes[index])) original = model.paragraphIndexes[index]; else if (paragraph.hasAttribute('data-bm-source-index')) { var stored = Number(paragraph.getAttribute('data-bm-source-index')); if (Number.isInteger(stored) && stored >= 0) original = stored; } paragraph.setAttribute('data-bm-progress-url', url.href); paragraph.setAttribute('data-bm-progress-index', String(original)); } purifier.refresh(); } function syncVisiblePage(url) { if (!activeReader() || state.tocOpen || state.progressOpen) return; var page = pageModelForUrl(url); if (!page) return; var heading = state.reader.querySelector('.via-reader-header h1'); var title = cleanChapterTitle(page.title); if (heading && heading.textContent !== title) { var anchor = captureFontAnchor(); heading.textContent = title; restoreFontAnchor(anchor); } var docTitle = page.docTitle || page.title; if (document.title !== docTitle) document.title = docTitle; if (location.href !== page.url.href) { try { history.replaceState(history.state, '', page.url.href); } catch (_) {} } } function saveProgressNow(allowProgressPanel) { if (!activeReader() || state.tocOpen || state.shelfOpen || scriptDownloader.isOpen() || (captchaAutomation.isOpen() || (siteAccount && siteAccount.isOpen())) || (state.progressOpen && allowProgressPanel !== true) || !state.first) return; var paragraphs = state.content.querySelectorAll('p[data-bm-progress-index]'); var anchor = state.reader.getBoundingClientRect().top + Math.min(120, state.reader.clientHeight * .22); var selected = null; var rect = null; for (var i = 0; i < paragraphs.length; i++) { var candidate = paragraphs[i]; if (candidate.hidden || candidate.closest('[hidden], .readPage, .page2-chapter')) continue; var bounds = candidate.getBoundingClientRect(); if (!(bounds.height > 0)) continue; selected = candidate; rect = bounds; if (bounds.bottom > anchor) break; } if (!selected || !rect) return; var url = readUrl(selected.getAttribute('data-bm-progress-url')); var index = Number(selected.getAttribute('data-bm-progress-index')); if (!url || !(index >= 0 && index % 1 === 0)) return; var ratio = Math.max(0, Math.min(1, (anchor - rect.top) / rect.height)); var checkpoint = { url: url.href, index: index, ratio: Math.round(ratio * 1000) / 1000, time: Date.now(), title: titleForProgress(url) }; if (recordProgress(checkpoint) && siteAccount) siteAccount.progressChanged(bookId, state.savedProgress); syncVisiblePage(url); bookProgress.update(); bookProgress.ensure(); } function scheduleProgress() { if (state.tocOpen || state.progressOpen || state.shelfOpen || scriptDownloader.isOpen() || (captchaAutomation.isOpen() || (siteAccount && siteAccount.isOpen()))) return; state.progressTouched = true; if (state.progressTimer) return; state.progressTimer = setTimeout(function () { state.progressTimer = null; saveProgressNow(); compactReadPages(); }, 800); } function restoreProgressHere(progress) { if (!activeReader()) return false; state.lastRestoreTarget = null; var targetUrl = readUrl(progress.url); if (!targetUrl) return false; var targetIndex = progress.index; var model = pageModelForUrl(targetUrl); if (model && model.progressAliases) { var alias = model.progressAliases.find(function (item) { return item.from === targetIndex; }); if (alias) { targetUrl = readUrl(alias.toUrl || targetUrl.href); targetIndex = alias.toIndex; } } if (!targetUrl) return false; var paragraphs = state.content.querySelectorAll('p[data-bm-progress-index]'); for (var i = 0; i < paragraphs.length; i++) { var paragraph = paragraphs[i]; var url = readUrl(paragraph.getAttribute('data-bm-progress-url')); if (!url || pageKey(url) !== pageKey(targetUrl) || Number(paragraph.getAttribute('data-bm-progress-index')) !== targetIndex) continue; var rect = paragraph.getBoundingClientRect(); if (!(rect.height > 0)) return false; var anchor = state.reader.getBoundingClientRect().top + Math.min(120, state.reader.clientHeight * .22); state.reader.scrollTop += rect.top + progress.ratio * rect.height - anchor; state.lastRestoreTarget = normalizeProgress({ url: targetUrl.href, index: targetIndex, ratio: progress.ratio, time: progress.time, title: titleForProgress(targetUrl) }); syncVisiblePage(targetUrl); return true; } return false; } function restorePendingProgress() { var pending = normalizeProgress(state.pendingResume); if (!pending || !state.first || pageKey(readUrl(pending.url)) !== pageKey(state.first.url)) return; var attempts = 0; function attempt() { if (!activeReader()) return; markProgressParagraphs(); if (restoreProgressHere(pending)) { state.pendingResume = null; try { sessionStorage.removeItem(pendingKey); } catch (_) {} saveProgressNow(); } else if (++attempts < 4) setTimeout(attempt, 250); else { state.pendingResume = null; try { sessionStorage.removeItem(pendingKey); } catch (_) {} } } attempt(); } async function chooseHistoryPosition(button) { if (!state.progressOpen) return; var url = readUrl(button.getAttribute('data-bm-progress-url')); var index = Number(button.getAttribute('data-bm-progress-index')); var ratio = Number(button.getAttribute('data-bm-progress-ratio')); if (!url || !(index >= 0 && index % 1 === 0) || !(ratio >= 0 && ratio <= 1)) return; var choices = [state.savedProgress, state.progressHistory.furthest].concat(state.progressHistory.recent); var progress = null; for (var i = 0; i < choices.length; i++) { var entry = normalizeProgress(choices[i]); if (entry && pageKey(readUrl(entry.url)) === pageKey(url) && entry.index === index && Math.abs(entry.ratio - ratio) < .001) { progress = entry; break; } } if (!progress) return; if (progress.index === 0 && progress.ratio === 0 && state.first && pageKey(state.first.url) === pageKey(url)) { state.reader.scrollTop = 0; closeProgressPanel(); syncVisiblePage(state.first.url); progress.time = Date.now(); recordProgress(progress); return; } if (restoreProgressHere(progress)) { progress = state.lastRestoreTarget || progress; closeProgressPanel(); syncVisiblePage(readUrl(progress.url)); progress.time = Date.now(); recordProgress(progress); return; } stopCatalogueRequest(); var token = ++state.progressSeq; setProgressStatus('正在打开保存的位置…'); try { var doc = await fetchInertPage(url, '进度'); var page = extract(doc, url); if (!state.progressOpen || token !== state.progressSeq) return; showCatalogueChapter(page, progress); } catch (error) { if (state.progressOpen && token === state.progressSeq) { setProgressStatus(error.message || '暂时无法打开,当前阅读已保留'); } } } ['C061V4rne', 'ZJo6i4ZXG', 'r2lTyoQWN'].forEach(function (name) { try { Object.defineProperty(pageWindow, name, { value: function () {}, writable: false, configurable: false }); } catch (_) {} }); try { Object.defineProperty(pageWindow, 'open', { value: function () { return null; }, writable: false, configurable: false }); } catch (_) { try { pageWindow.open = function () { return null; }; } catch (ignored) {} } // BEGIN VIA TXT DOWNLOADER 1.9.0 var scriptDownloader = (function () { 'use strict'; var ownOrigin = location.origin, databasePromise = null; var owner = 'tab-' + Date.now() + '-' + Math.random().toString(36).slice(2); var panel = null, list = null, message = null, input = null, intervalInput = null, maxInput = null; var generation = 0, running = false, requesting = false, opened = false, activeTask = null, lease = null; var controller = null, waitTimer = null, waitReject = null, refreshSequence = 0; var prepared = null, deleteChoice = '', deleteUntil = 0, selectedTask = null, prepareSequence = 0, displayCount = 30, activeRequest = null; var MB = 1024 * 1024, taskLimit = 25 * MB, totalLimit = 100 * MB, referenceLimit = 2000000, leaseDuration = 180000; function fail(text) { throw new Error(text); } function interrupted() { var error = new Error('下载已暂停,已提交的正文和进度已保留。'); error.downloadCancelled = true; return error; } function check(epoch) { if (!running || epoch !== generation) throw interrupted(); } function bookOf(path) { var match = /^\/(?:book\/(\d{1,18})|read\/(\d{1,18})\/\d{1,18})\.html$/.exec(path); return match ? match[1] || match[2] : ''; } function validateUrl(raw, base, book) { var url; try { url = new URL(raw, base || ownOrigin); } catch (_) { fail('网址格式不正确。'); } var id = bookOf(url.pathname); if (url.protocol !== 'https:' || url.origin !== ownOrigin || url.username || url.password || !id || (book && id !== book)) fail('只支持当前菠萝猫域名内同一本书的 HTTPS 书籍或章节页。'); var count = 0, valid = true, parameter = /^\/read\//.test(url.pathname) ? 'p' : 'cp'; url.searchParams.forEach(function (value, key) { if (++count > 1 || key !== parameter || !/^[1-9]\d{0,3}$/.test(value)) valid = false; }); if (!valid) fail('页码参数不正确。'); url.hash = ''; if (parameter === 'p' && url.searchParams.get('p') === '1') url.searchParams.delete('p'); return url; } function pageNumber(url) { return Number(url.searchParams.get('p')) || 1; } function defaultUrl() { try { var visible = typeof currentReadingUrl === 'function' && currentReadingUrl(); if (visible) return validateUrl(visible.href).href; } catch (_) {} try { var here = validateUrl(location.href); if (/^\/read\//.test(here.pathname)) return here.href; return new URL('/book/' + bookOf(here.pathname) + '.html?cp=1', ownOrigin).href; } catch (_) { return ''; } } function database() { if (databasePromise) return databasePromise; databasePromise = new Promise(function (resolve, reject) { if (!window.indexedDB) { reject(new Error('浏览器未允许 IndexedDB,无法保存断点下载。')); return; } var request = indexedDB.open('bm-via-downloads-v1', 1); request.onupgradeneeded = function () { var db = request.result; db.createObjectStore('tasks', { keyPath: 'id' }); var pages = db.createObjectStore('pages', { keyPath: ['taskId', 'seq'] }); pages.createIndex('taskId', 'taskId', { unique: false }); pages.createIndex('chapter', ['taskId', 'chapterPath'], { unique: false }); db.createObjectStore('control', { keyPath: 'key' }); }; request.onsuccess = function () { request.result.onversionchange = function () { request.result.close(); databasePromise = null; }; resolve(request.result); }; request.onerror = function () { databasePromise = null; reject(request.error || new Error('下载存储无法打开。')); }; request.onblocked = function () { status('下载存储被其他标签占用,请关闭旧标签后重试。'); }; }); return databasePromise; } async function transact(names, mode, action) { var db = await database(); return new Promise(function (resolve, reject) { var tx = db.transaction(names, mode), value, problem; function abort(error) { problem = error; try { tx.abort(); } catch (_) {} } function safe(callback) { return function () { try { callback.apply(null, arguments); } catch (error) { abort(error); } }; } tx.oncomplete = function () { resolve(value); }; tx.onabort = tx.onerror = function () { reject(problem || tx.error || new Error('下载存储写入失败,当前页未提交。')); }; try { action(tx, function (result) { value = result; }, safe, abort); } catch (error) { abort(error); } }); } function taskById(id) { return transact(['tasks'], 'readonly', function (tx, result, safe) { var req = tx.objectStore('tasks').get(id); req.onsuccess = safe(function () { if (!req.result) fail('下载任务不存在。'); result(req.result); }); }); } function allTasks() { return transact(['tasks'], 'readonly', function (tx, result, safe) { var req = tx.objectStore('tasks').getAll(); req.onsuccess = safe(function () { result(req.result); }); }); } function taskPages(id, chapter) { return transact(['pages'], 'readonly', function (tx, result, safe) { var store = tx.objectStore('pages'), req = chapter ? store.index('chapter').getAll(IDBKeyRange.only([id, chapter])) : store.index('taskId').getAll(IDBKeyRange.only(id)); req.onsuccess = safe(function () { result(req.result.sort(function (a, b) { return a.seq - b.seq; })); }); }); } function owns(record, token) { return !!record && !!token && record.owner === owner && record.taskId === token.taskId && record.fence === token.fence && record.expiresAt > Date.now(); } function acquire(id, epoch) { return transact(['control', 'tasks'], 'readwrite', function (tx, result, safe) { var store = tx.objectStore('control'), req = store.get('runner'); req.onsuccess = safe(function () { check(epoch); var old = req.result; if (old && old.owner && old.owner !== owner && old.expiresAt > Date.now()) fail('另一个标签正在下载,请先在那个标签暂停。'); var token = { key: 'runner', owner: owner, taskId: id, fence: (old && old.fence || 0) + 1, expiresAt: Date.now() + leaseDuration }; var job = tx.objectStore('tasks').get(id); job.onsuccess = safe(function () { check(epoch); if (!job.result) fail('任务不存在。'); store.put(token); job.result.state = 'running'; job.result.status = '正在准备下载'; job.result.updatedAt = Date.now(); tx.objectStore('tasks').put(job.result); result(token); }); }); }); } function release(token, text) { if (!token) return Promise.resolve(); return transact(['control', 'tasks'], 'readwrite', function (tx, result, safe) { var store = tx.objectStore('control'), req = store.get('runner'); req.onsuccess = safe(function () { var old = req.result; if (!old || old.owner !== owner || old.fence !== token.fence || old.taskId !== token.taskId) return; old.owner = ''; old.expiresAt = 0; store.put(old); if (text) { var job = tx.objectStore('tasks').get(token.taskId); job.onsuccess = safe(function () { if (!job.result) return; if (job.result.state === 'running') job.result.state = 'paused'; job.result.status = text; job.result.updatedAt = Date.now(); tx.objectStore('tasks').put(job.result); }); } }); }); } function updateTask(token, epoch, change) { return transact(['control', 'tasks'], 'readwrite', function (tx, result, safe) { var controls = tx.objectStore('control'), req = controls.get('runner'); req.onsuccess = safe(function () { check(epoch); if (!owns(req.result, token)) fail('下载租约已被其他标签接手,请重新点继续。'); req.result.expiresAt = Date.now() + leaseDuration; controls.put(req.result); var jobs = tx.objectStore('tasks'), job = jobs.get(token.taskId); job.onsuccess = safe(function () { check(epoch); if (!job.result) fail('下载任务不存在。'); change(job.result); job.result.updatedAt = Date.now(); jobs.put(job.result); result(job.result); }); }); }); } function sleep(milliseconds, epoch) { check(epoch); return new Promise(function (resolve, reject) { waitReject = reject; waitTimer = setTimeout(function () { waitTimer = null; waitReject = null; try { check(epoch); resolve(); } catch (error) { reject(error); } }, milliseconds); }); } async function waitAllowed(job, token, epoch) { var lastRenew = 0; while (true) { check(epoch); var now = Date.now(); if (now - lastRenew > 10000) { job = await updateTask(token, epoch, function () {}); lastRenew = now; } if (job.nextAllowedAt - now > 300000) fail('网站要求等待至 ' + new Date(job.nextAllowedAt).toLocaleString() + ',到期后点继续。'); var readingBusy = typeof state !== 'undefined' && (state.busy || state.recovering); var readingLast = typeof state !== 'undefined' ? state.lastRequest || 0 : 0; var until = Math.max(job.nextAllowedAt || 0, Math.max(job.lastAttempt || 0, readingLast) + job.interval * 1000); if (!readingBusy && now >= until) return job; await sleep(Math.min(1000, Math.max(100, until - now)), epoch); } } function retryAfter(value, attempt) { var fallback = Math.min(300000, 10000 * Math.pow(2, attempt)); if (!value) return fallback; var requested; if (/^\s*\d+\s*$/.test(value)) { var seconds = Number(value); requested = Number.isFinite(seconds) && seconds <= (8640000000000000 - Date.now()) / 1000 ? seconds * 1000 : 8640000000000000 - Date.now(); } else { var date = Date.parse(value); requested = Number.isFinite(date) ? Math.max(0, date - Date.now()) : 0; } return Math.max(fallback, requested); } async function fetchPage(job,token,epoch){for(var attempt=0;attempt<4;attempt++){job=await waitAllowed(job,token,epoch);check(epoch);var url=validateUrl(job.nextUrl,null,job.bookId);job=await updateTask(token,epoch,function(stored){stored.lastAttempt=Date.now();stored.status="正在下载第 "+(stored.pageCount+1)+" 页"});check(epoch);if(typeof state!=="undefined")state.lastRequest=Date.now();if(typeof AbortController!=="function")fail("浏览器不支持取消网络请求,无法安全运行断点下载。");controller=new AbortController;let requestController=controller,timer,transient=false,response,requestKey={};activeRequest=requestKey;requesting=true;status(job.status);function checkAttempt(){check(epoch);if(activeRequest!==requestKey||requestKey.expired)throw interrupted()}try{var timeout=new Promise(function(_,reject){timer=setTimeout(function(){requestKey.expired=true;if(requestController)requestController.abort();var error=new Error("请求超时");error.downloadTransient=true;reject(error)},2e4)});var operation=async function(){response=await fetch(url.href,{credentials:"same-origin",redirect:"manual",signal:requestController?requestController.signal:undefined});checkAttempt();if(response.type==="opaqueredirect"||response.status>=300&&response.status<400||response.status===401||response.status===403)fail("需要在原站手动登录或验证,下载已暂停。");if([408,429,500,502,503,504].indexOf(response.status)>=0){var delay=retryAfter(response.headers&&response.headers.get("Retry-After"),attempt);job=await updateTask(token,epoch,function(stored){checkAttempt();stored.nextAllowedAt=Math.min(864e13,Date.now()+delay);stored.status="网站暂时限制请求,已保存重试时间。"});if(delay>3e5||attempt===3)fail("网站暂时限制请求,请在 "+new Date(job.nextAllowedAt).toLocaleString()+" 后点继续。");var error=new Error(job.status);error.downloadTransient=true;error.deadlineSaved=true;throw error}if(!response.ok)fail("网页请求失败("+response.status+"),已暂停保存进度。");if(response.url&&validateUrl(response.url,null,job.bookId).href!==url.href)fail("网页响应地址与请求不一致,下载已暂停。");var text=await response.text();checkAttempt();if(text.length>5*MB||new Blob([text]).size>5*MB)fail("单页响应超过 5 MB,下载已暂停。");return text}();return{html:await Promise.race([operation,timeout]),job:job,url:url}}catch(error){check(epoch);transient=!!error.downloadTransient||error.name==="TypeError"||error.name==="AbortError";if(!transient)throw error;if(!error.deadlineSaved)job=await updateTask(token,epoch,function(stored){stored.nextAllowedAt=Date.now()+retryAfter(null,attempt);stored.status="网络暂时失败,已保存重试时间。"});if(attempt===3)fail("网络重试失败,已暂停保存进度。")}finally{clearTimeout(timer);if(controller===requestController)controller=null;if(activeRequest===requestKey){activeRequest=null;requesting=false}if(typeof schedule==="function")schedule()}}fail("请求失败。")} function parseDocument(html) { var template = document.createElement('template'); template.innerHTML = html; return template.content; } function verifiedDocument(doc) { var title = doc.querySelector('title'); if (doc.querySelector('input[name="GOEDGE_WAF_CAPTCHA_CODE"],#loginForm,input[type="password"]') || title && /^(?:身份验证|安全验证|访问验证|人机验证|登录\s*[-—]|Just a moment)/i.test(title.textContent.trim())) fail('需要在原站手动登录或输入验证码,下载已暂停。'); } function firstChapter(doc, url, book) { verifiedDocument(doc); var current = doc.querySelector('#chapters .page-range-list .cur'); if (current) { var range = current.textContent.trim().match(/^(\d+)\s*[-—~]\s*\d+$/); var cp = Number(url.searchParams.get('cp')) || 1; if (range && Math.floor((Number(range[1]) - 1) / 100) + 1 !== cp) fail('原站返回了不同的目录组,请填写实际起始章节网址。'); } var links = doc.querySelectorAll('#chapters .direList li .name > a[href]'); for (var i = 0; i < links.length; i++) { try { var chapter = validateUrl(links[i].getAttribute('href'), url.href, book); if (/^\/read\//.test(chapter.pathname) && !chapter.search && !hidden(links[i], doc)) return chapter.href; } catch (_) {} } fail('原站目录没有可用章节,请填写实际章节网址。'); } function extractPage(doc, url, book) { verifiedDocument(doc); var content = doc.querySelector('.conC .content'); if (!content) fail('未找到可正常阅读的正文,下载已暂停。'); if (doc.querySelector('.conC .chapter-locked,.conC .vip-content,.conC .paywall,.conC [data-paid="true"]') || content.textContent.length < 200 && /购买章节|订阅本章|付费阅读/.test(content.textContent)) fail('章节未开放正常阅读,下载已暂停。'); var canonical = doc.querySelector('link[rel~="canonical"][href]'); if (canonical && validateUrl(canonical.getAttribute('href'), url.href, book).pathname !== url.pathname) fail('网页章节标识与请求不一致,下载已暂停。'); var heading = doc.querySelector('.conC h1.title'), title = heading ? heading.textContent.trim() : (doc.querySelector('title') || {}).textContent || '章节'; var paragraphs = [], nodes = content.querySelectorAll('p'); for (var i = 0; i < nodes.length; i++) { if (hidden(nodes[i], content)) continue; var text = nodes[i].hasAttribute('data-obf') ? decode(nodes[i].getAttribute('data-obf')) : nodes[i].textContent; if (text && text.trim() && !isChapterArtifact(text)) paragraphs.push(text.trim()); } if (!paragraphs.length) fail('正文为空,下载已暂停。'); var next = null, links = Array.from(doc.querySelectorAll('.readPage a[href],.page2-chapter a[href]')); links.sort(function (a, b) { return (a.textContent.indexOf('下一页') >= 0 ? 0 : 1) - (b.textContent.indexOf('下一页') >= 0 ? 0 : 1); }); for (var j = 0; j < links.length; j++) { if (hidden(links[j], doc) || !/下一页|下一章/.test(links[j].textContent)) continue; var raw = links[j].getAttribute('href'); if (!raw || /^javascript:/i.test(raw)) continue; var target = validateUrl(raw, url.href, book); if (!/^\/read\//.test(target.pathname)) continue; if (target.pathname === url.pathname) { if (pageNumber(target) !== pageNumber(url) + 1) fail('本章下一页页码没有连续前进,下载已暂停。'); } else if (pageNumber(target) !== 1) fail('下一章指向中间分页,下载已暂停。'); next = target; break; } var label = title.match(/(?:第\s*(\d+)\s*页|[((]\s*(\d+)\s*\/\s*(\d+)\s*[))])/); if (label) { var shown = Number(label[1] || label[2]), total = Number(label[3]) || 0; if (shown !== pageNumber(url) || total && total < shown) fail('显示页码与请求不一致,下载已暂停。'); if (total && shown < total && (!next || next.pathname !== url.pathname)) fail('当前章仍有分页,但缺少实际下一页链接,下载已暂停。'); if (total && shown === total && next && next.pathname === url.pathname) fail('最终页仍指向本章分页,下载已暂停。'); } var bookName = '', breadcrumbs = doc.querySelectorAll('.conC .position a[href]'); for (var b = 0; b < breadcrumbs.length; b++) { try { var bookLink = validateUrl(breadcrumbs[b].getAttribute('href'), url.href, book); if (bookLink.pathname === '/book/' + book + '.html') { bookName = breadcrumbs[b].textContent.trim().slice(0, 120); break; } } catch (_) {} } if (!bookName) { var description = doc.querySelector('meta[name="description"]'); var named = description && (description.getAttribute('content') || '').match(/《([^》]{1,120})》/); if (named) bookName = named[1]; } return { url: url.href, chapterPath: url.pathname, bookTitle: bookName, title: title.replace(/\s*[((](?:第\s*\d+\s*页|\d+\s*\/\s*\d+)[))]\s*$/, '').trim(), paragraphs: paragraphs, nextUrl: next ? next.href : '', skipped: 0 }; } function referenceBudget(pages, extra) { var count = 0, chars = 0; pages.concat(extra ? [{ paragraphs: extra }] : []).forEach(function (page) { page.paragraphs.forEach(function (text) { count++; chars += text.length; }); }); if (count > 5000 || chars > referenceLimit) fail('当前章超过安全内存上限,已暂停保留完成内容。'); } function commitPage(token, epoch, expected, page) { return transact(['control', 'tasks', 'pages'], 'readwrite', function (tx, result, safe) { var controls = tx.objectStore('control'), lock = controls.get('runner'); lock.onsuccess = safe(function () { check(epoch); if (!owns(lock.result, token)) fail('下载租约已经变更,旧响应不会写入。'); var jobs = tx.objectStore('tasks'), req = jobs.get(token.taskId); req.onsuccess = safe(function () { check(epoch); var job = req.result; if (!job || job.pageCount !== expected.pageCount || job.nextUrl !== page.url || job.seen.indexOf(page.url) >= 0) fail('断点已变更或下一页形成循环,当前页未写入。'); page.taskId = job.id; page.seq = job.pageCount; page.bytes = new Blob([JSON.stringify(page)]).size; var stats = controls.get('stats'); stats.onsuccess = safe(function () { check(epoch); if (!owns(lock.result, token)) fail('下载租约已过期,旧响应不会写入。'); var total = stats.result || { key: 'stats', totalBytes: 0 }; if (job.bytes + page.bytes > taskLimit || total.totalBytes + page.bytes > totalLimit) fail('下载存储达到 25 MB 单任务或 100 MB 总上限,请导出并删除旧任务。'); var changedChapter = job.chapterPath !== page.chapterPath; tx.objectStore('pages').add(page); total.totalBytes += page.bytes; controls.put(total); job.bytes += page.bytes; job.pageCount++; if (changedChapter) { job.chapterCount++; job.chapterStartSeq = page.seq; } job.chapterPath = page.chapterPath; job.lastUrl = page.url; job.nextUrl = page.nextUrl; job.seen.push(page.url); job.nextAllowedAt = 0; job.lastTitle = page.title; job.lastPage = pageNumber(validateUrl(page.url, null, job.bookId)); job.bookTitle = job.bookTitle || page.bookTitle; job.status = '已保存 ' + job.chapterCount + ' 章 / ' + job.pageCount + ' 页' + (page.skipped ? ';本页跳过 ' + page.skipped + ' 段重复文字' : ''); job.updatedAt = Date.now(); jobs.put(job); lock.result.expiresAt = Date.now() + leaseDuration; controls.put(lock.result); result(job); }); }); }); }); } async function run(id, epoch, options) { var token, lastMessage = '下载已暂停。'; try { token = await acquire(id, epoch); if (epoch !== generation) { await release(token, '下载已暂停。'); return; } lease = token; activeTask = id; var job = await taskById(id); check(epoch); if (options) job = await updateTask(token, epoch, function (stored) { stored.interval = options.interval; stored.maxChapters = options.maximum; }); var refs = job.chapterPath ? await taskPages(id, job.chapterPath) : []; referenceBudget(refs); while (true) { check(epoch); if (!job.nextUrl) { lastMessage = '已完成全部可用页面。'; await updateTask(token, epoch, function (stored) { stored.state = 'done'; stored.status = lastMessage; }); break; } var next = validateUrl(job.nextUrl, null, job.bookId), newChapter = /^\/read\//.test(next.pathname) && job.chapterPath !== next.pathname; if (newChapter && job.chapterCount >= job.maxChapters) { lastMessage = '已达到 ' + job.maxChapters + ' 章上限,可提高上限后继续。'; break; } if (job.seen.indexOf(next.href) >= 0) fail('下一页链接形成循环,已暂停保留进度。'); var fetched = await fetchPage(job, token, epoch); job = fetched.job; check(epoch); var doc = parseDocument(fetched.html); if (/^\/book\//.test(fetched.url.pathname)) { var first = firstChapter(doc, fetched.url, job.bookId); var bookHeading = doc.querySelector('.info h1,.bookInfo h1,h1'); job = await updateTask(token, epoch, function (stored) { stored.nextUrl = first; if (bookHeading) stored.bookTitle = bookHeading.textContent.trim().slice(0, 120); }); continue; } var page = extractPage(doc, fetched.url, job.bookId); if (job.chapterPath !== page.chapterPath) refs = []; referenceBudget(refs, page.paragraphs); var cleaned = deduplicateParagraphBlocks(page.paragraphs, refs); page.paragraphs = cleaned.paragraphs; page.skipped = cleaned.removed; referenceBudget(refs, page.paragraphs); check(epoch); job = await commitPage(token, epoch, job, page); refs.push(page); status(job.status); refreshList(); } } catch (error) { lastMessage = error.message || '下载失败,已保存断点。'; } finally { if (token) try { await release(token, lastMessage); } catch (_) {} if (epoch === generation) { running = false; requesting = false; activeTask = null; lease = null; status(lastMessage); refreshList(); } } } function pause(text) { var old = lease; generation++; running = false; requesting = false; activeRequest = null; activeTask = null; lease = null; if (controller) { controller.abort(); controller = null; } if (waitTimer) { clearTimeout(waitTimer); waitTimer = null; } if (waitReject) { var reject = waitReject; waitReject = null; reject(interrupted()); } status(text || '下载已暂停,已提交的正文和进度已保留。'); release(old, text || '下载已暂停。').then(refreshList, function (error) { status(error.message); }); if (typeof schedule === 'function') schedule(); } function integer(input, low, high) { var number = Number(input.value); if (!Number.isInteger(number) || number < low || number > high) fail('请求间隔需为 5—300 秒,章节上限需为 1—1000。'); return number; } async function start() { if (running) { status('当前标签已有下载任务,请先暂停。'); return; } running = true; var epoch = ++generation; try { var url = validateUrl(input.value.trim()); if (/^\/book\//.test(url.pathname) && !url.search) url.searchParams.set('cp', '1'); var now = Date.now(), job = { id: 'job-' + now + '-' + Math.random().toString(36).slice(2), bookId: bookOf(url.pathname), bookTitle: '', startUrl: url.href, nextUrl: url.href, lastUrl: '', chapterPath: '', chapterStartSeq: 0, chapterCount: 0, pageCount: 0, bytes: 0, seen: [], interval: integer(intervalInput, 5, 300), maxChapters: integer(maxInput, 1, 1000), lastAttempt: 0, nextAllowedAt: 0, state: 'paused', status: '等待开始', createdAt: now, updatedAt: now }; await transact(['tasks'], 'readwrite', function (tx) { check(epoch); tx.objectStore('tasks').add(job); }); check(epoch); selectedTask = job.id; run(job.id, epoch); refreshList(); } catch (error) { if (epoch === generation) { running = false; status(error.message); } } } async function resume(id) { if (running) { status('当前标签已有下载任务,请先暂停。'); return; } running = true; var epoch = ++generation; try { var job = await taskById(id); check(epoch); var interval = selectedTask === id ? integer(intervalInput, 5, 300) : job.interval; var maximum = selectedTask === id ? integer(maxInput, 1, 1000) : job.maxChapters; intervalInput.value = String(interval); maxInput.value = String(maximum); selectedTask = id; run(id, epoch, { interval: interval, maximum: maximum }); refreshList(); } catch (error) { if (epoch === generation) { running = false; status(error.message); } } } async function chooseTask(id) { try { var job = await taskById(id); selectedTask = id; intervalInput.value = String(job.interval); maxInput.value = String(job.maxChapters); input.value = job.startUrl; status('已选此任务,可调整间隔和章节上限,再点“继续下载”。' + job.status); refreshList(); } catch (error) { status(error.message); } } async function removeTask(id) { if (activeTask === id) { status('请先暂停这个任务,再删除下载内容。'); return; } if (deleteChoice !== id || Date.now() > deleteUntil) { deleteChoice = id; deleteUntil = Date.now() + 10000; status('再次点此任务的“删除”确认,仅删除下载内容。'); return; } try { await transact(['control', 'tasks', 'pages'], 'readwrite', function (tx, result, safe) { var controls = tx.objectStore('control'), lock = controls.get('runner'); lock.onsuccess = safe(function () { if (lock.result && lock.result.taskId === id && lock.result.owner && lock.result.expiresAt > Date.now()) fail('该任务仍被另一个标签下载,请先暂停。'); var jobs = tx.objectStore('tasks'), request = jobs.get(id); request.onsuccess = safe(function () { if (!request.result) return; var bytes = request.result.bytes, cursor = tx.objectStore('pages').index('taskId').openKeyCursor(IDBKeyRange.only(id)); cursor.onsuccess = safe(function () { if (cursor.result) { tx.objectStore('pages').delete(cursor.result.primaryKey); cursor.result.continue(); } }); jobs.delete(id); var stats = controls.get('stats'); stats.onsuccess = safe(function () { var data = stats.result || { key: 'stats', totalBytes: 0 }; data.totalBytes = Math.max(0, data.totalBytes - bytes); controls.put(data); }); }); }); }); prepareSequence++; if (prepared && prepared.taskId === id) clearPrepared(); if (selectedTask === id) selectedTask = null; deleteChoice = ''; status('下载任务已删除,阅读进度未修改。'); refreshList(); } catch (error) { status(error.message); } } function clearPrepared() { if (prepared) { URL.revokeObjectURL(prepared.url); if (prepared.anchor) prepared.anchor.remove(); prepared = null; } } async function prepare(id) { var sequence = ++prepareSequence; clearPrepared(); if (panel) { panel.querySelector('[data-bm-download-save]').disabled = true; panel.querySelector('[data-bm-download-copy]').disabled = true; } status('正在准备所选任务的 TXT…'); try { var job = await taskById(id), pages = await taskPages(id), parts = [], previousChapter = ''; if (sequence !== prepareSequence) return; var committed = pages.filter(function (page) { return page.seq < job.pageCount; }); if (committed.length !== job.pageCount || committed.some(function (page, index) { return page.seq !== index; })) fail('下载记录不完整,未生成可能缺页的 TXT。'); pages.forEach(function (page) { if (page.seq >= job.pageCount) return; if (page.paragraphs.length && page.chapterPath !== previousChapter) { parts.push(page.title + '\n\n'); previousChapter = page.chapterPath; } if (page.paragraphs.length) parts.push(page.paragraphs.join('\n\n') + '\n\n'); }); if (!parts.length) fail('这个任务尚无已保存的正文。'); if (sequence !== prepareSequence) return; var blob = new Blob(parts, { type: 'text/plain;charset=utf-8' }); clearPrepared(); var anchor = document.createElement('a'), url = URL.createObjectURL(blob); anchor.href = url; anchor.download = (job.bookTitle || '菠萝猫-' + job.bookId).replace(/[\\/:*?"<>|]/g, '_').slice(0, 80) + '.txt'; anchor.style.display = 'none'; document.body.appendChild(anchor); prepared = { taskId: id, url: url, anchor: anchor, text: blob.size <= MB ? parts.join('') : '', readyAt: Date.now() }; status((job.bookTitle || '书籍 ' + job.bookId) + ' · ' + job.pageCount + ' 页 TXT 已准备好,请点“保存 TXT”。保存失败时,1 MB 以内可复制文本。'); refreshList(); } catch (error) { if (sequence === prepareSequence) status(error.message || 'TXT 准备失败。'); } } function save() { if (!prepared) { status('先选择任务并点“准备 TXT”。'); return; } prepared.anchor.click(); status('已请求保存 TXT,请查看 Via 下载提示。'); var ready = prepared; setTimeout(function () { if (prepared === ready) { clearPrepared(); refreshList(); } }, 120000); } function copy() { if (!prepared || !prepared.text) { status('先准备不超过 1 MB 的 TXT,较大文件请用保存按钮。'); return; } var area = panel.querySelector('[data-bm-download-text]'); area.hidden = false; area.value = prepared.text; area.focus(); area.select(); var copied = false; try { copied = document.execCommand('copy'); } catch (_) {} status(copied ? 'TXT 文本已复制。' : '请长按文本,全选并复制。'); } function status(text) { if (message) message.textContent = text || ''; } function button(label, attr, value) { var node = document.createElement('button'); node.type = 'button'; node.textContent = label; node.setAttribute(attr, value || ''); node.style.cssText = 'padding:7px 10px;border:1px solid currentColor;border-radius:5px;background:none;color:inherit;font:inherit;font-size:14px;'; return node; } async function refreshList() { if (!list) return; var sequence = ++refreshSequence; try { var tasks = await allTasks(); if (sequence !== refreshSequence) return; list.textContent = ''; tasks.sort(function (a, b) { return b.updatedAt - a.updatedAt; }); if (!tasks.length) { var empty = document.createElement('p'); empty.textContent = '还没有下载任务。'; list.appendChild(empty); } tasks.slice(0, displayCount).forEach(function (job) { var row = document.createElement('div'); row.style.cssText = 'padding:12px 0;border-bottom:1px solid rgba(100,90,80,.2);'; var title = button((job.bookTitle || '书籍 ' + job.bookId) + ' · ' + job.chapterCount + ' 章 / ' + job.pageCount + ' 页', 'data-bm-download-select-task', job.id); title.setAttribute('aria-pressed', selectedTask === job.id ? 'true' : 'false'); if (selectedTask === job.id) { title.setAttribute('data-bm-download-selected', ''); title.style.setProperty('font-weight', '700'); title.style.setProperty('border-left', '3px solid currentColor'); } title.style.setProperty('display', 'block'); title.style.setProperty('width', '100%'); title.style.setProperty('text-align', 'left'); row.appendChild(title); var note = document.createElement('div'); note.textContent = job.status + (job.state === 'running' && activeTask !== job.id ? '(重新打开页面后需点继续)' : ''); note.style.cssText = 'font-size:12px;padding:6px 0;'; row.appendChild(note); if (job.lastTitle) { var position = document.createElement('div'); position.textContent = '最后保存:' + job.lastTitle + ' · 第 ' + (job.lastPage || 1) + ' 页'; position.style.cssText = 'font-size:12px;padding-bottom:6px;'; row.appendChild(position); } var actions = document.createElement('div'); actions.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;'; if (activeTask === job.id && running) actions.appendChild(button('暂停', 'data-bm-download-pause', job.id)); else if (job.nextUrl) actions.appendChild(button('继续下载', 'data-bm-download-resume', job.id)); actions.appendChild(button('准备 TXT', 'data-bm-download-prepare', job.id)); actions.appendChild(button('删除', 'data-bm-download-delete', job.id)); row.appendChild(actions); list.appendChild(row); }); if (tasks.length > displayCount) list.appendChild(button('显示更多任务', 'data-bm-download-more')); var saveButton = panel.querySelector('[data-bm-download-save]'), copyButton = panel.querySelector('[data-bm-download-copy]'); saveButton.disabled = !prepared; copyButton.disabled = !prepared || !prepared.text; } catch (error) { status(error.message); } } function installEntry(parent) { if (!parent || parent.querySelector('[data-bm-download-open]')) return; parent.appendChild(button('TXT 下载', 'data-bm-download-open')); } function makePanel() { panel = document.createElement('div'); panel.setAttribute('data-bm-download-panel', ''); panel.setAttribute('role', 'dialog'); panel.setAttribute('aria-label', '网页章节 TXT 下载'); panel.style.cssText = 'position:fixed;inset:0;z-index:2147483647;box-sizing:border-box;overflow:auto;overscroll-behavior:contain;font-size:16px;line-height:1.5;padding:0 14px 24px;'; var header = document.createElement('div'); header.style.cssText = 'position:sticky;top:0;z-index:2;display:flex;justify-content:space-between;align-items:center;padding:10px 0;'; var title = document.createElement('strong'); title.textContent = '网页章节 TXT 下载'; header.appendChild(title); header.appendChild(button('关闭', 'data-bm-download-close')); panel.appendChild(header); var note = document.createElement('p'); note.textContent = '保持本站标签打开。后台是否继续取决于 Via 和手机系统;离页或关闭标签会中断,重新打开后点继续。只下载你能正常阅读的正文,验证码需手动处理。'; note.style.fontSize = '13px'; panel.appendChild(note); var parameterHelp = document.createElement('p'); parameterHelp.textContent = '恢复旧任务或提高上限时,先点任务标题载入参数,再修改间隔和章节上限,最后点该任务的“继续下载”。'; parameterHelp.style.fontSize = '13px'; panel.appendChild(parameterHelp); input = document.createElement('input'); input.type = 'url'; input.setAttribute('data-bm-download-url', ''); input.setAttribute('aria-label', '起始书籍或章节网址'); input.style.cssText = 'box-sizing:border-box;width:100%;padding:8px;color:inherit;background:none;font:inherit;border:1px solid currentColor;'; panel.appendChild(input); var row = document.createElement('div'); row.style.cssText = 'display:flex;gap:10px;margin:10px 0;'; intervalInput = document.createElement('input'); intervalInput.type = 'number'; intervalInput.min = '5'; intervalInput.max = '300'; intervalInput.value = '10'; intervalInput.setAttribute('data-bm-download-interval', ''); maxInput = document.createElement('input'); maxInput.type = 'number'; maxInput.min = '1'; maxInput.max = '1000'; maxInput.value = '10'; maxInput.setAttribute('data-bm-download-max', ''); [[intervalInput, '每次请求间隔(秒)'], [maxInput, '章节总上限']].forEach(function (pair) { var label = document.createElement('label'); label.style.cssText = 'flex:1;min-width:0;font-size:13px;'; label.appendChild(document.createTextNode(pair[1])); pair[0].setAttribute('aria-label', pair[1]); pair[0].style.cssText = 'box-sizing:border-box;width:100%;padding:8px;border:1px solid currentColor;font:inherit;color:inherit;background:none;'; label.appendChild(pair[0]); row.appendChild(label); }); panel.appendChild(row); var actions = document.createElement('div'); actions.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;'; actions.appendChild(button('开始新任务', 'data-bm-download-start')); actions.appendChild(button('暂停当前任务', 'data-bm-download-pause')); actions.appendChild(button('保存 TXT', 'data-bm-download-save')); actions.appendChild(button('复制文本', 'data-bm-download-copy')); panel.appendChild(actions); message = document.createElement('div'); message.setAttribute('data-bm-download-status', ''); message.setAttribute('role', 'status'); message.style.cssText = 'padding:12px 0;font-size:13px;'; panel.appendChild(message); var area = document.createElement('textarea'); area.hidden = true; area.setAttribute('data-bm-download-text', ''); area.style.cssText = 'box-sizing:border-box;width:100%;height:160px;color:inherit;background:none;font-size:12px;'; panel.appendChild(area); list = document.createElement('div'); list.setAttribute('data-bm-download-list', ''); panel.appendChild(list); document.body.appendChild(panel); } function open() { if (typeof state !== 'undefined') { if (state.progressOpen && typeof closeProgressPanel === 'function') closeProgressPanel(); if (state.tocOpen && typeof closeCatalogue === 'function') closeCatalogue(); if (state.shelfOpen && typeof closeShelfPanel === 'function') closeShelfPanel(); } if (!panel) makePanel(); document.body.appendChild(panel); opened = true; panel.hidden = false; panel.style.setProperty('display', 'block', 'important'); var theme = themes.find(function (candidate) { return candidate.id === currentTheme; }) || themes[0]; panel.style.setProperty('background-color', theme.bg, 'important'); panel.style.setProperty('color', theme.text, 'important'); panel.firstChild.style.setProperty('background-color', theme.bg, 'important'); if (typeof themeBackground === 'function') { panel.style.setProperty('background-image', themeBackground(theme), 'important'); panel.style.setProperty('background-size', 'cover', 'important'); panel.style.setProperty('background-position', 'center', 'important'); } if (!input.value) input.value = defaultUrl(); refreshList(); status(running ? '下载正在运行,关闭此面板可继续阅读。' : '选择已保存的任务继续,或从本书创建新任务。'); } function guard(event) { var target = event.target; if (!target || !target.closest) return false; var anchor = target.closest('a[href]'); if (prepared && anchor === prepared.anchor && anchor.href === prepared.url) { event.stopImmediatePropagation(); return true; } var control = target.closest('[data-bm-download-open],[data-bm-download-close],[data-bm-download-start],[data-bm-download-pause],[data-bm-download-resume],[data-bm-download-select-task],[data-bm-download-prepare],[data-bm-download-save],[data-bm-download-copy],[data-bm-download-delete],[data-bm-download-more]'); if (control) { if (event.type === 'click') { event.preventDefault(); if (control.hasAttribute('data-bm-download-open')) open(); else if (control.hasAttribute('data-bm-download-close')) { opened = false; panel.hidden = true; panel.style.setProperty('display', 'none', 'important'); if (typeof schedule === 'function') schedule(); } else if (control.hasAttribute('data-bm-download-start')) start(); else if (control.hasAttribute('data-bm-download-pause')) pause(); else if (control.hasAttribute('data-bm-download-resume')) resume(control.getAttribute('data-bm-download-resume')); else if (control.hasAttribute('data-bm-download-select-task')) chooseTask(control.getAttribute('data-bm-download-select-task')); else if (control.hasAttribute('data-bm-download-prepare')) prepare(control.getAttribute('data-bm-download-prepare')); else if (control.hasAttribute('data-bm-download-save')) save(); else if (control.hasAttribute('data-bm-download-copy')) copy(); else if (control.hasAttribute('data-bm-download-delete')) removeTask(control.getAttribute('data-bm-download-delete')); else if (control.hasAttribute('data-bm-download-more')) { displayCount += 30; refreshList(); } } event.stopImmediatePropagation(); return true; } if (target.closest('[data-bm-download-panel]')) { event.stopImmediatePropagation(); return true; } return false; } window.addEventListener('pagehide', function () { prepareSequence++; clearPrepared(); if (running) pause('页面已离开,重新打开后点继续下载。'); }); document.addEventListener('visibilitychange', function () { if (!document.hidden && opened) refreshList(); }); return { installEntry: installEntry, open: open, guard: guard, isExportUrl: function (url) { return !!prepared && prepared.url === String(url); }, isBusy: function () { return running; }, isOpen: function () { return opened; }, isRequesting: function () { return requesting; } }; })(); // END VIA TXT DOWNLOADER 1.9.0 function guard(event) { var target = event.target; if (!target || !target.closest) return; if (purifier.guard(event)) return; if (siteAccount && siteAccount.guard(event)) return; if (readerWidget.guard(event)) return; if (readerEntry.guard(event)) return; if (captchaAutomation.guard(event)) return; if (scriptDownloader.guard(event)) return; var retryControl = target.closest('[data-bm-pager-retry]'); if (retryControl) { if (event.type === 'click') { event.preventDefault(); retryNextPage(); } event.stopImmediatePropagation(); return; } var download = target.closest('a[href]'); if (download && download === state.backupDownloadAnchor && state.backupDownloadUrl && download.getAttribute('href') === state.backupDownloadUrl) { event.stopImmediatePropagation(); return; } var backupField = target.closest('[data-bm-backup-file], [data-bm-backup-text]'); if (backupField) { event.stopImmediatePropagation(); return; } var shelfControl = target.closest('[data-bm-shelf-open], [data-bm-shelf-add], [data-bm-shelf-close], ' + '[data-bm-shelf-read], [data-bm-shelf-favorite], [data-bm-shelf-more]'); if (shelfControl) { if (event.type === 'click') { event.preventDefault(); if (shelfControl.hasAttribute('data-bm-shelf-open')) openShelfPanel(); else if (shelfControl.hasAttribute('data-bm-shelf-add')) favoriteShelfBook(libraryBookId); else if (shelfControl.hasAttribute('data-bm-shelf-close')) closeShelfPanel(); else if (shelfControl.hasAttribute('data-bm-shelf-read')) readShelfBook(shelfControl.getAttribute('data-bm-shelf-read')); else if (shelfControl.hasAttribute('data-bm-shelf-favorite')) favoriteShelfBook(shelfControl.getAttribute('data-bm-shelf-favorite')); else if (shelfControl.hasAttribute('data-bm-shelf-more')) { state.shelfLimit += 30; renderShelf(); } } event.stopImmediatePropagation(); return; } if (target.closest('[data-bm-shelf-panel], [data-bm-library-entry]')) { event.stopImmediatePropagation(); return; } var backupControl = target.closest('[data-bm-backup-export], [data-bm-backup-import], ' + '[data-bm-backup-toggle-text], [data-bm-backup-copy], [data-bm-backup-restore-text]'); if (backupControl) { if (event.type === 'click') { event.preventDefault(); if (backupControl.hasAttribute('data-bm-backup-export')) exportBackup(); else if (backupControl.hasAttribute('data-bm-backup-import')) state.backupFile.click(); else if (backupControl.hasAttribute('data-bm-backup-toggle-text')) toggleBackupText(); else if (backupControl.hasAttribute('data-bm-backup-copy')) copyBackupText(); else if (backupControl.hasAttribute('data-bm-backup-restore-text')) restoreBackupText(); } event.stopImmediatePropagation(); return; } var searchControl = target.closest('[data-bm-search-open], .btnSearchM, .searchBox form, .searchBoxM input'); if (searchControl) { if (event.type === 'click') { event.preventDefault(); openReaderSearch(); } event.stopImmediatePropagation(); return; } var readerControl = target.closest('[data-bm-toc-open], [data-bm-toc-close], ' + '[data-bm-toc-page], [data-bm-toc-url], [data-bm-toolbar-collapse], ' + '[data-bm-toolbar-expand], [data-bm-resume], [data-bm-tools-open], [data-bm-progress-close], ' + '[data-bm-progress-url], [data-bm-reader-exit], [data-bm-repeat-log-toggle], [data-bm-progress-more]'); if (readerControl) { if (event.type === 'click') { event.preventDefault(); if (readerControl.hasAttribute('data-bm-toc-open')) openCatalogue(); else if (readerControl.hasAttribute('data-bm-toc-close')) closeCatalogue(); else if (readerControl.hasAttribute('data-bm-toc-page')) { loadCatalogue(Number(readerControl.getAttribute('data-bm-toc-page'))); } else if (readerControl.hasAttribute('data-bm-toc-url')) { chooseCatalogueChapter(readerControl.getAttribute('data-bm-toc-url')); } else if (readerControl.hasAttribute('data-bm-progress-close')) closeProgressPanel(); else if (readerControl.hasAttribute('data-bm-reader-exit')) exitAssistedReader(); else if (readerControl.hasAttribute('data-bm-progress-url')) chooseHistoryPosition(readerControl); else if (readerControl.hasAttribute('data-bm-toolbar-collapse')) readerWidget.hide(); else if (readerControl.hasAttribute('data-bm-toolbar-expand')) setToolbarCollapsed(false); else if (readerControl.hasAttribute('data-bm-resume')) openProgressPanel(); else if (readerControl.hasAttribute('data-bm-tools-open')) openToolsPanel(); else if (readerControl.hasAttribute('data-bm-repeat-log-toggle')) toggleRepeatLog(); else if (readerControl.hasAttribute('data-bm-progress-more')) appendRecentProgressRows(); } event.stopImmediatePropagation(); return; } var fontButton = target.closest('[data-bm-font-step]'); if (fontButton) { if (event.type === 'touchstart') fontTouch.button = fontButton; if (event.type === 'touchend' && fontTouch.button === fontButton) { if (!fontButton.disabled) applyFontSize(fontSize + Number(fontButton.getAttribute('data-bm-font-step'))); fontTouch.at = Date.now(); } if (event.type === 'click') { event.preventDefault(); if (!fontButton.disabled && !(fontTouch.button === fontButton && Date.now() - fontTouch.at < 700)) { applyFontSize(fontSize + Number(fontButton.getAttribute('data-bm-font-step'))); } fontTouch.button = null; } event.stopImmediatePropagation(); return; } var themeButton = target.closest('[data-bm-theme-choice]'); if (themeButton) { if (event.type === 'touchend') { themeTouch.button=themeButton;themeTouch.at=Date.now(); if(themeButton.getAttribute('data-bm-theme-choice')!==currentTheme)applyTheme(themeButton.getAttribute('data-bm-theme-choice')); } else if (event.type === 'click') { event.preventDefault(); if(!(themeTouch.button===themeButton&&Date.now()-themeTouch.at<700)&&themeButton.getAttribute('data-bm-theme-choice')!==currentTheme) applyTheme(themeButton.getAttribute('data-bm-theme-choice')); themeTouch.button=null; } event.stopImmediatePropagation(); return; } if (progressUiGuard(event)) return; var anchor = target.closest('a[href], a[_href]'); if (anchor && state.reader && state.reader.contains(anchor) && /^\s*目录\s*$/.test(anchor.textContent)) { var indexUrl = siteUrl(anchor.getAttribute('href')); if (indexUrl && indexUrl.pathname === '/book/' + bookId + '.html') { if (event.type === 'click') { event.preventDefault(); openCatalogue(); } event.stopImmediatePropagation(); return; } } var href = anchor && (anchor.getAttribute('href') || '#'); var placeholder = href && /^javascript:\s*(?:void\s*\(\s*0\s*\)\s*;?|;)$/i.test(href.trim()); if (anchor && ((!placeholder && !siteUrl(href)) || (anchor.hasAttribute('_href') && !siteUrl(anchor.getAttribute('_href'))))) { event.preventDefault(); event.stopImmediatePropagation(); return; } if (target.closest('[data-bm-pager-status]')) event.stopImmediatePropagation(); } ['click', 'auxclick', 'pointerdown', 'mousedown', 'touchstart', 'touchend'].forEach(function (type) { window.addEventListener(type, guard, { capture: true, passive: false }); }); window.addEventListener('submit', function (event) { if (event.target && event.target.matches && event.target.matches('.searchBox form')) { event.preventDefault(); event.stopImmediatePropagation(); searchNavigationUntil = Date.now() + 5000; location.assign(searchEntry.href); return; } if (event.target && event.target.action && !siteUrl(event.target.action)) { event.preventDefault(); event.stopImmediatePropagation(); } }, true); if (window.navigation && window.navigation.addEventListener) { window.navigation.addEventListener('navigate', function (event) { if (event.cancelable && !siteUrl(event.destination.url) && !(state.backupDownloadUrl && event.destination.url === state.backupDownloadUrl) && !scriptDownloader.isExportUrl(event.destination.url) && !intentionalSearchNavigation(event.destination.url)) event.preventDefault(); }); } if (!bookId) { try { localStorage.__via_reader_enabled__ = null; } catch (_) {} var catalogMatch = location.pathname.match(/^\/book\/(\d+)\.html$/); var visitedCp = Number(new URLSearchParams(location.search).get('cp')); if (catalogMatch && visitedCp >= 1 && visitedCp <= 100 && visitedCp % 1 === 0) { try { localStorage.setItem('bm-reader-catalog-page-' + catalogMatch[1], String(visitedCp)); } catch (_) {} } if (catalogMatch) { var rememberBookOrders = function () { var cp = visitedCp >= 1 && visitedCp <= 100 && visitedCp % 1 === 0 ? visitedCp : 1; var key = 'bm-reader-chapter-order-' + catalogMatch[1] + '-v1'; var titleKey = 'bm-reader-chapter-title-' + catalogMatch[1] + '-v1'; var orders = new Map(); var titles = new Map(); try { var stored = JSON.parse(localStorage.getItem(key) || '[]'); if (Array.isArray(stored)) stored.forEach(function (item) { if (Array.isArray(item) && typeof item[0] === 'string' && Number.isInteger(item[1])) { orders.set(item[0], item[1]); } }); } catch (_) {} try { var knownTitles = JSON.parse(localStorage.getItem(titleKey) || '[]'); if (Array.isArray(knownTitles)) knownTitles.forEach(function (item) { if (Array.isArray(item) && typeof item[0] === 'string' && typeof item[1] === 'string') { titles.set(item[0], cleanChapterTitle(item[1])); } }); } catch (_) {} var links = document.querySelectorAll('#chapters .direList li .name > a[href]'); for (var i = 0; i < links.length; i++) { var chapter = siteUrl(links[i].getAttribute('href')); if (chapter && new RegExp('^/read/' + catalogMatch[1] + '/\\d+\\.html$').test(chapter.pathname) && !chapter.search) { orders.set(chapter.pathname, (cp - 1) * 100 + i + 1); titles.set(chapter.pathname, cleanChapterTitle(links[i].textContent)); } } if (links.length) try { localStorage.setItem(key, JSON.stringify(Array.from(orders))); } catch (_) {} if (links.length) try { localStorage.setItem(titleKey, JSON.stringify(Array.from(titles))); } catch (_) {} installBookShelfActions(); }; if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', rememberBookOrders, { once: true }); else rememberBookOrders(); } return; } function decode(value) { var raw = atob(value); var bytes = new Uint8Array(raw.length); for (var i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i) ^ ((i % 127) + 1); if (typeof TextDecoder === 'function') return new TextDecoder('utf-8', { fatal: true }).decode(bytes); var percent = ''; for (var j = 0; j < bytes.length; j++) percent += '%' + ('0' + bytes[j].toString(16)).slice(-2); return decodeURIComponent(percent); } function hidden(node, boundary) { for (var el = node; el && el !== boundary; el = el.parentElement) { var style = (el.getAttribute('style') || '').replace(/\s/g, '').toLowerCase(); if (el.hidden || el.getAttribute('aria-hidden') === 'true' || el.classList.contains('honeypot') || /(?:display:none|visibility:hidden|opacity:0(?:;|$)|font-size:0(?:px)?(?:;|$))/.test(style)) return true; } return false; } function isChapterArtifact(text) { return /^\s*chapter_\s*\(\s*\)\s*;?\s*$/i.test(text); } function deduplicateParagraphBlocks(paragraphs,references){var normalized=paragraphs.map(function(text){return String(text).replace(/[\s\u200b-\u200d\ufeff]+/g,"")});var lengths=[0];normalized.forEach(function(text){lengths.push(lengths[lengths.length-1]+text.length)});var removed=new Map;function digest(text){var first=2166136261,second=5381;for(var i=0;i>>0).toString(16)+":"+(second>>>0).toString(16)}var digests=normalized.map(digest);function paragraphPositions(texts){var positions=new Map;texts.forEach(function(text,index){var list=positions.get(text);if(list)list.push(index);else positions.set(text,[index])});return positions}var internalPositions=paragraphPositions(normalized);var knownParagraphs=new Map;var hasReferenceBlock=false;function longEnough(start,count){return count>=5&&lengths[start+count]-lengths[start]>=160}function targetFor(index){var alias=removed.get(index);return alias?{toUrl:alias.toUrl,toIndex:alias.toIndex}:{toUrl:null,toIndex:index}}function mark(start,count,targets){for(var offset=0;offset=0?originalIndex:index})});for(var candidate=0;candidate0&&source>0&&texts[source-1]===compared[candidate-1])continue;var count=0;while(source+count=0?originalIndex:referenceStart+offset}})})(candidate,source,count)}}});for(var candidate=1;candidate0&&normalized[source-1]===normalized[candidate-1])continue;var count=0;while(source+count>> 0).toString(16) + ':' + (second >>> 0).toString(16); } function cleanPageParagraphs(page, references) { if (page.paragraphIndexes) return page; page.originalParagraphCount = page.paragraphs.length; var result = deduplicateParagraphBlocks(page.paragraphs, references || []); page.paragraphs = result.paragraphs; page.paragraphIndexes = result.indexes; page.progressAliases = result.aliases; page.removedRepeated = result.removed; page.fingerprint = contentFingerprint(page.paragraphs); return page; } function referencesForChapter(url) { return state.repeatMemory.path === url.pathname ? state.repeatMemory.pages : []; } function rememberParagraphDigests(page) { if (!page.paragraphs.length) return; if (state.repeatMemory.path !== page.url.pathname) state.repeatMemory = { path: page.url.pathname, pages: [], size: 0 }; var memory = state.repeatMemory; var record = { url: page.url.href, keys: page.paragraphs.map(function (text) { return contentFingerprint([text]); }), indexes: (page.paragraphIndexes || page.paragraphs.map(function (_, i) { return i; })).slice() }; memory.pages.push(record); memory.size += record.keys.length; while (memory.size > 20000 && memory.pages.length > 1) memory.size -= memory.pages.shift().keys.length; } function pageModelForUrl(url) { var key = pageKey(url); if (state.first && pageKey(state.first.url) === key) return state.first; for (var i = 0; i < state.pages.length; i++) if (pageKey(state.pages[i].url) === key) return state.pages[i]; return null; } function recoverablePageError(message) { var error = new Error(message); error.bmRecoverable = true; return error; } function isForwardReadLink(current, next) { if (current.pathname === next.pathname) return progressPage(next) === progressPage(current) + 1; if (progressPage(next) !== 1) return false; var before = state.chapterOrders.get(current.pathname); var after = state.chapterOrders.get(next.pathname); return !(before && after) || after > before; } function verifyDisplayedPage(heading, url) { if (!heading) return { number: progressPage(url), total: 0 }; var label = heading.querySelector('.pageLabel'); var text = (label || heading).textContent.trim(); var pair = text.match(/[((]\s*(\d+)\s*\/\s*(\d+)\s*[))]\s*$/) || (label && text.match(/^\s*(\d+)\s*\/\s*(\d+)\s*$/)); var number = text.match(/[((]\s*第\s*(\d+)\s*页\s*[))]\s*$/) || (label && text.match(/^(?:第\s*)?(\d+)\s*页$/)); var actual = pair ? Number(pair[1]) : number ? Number(number[1]) : 0; var total = pair ? Number(pair[2]) : 0; if ((pair && !(actual >= 1 && total >= actual && total <= 10000)) || (actual && (actual !== progressPage(url) || (total && progressPage(url) > total)))) { throw recoverablePageError('网站返回的正文页码与请求不一致'); } return { number: actual || progressPage(url), total: total }; } function extract(doc,url){var title=doc.querySelector("title");var docTitle=doc.title||(title?title.textContent:"");if(doc.querySelector('input[name="GOEDGE_WAF_CAPTCHA_CODE"], #loginForm')||/^(?:身份验证|安全验证|访问验证|人机验证|登录\s*[-—]|Just a moment)/i.test(docTitle.trim())){throw new Error("下一页需要验证码或登录,自动接续已停止")}var content=doc.querySelector(".conC .content");if(!content)throw new Error("未找到下一页正文,自动接续已停止");var canonical=doc.querySelector('link[rel~="canonical"][href]');if(canonical){var identity=readUrl(canonical.getAttribute("href"),url.href);if(!identity)throw new Error("网站返回的章节标识不属于本站这本书,接续已停止");if(identity.pathname!==url.pathname)throw recoverablePageError("网站返回的章节标识与请求不一致")}var nodes=content.querySelectorAll("p");var paragraphs=[];for(var i=0;i .font14");return{url:url,title:heading?heading.textContent.trim():docTitle,docTitle:docTitle,chapterInfo:chapterInfo?chapterInfo.textContent.trim():"",paragraphs:paragraphs,fingerprint:contentFingerprint(paragraphs),next:next,nextIssue:!next?invalidNext:"",pageNumber:paging.number,pageTotal:paging.total,content:content}} function hasPage(target, url) { var wanted = pageKey(url); if (target.getAttribute('data-bm-page') === wanted) return true; var nodes = target.querySelectorAll('[data-bm-page]'); for (var i = 0; i < nodes.length; i++) if (nodes[i].getAttribute('data-bm-page') === wanted) return true; return false; } function appendPage(target, page) { if (!target || hasPage(target, page.url)) return; var section = document.createElement('section'); section.setAttribute('data-bm-page', pageKey(page.url)); if (page.chapterStart) { var title = document.createElement('h2'); title.textContent = page.title; section.appendChild(title); } page.paragraphs.forEach(function (text, index) { var paragraph = document.createElement('p'); paragraph.textContent = text; paragraph.setAttribute('data-bm-source-index', String(page.paragraphIndexes ? page.paragraphIndexes[index] : index)); section.appendChild(paragraph); }); target.appendChild(section); } function compactReadPages(){if(state.compacting||state.busy||state.tocOpen||state.progressOpen||state.shelfOpen||scriptDownloader.isOpen()||(captchaAutomation.isOpen()||siteAccount&&siteAccount.isOpen())||document.hidden||!activeReader()||!state.first||state.pages.length+1<=12)return;var pages=[state.first].concat(state.pages);var paragraphs=state.content.querySelectorAll("p[data-bm-progress-index]");var viewport=state.reader.getBoundingClientRect();var height=state.reader.clientHeight;if(!(height>0)||!Number.isFinite(viewport.top))return;var anchorY=viewport.top+Math.min(120,height*.22);var visible=null;var bounds=null;var starts=new Map;for(var i=0;i0)||!Number.isFinite(rect.top)||!Number.isFinite(rect.bottom))continue;var key=pageKey(url);if(!starts.has(key))starts.set(key,rect.top);if(!visible&&rect.bottom>anchorY&&rect.top0&&!(starts.has(pageKey(pages[cut].url))&&starts.get(pageKey(pages[cut].url))<=viewport.top-64))cut--;if(!(cut>0))return;var index=visible.getAttribute("data-bm-progress-index");var retained=pages.slice(cut);try{history.replaceState(history.state,"",visibleKey)}catch(_){return}state.compacting=true;try{saveProgressNow();replaceReadingPages(retained[0],retained.slice(1));delete retained[0].content;state.first=retained[0];state.pages=retained.slice(1);state.compactedPages+=cut;markProgressParagraphs();var updated=state.content.querySelectorAll("p[data-bm-progress-index]");for(var j=0;j0&&Number.isFinite(after.top))state.reader.scrollTop+=after.top-bounds.top;break}syncVisiblePage(readUrl(visibleKey));if(state.progressPanel){var note=state.progressPanel.querySelector("[data-bm-memory-note]");if(note)note.hidden=false}}finally{state.compacting=false}} function hideReaderNavigation(reader, content) { var metadata = reader.querySelector('.via-reader-header > div'); if (metadata) metadata.remove(); var siteNav = content.querySelectorAll('.readPage, .page2-chapter'); for (var i = 0; i < siteNav.length; i++) siteNav[i].remove(); var paragraphs = content.querySelectorAll('p'); for (var j = 0; j < paragraphs.length; j++) { var paragraph = paragraphs[j]; if (!paragraph.querySelector('a[_href], a[href*="/book/"]')) continue; var text = paragraph.textContent.replace(/[\s||·•]/g, ''); if (!/^(?:(?:上一|下一)(?:页|章)|目录)+$/.test(text)) continue; if (paragraph.nextElementSibling && paragraph.nextElementSibling.tagName === 'BR') { paragraph.nextElementSibling.remove(); } paragraph.remove(); } } function updateStatus() { if (!state.status) return; var message = state.recovering ? state.recoveryMessage : state.retryPending ? '等待重试接续…' : state.blocked ? state.error : state.busy ? '正在接续下一页…' : ''; if (state.status.getAttribute('data-message') === message) return; state.status.setAttribute('data-message', message); state.status.textContent = message; state.status.hidden = !message; if (state.blocked && state.next && !state.retryPending) { var retry = document.createElement('button'); retry.type = 'button'; retry.textContent = '重试接续'; retry.setAttribute('data-bm-pager-retry', ''); retry.style.cssText = 'padding:5px 8px;margin:4px 6px 4px 0;border:1px solid currentColor;' + 'border-radius:5px;background:none;color:inherit;font-size:inherit;'; state.status.appendChild(document.createTextNode(' ')); state.status.appendChild(retry); var manual = document.createElement('a'); manual.href = state.next.href; manual.textContent = '打开该页手动处理'; state.status.appendChild(document.createTextNode(' ')); state.status.appendChild(manual); } } function halt(message) { state.blocked = true; state.error = message; if (state.timer) { clearTimeout(state.timer); state.timer = null; } updateStatus(); } function activeReader() { if (!state.reader || !state.reader.isConnected || !state.content || !state.content.isConnected || state.reader.hidden) return false; var style = window.getComputedStyle(state.reader); return style.display !== 'none' && style.visibility !== 'hidden' && style.visibility !== 'collapse'; } function nearEnd() { if (!readerEntry.allowPaging()) return false; if (!activeReader() || document.hidden || state.tocOpen || state.progressOpen || state.shelfOpen || scriptDownloader.isOpen() || (captchaAutomation.isOpen() || (siteAccount && siteAccount.isOpen())) || scriptDownloader.isRequesting()) return false; var height = state.reader.clientHeight; if (!(height > 0 && state.reader.scrollHeight >= height)) return false; return state.reader.scrollHeight - state.reader.scrollTop - height <= Math.min(350, height * 0.6); } function schedule() { if (state.timer || state.busy || state.blocked || (!state.next && !state.nextIssue) || !nearEnd()) return; state.timer = setTimeout(function () { state.timer = null; if (nearEnd()) loadNext(); }, Math.max(250, 2000 - (Date.now() - state.lastRequest))); } function retryNextPage() { if (!state.blocked || state.retryPending || state.busy || state.timer || !state.next || !activeReader() || state.tocOpen || state.progressOpen) return; var sequence = state.readSeq; state.retryPending = true; updateStatus(); state.timer = setTimeout(function () { state.timer = null; state.retryPending = false; if (sequence !== state.readSeq || !activeReader() || state.tocOpen || state.progressOpen || document.hidden) { updateStatus(); return; } state.blocked = false; state.error = ''; loadNext(true); }, Math.max(0, 2000 - (Date.now() - state.lastRequest))); } function cancelledRead() { var error = new Error('阅读操作已取消'); error.bmCancelled = true; return error; } function checkReadSequence(sequence, recovery) { if (sequence !== state.readSeq || (recovery && (!activeReader() || state.tocOpen || state.progressOpen || document.hidden))) { throw cancelledRead(); } } function cancelAutoRecovery() { if (!state.recovering) return; state.readSeq++; if (state.readController) { state.readController.abort(); state.readController = null; } if (state.recoveryTimer) { clearTimeout(state.recoveryTimer); state.recoveryTimer = null; } if (state.recoveryResolve) { state.recoveryResolve(false); state.recoveryResolve = null; } state.recovering = false; state.recoveryMessage = ''; state.busy = false; updateStatus(); } async function waitAutoRecovery(delay, sequence, message) { checkReadSequence(sequence, true); state.recovering = true; state.recoveryMessage = message || '正在自动校正接续…'; updateStatus(); if (delay > 0) { var elapsed = await new Promise(function (resolve) { state.recoveryResolve = resolve; state.recoveryTimer = setTimeout(function () { state.recoveryTimer = null; state.recoveryResolve = null; resolve(true); }, delay); }); if (!elapsed) throw cancelledRead(); } checkReadSequence(sequence, true); } async function clearFailedPageCache(url, sequence) { if (!window.caches || !/^\/read\//.test(url.pathname)) return; try { var names = await caches.keys(); checkReadSequence(sequence, true); for (var i = 0; i < names.length; i++) { if (!/^boluobao-v\d+$/.test(names[i])) continue; var cache = await caches.open(names[i]); checkReadSequence(sequence, true); await cache.delete(url.href, { ignoreVary: true }); } } catch (error) { if (error.bmCancelled) throw error; } } async function fetchReadDocument(url, sequence, fresh, recovery) { checkReadSequence(sequence, recovery); if (fresh) await clearFailedPageCache(url, sequence); checkReadSequence(sequence, recovery); var controller = typeof AbortController === 'function' ? new AbortController() : null; state.readController = controller; var timeout; try { var operation = (async function () { var response = await fetch(url.href, { credentials: 'same-origin', redirect: 'manual', cache: fresh ? 'no-store' : 'default', signal: controller ? controller.signal : undefined }); if (response.type === 'opaqueredirect' || (response.status >= 300 && response.status < 400)) { if (await openReadingVerification(url, sequence, recovery)) throw cancelledRead(); throw new Error('下一页要求跳转,可能需要验证码或登录;可在进度中配置在线验证'); } if (!response.ok) throw new Error('下一页请求失败(' + response.status + '),自动接续已停止'); if (response.url && /\/WAF\/VERIFY\/CAPTCHA(?:[?#]|$)/.test(response.url)) { if (await openReadingVerification(url, sequence, recovery)) throw cancelledRead(); throw new Error('下一页需要验证;可在进度中配置在线验证'); } if (response.url) { var finalUrl = /^\/read\//.test(url.pathname) ? readUrl(response.url) : siteUrl(response.url); if (!finalUrl || (/^\/read\//.test(url.pathname) ? pageKey(finalUrl) !== pageKey(url) : finalUrl.pathname !== url.pathname || finalUrl.search !== url.search)) { throw new Error('下一页响应地址异常,自动接续已停止'); } } var template = document.createElement('template'); template.innerHTML = await response.text(); return template.content; })(); var doc = await Promise.race([operation, new Promise(function (_, reject) { timeout = setTimeout(function () { if (controller) controller.abort(); reject(new Error('下一页网络超时,自动接续已停止')); }, 20000); })]); checkReadSequence(sequence, recovery); return doc; } finally { clearTimeout(timeout); if (state.readController === controller) state.readController = null; } } async function repairCataloguePage(cp, sequence) { await waitAutoRecovery(Math.max(0, 2000 - (Date.now() - state.lastRequest)), sequence, '正在根据本站目录自动校正…'); var url = new URL('/book/' + bookId + '.html', origin); if (cp > 1) url.searchParams.set('cp', String(cp)); state.lastRequest = Date.now(); var doc = await fetchReadDocument(url, sequence, true, true); checkReadSequence(sequence, true); var current = doc.querySelector('#chapters .page-range-list .cur'); var range = current && current.textContent.trim().match(/^(\d+)\s*[-—~]\s*(\d+)$/); var actualCp = range ? Math.floor((Number(range[1]) - 1) / 100) + 1 : cp; if (!(actualCp >= 1 && actualCp <= 100)) throw new Error('无法确认本站目录区间,当前阅读已保留'); var data = parseCatalogue(doc, url, actualCp); state.tocCache.set(actualCp, data); return { cp: actualCp, data: data }; } async function nextFromRealCatalogue(cursor,sequence){checkReadSequence(sequence,true);var knownNext=null;state.tocCache.forEach(function(data,cp){if(knownNext||!data||!Array.isArray(data.chapters))return;var index=data.chapters.findIndex(function(chapter){return chapter.url.pathname===cursor.pathname});if(index<0)return;if(index+1=1&&queued<=100&&!visited.has(queued))cp=queued}if(!cp){for(var missing=1;missing<=max;missing++)if(!visited.has(missing)){cp=missing;break}}if(!cp)break;visited.add(cp);var result=await repairCataloguePage(cp,sequence);checkReadSequence(sequence,true);visited.add(result.cp);max=result.data.max;var chapters=result.data.chapters;var index=chapters.findIndex(function(chapter){return chapter.url.pathname===cursor.pathname});if(index<0)continue;if(index+1=max)return null;var following=await repairCataloguePage(result.cp+1,sequence);checkReadSequence(sequence,true);if(following.cp!==result.cp+1||!following.data.chapters.length){throw new Error("本站目录区间没有前进,当前阅读已保留")}return following.data.chapters[0].url}throw new Error("真实目录中未找到当前章节,当前阅读已保留")} async function loadNext(freshRequest){if(state.busy||state.blocked||scriptDownloader.isOpen()||(captchaAutomation.isOpen()||siteAccount&&siteAccount.isOpen())||scriptDownloader.isRequesting()||!state.next&&!state.nextIssue||(freshRequest===true?!activeReader()||state.tocOpen||state.progressOpen:!nearEnd()))return;var sequence=state.readSeq;var lastPage=state.pages.length?state.pages[state.pages.length-1]:state.first;var previous=lastPage.url;var url=state.next&&readUrl(state.next.href);var cursor=previous;var attempts=0;var correctedChapters=0;var skippedParts=0;var targets=new Set;var repair=!!state.nextIssue||!!url&&(state.seen.has(pageKey(url))||!isForwardReadLink(previous,url));bookProgress.stop();state.busy=true;updateStatus();try{if(state.nextIssue&&lastPage.pageTotal>progressPage(previous)){url=new URL(previous.href);url.searchParams.set("p",String(progressPage(previous)+1));repair=false;freshRequest=true;await waitAutoRecovery(Math.max(0,2e3-(Date.now()-state.lastRequest)),sequence,"正在自动校正本章页码…")}while(true){if(repair){if(++correctedChapters>3)throw new Error("本站连续返回异常内容,自动校正暂未取得新正文,当前阅读已保留");url=await nextFromRealCatalogue(cursor,sequence);if(!url){state.next=null;state.nextIssue="";return}if(targets.has(pageKey(url))||state.seen.has(pageKey(url))){throw new Error("本站目录链接重复,当前阅读已保留")}targets.add(pageKey(url));cursor=url;repair=false;freshRequest=true}if(!url)throw new Error("下一页地址不属于本站这本书,自动接续已停止");if(state.recovering)await waitAutoRecovery(Math.max(0,2e3-(Date.now()-state.lastRequest)),sequence);state.lastRequest=Date.now();var page=null;try{var doc=await fetchReadDocument(url,sequence,freshRequest===true,state.recovering);page=cleanPageParagraphs(extract(doc,url),referencesForChapter(url));if(!page.paragraphs.length){var canAdvancePart=page.url.pathname===previous.pathname&&page.pageTotal>0&&page.pageNumber===progressPage(page.url)&&(page.next||page.pageNumber12)throw new Error("本站连续重复分页过多,当前阅读已保留");recordRepeatPage(page,"skip");var followingPart=page.next;if(!followingPart&&page.pageNumber