public paste
VxSzy6nf3
—4 views467,270 chars487.5 KB
stdout
Paste content
// ==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<ids.length;i++){if(stopped||token!==epoch||expectedScope!==scope||status.phase==="paused"||!status.loggedIn)return;var id=ids[i],action=cache.pendingFavorite[id];var response=await api.setFavorite(scopedContext(token),id,action.favorite,session);if(token!==epoch||stopped||expectedScope!==scope)return;if(!plain(response)||response.success!==true)throw problem("format","网站尚未确认收藏同步成功,自动操作已暂停");if(cache.pendingFavorite[id]&&cache.pendingFavorite[id].time===action.time&&cache.pendingFavorite[id].favorite===action.favorite)delete cache.pendingFavorite[id];persist();emit()}}function enqueueMutation(work,expectedScope){expectedScope=expectedScope||scope;var workEpoch=epoch;mutationChain=mutationChain.catch(function(){}).then(work).catch(function(error){if(!stopped&&workEpoch===epoch&&expectedScope===scope)pause(error)});return mutationChain}function refresh(manual){if(stopped||!isVisible())return Promise.resolve(publicStatus());if(status.phase==="paused"&&manual!==true)return Promise.resolve(publicStatus());if(inFlight)return inFlight;var token=epoch;status.phase="connecting";status.message="正在连接网站账号…";emit();inFlight=(async function(){try{var result=await inspect();if(token!==epoch||stopped)return publicStatus();if(earlyFavoriteIntents.size){earlyFavoriteIntents.forEach(function(action,id){cache.manualFavorite[id]=Object.assign({},action);cache.pendingFavorite[id]=Object.assign({},action)});earlyFavoriteIntents.clear();persist();emit()}await enqueueMutation(function(){return checkIn(result,token)});if(status.phase==="paused"||token!==epoch||stopped)return publicStatus();var read=await Promise.all([typeof api.readShelf==="function"?Promise.resolve(api.readShelf(scopedContext(token),result)).then(normalShelf):Promise.resolve(cache.shelf),typeof api.readRecent==="function"?Promise.resolve(api.readRecent(scopedContext(token),result)).then(normalRecent):Promise.resolve(cache.recent)]);if(token!==epoch||stopped)return publicStatus();cache.shelf=read[0];cache.recent=read[1];cache.lastSyncAt=now();mergeRemoteIntoLocal();persist();await enqueueMutation(function(){return flushFavorites(token)});if(status.phase==="paused"||token!==epoch||stopped)return publicStatus();status.phase="ready";status.lastSyncAt=cache.lastSyncAt;status.message=(status.signedToday?"今日签到已由网站确认;":"网站账号已连接;")+"书架与最近阅读已同步"+(needsFavoriteSync?";启动时未同步的本地收藏可点“同步本地已收藏”上传":"");emit()}catch(error){if(token===epoch&&!stopped)pause(error)}finally{if(token===epoch){inFlight=null;scheduleVisible()}}return publicStatus()})();return inFlight}function favoriteChanged(id,favorite,at){if(stopped||!idOf(id)||typeof favorite!=="boolean")return Promise.resolve(false);var action={favorite:favorite,time:time(at)||now()};if(!scope){if(earlyFavoriteIntents.size>=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;i<entries.length;i++){if(stopped||token!==epoch||expectedScope!==scope||status.phase==="paused"||!isVisible())return;var entry=entries[i],previous=recentLastSent.get(entry.id);if(previous&&previous.key===entry.key){if(recentQueue.get(entry.id)===entry)recentQueue.delete(entry.id);emit();continue}if(now()<entry.readyAt){if(!recentTimer)recentTimer=setTimeout(function(){recentTimer=null;flushRecent()},Math.max(1,entry.readyAt-now()));continue}var response=await api.writeRecent(scopedContext(token),entry,session);if(token!==epoch||stopped||expectedScope!==scope)return;if(plain(response)&&response.deferred===true)return;if(!plain(response)||response.success!==true)throw problem("format","网站尚未确认最近阅读同步成功,自动操作已暂停");recentLastSent.set(entry.id,{key:entry.key,at:now()});if(recentQueue.get(entry.id)===entry)recentQueue.delete(entry.id);emit()}},expectedScope).then(function(){return status.phase!=="paused"})}function installEntry(parent){if(!parent||parent.querySelector("[data-bm-account-open]"))return;var button=doc.createElement("button");button.type="button";button.textContent="网站账号";button.setAttribute("data-bm-account-open","");parent.appendChild(button)}function makePanel(){if(doc.querySelector("[data-bm-account-panel]"))return doc.querySelector("[data-bm-account-panel]");var panel=doc.createElement("section");panel.setAttribute("data-bm-account-panel","");panel.setAttribute("role","dialog");panel.setAttribute("aria-label","网站账号与同步");panel.hidden=true;panel.style.cssText="position:fixed;inset:0;z-index:2147483647;box-sizing:border-box;overflow:auto;padding:18px 16px;background:#f6f1e8;color:#463d32;font:16px/1.6 sans-serif;";var title=doc.createElement("strong");title.textContent="网站账号与同步";panel.appendChild(title);var stateRow=doc.createElement("p");stateRow.setAttribute("role","status");rows.push(stateRow);panel.appendChild(stateRow);var note=doc.createElement("p");note.textContent="打开本站页面时自动检查当天签到。关闭页面后不会定时签到。网站最近阅读保存章节;本机段落进度继续用于精确续读。";note.style.fontSize="13px";panel.appendChild(note);[["refresh","重新连接 / 同步"],["favorites","同步本地已收藏"],["close","返回阅读 ×"]].forEach(function(item){var button=doc.createElement("button");button.type="button";button.textContent=item[1];button.setAttribute("data-bm-account-"+item[0],"");button.style.cssText="margin:6px 8px 6px 0;padding:8px 12px;border:1px solid currentColor;border-radius:7px;background:none;color:inherit;font:inherit;";panel.appendChild(button)});var links=Array.isArray(options.taskLinks)?options.taskLinks:[];links.forEach(function(item){var url=safeUrl(item.url);if(!url)return;var anchor=doc.createElement("a");anchor.textContent=text(item.label,50);anchor.href=url.href;anchor.setAttribute("data-bm-account-task","");anchor.style.cssText="display:block;padding:10px 0;color:inherit;";panel.appendChild(anchor)});var help=doc.createElement("p");help.textContent="点赞、评分、评论、头像与密码请在网站按真实意愿操作。";help.style.fontSize="13px";panel.appendChild(help);doc.body.appendChild(panel);emit();return panel}function open(){if(typeof hooks.beforeOpen==="function")hooks.beforeOpen();var panel=makePanel();panel.hidden=false;panel.style.setProperty("display","block","important");if(typeof hooks.applyTheme==="function")hooks.applyTheme(panel);emit()}function guard(event){var target=event.target;if(!target||!target.closest)return false;var control=target.closest("[data-bm-account-open],[data-bm-account-close],[data-bm-account-refresh],[data-bm-account-favorites]");if(control){if(event.type==="click"){event.preventDefault();if(control.hasAttribute("data-bm-account-open"))open();else if(control.hasAttribute("data-bm-account-close")){var panel=doc.querySelector("[data-bm-account-panel]");if(panel){panel.hidden=true;panel.style.setProperty("display","none","important")}if(typeof hooks.afterClose==="function")hooks.afterClose()}else if(control.hasAttribute("data-bm-account-refresh"))refresh(true);else if(control.hasAttribute("data-bm-account-favorites"))syncLocalFavorites()}event.stopImmediatePropagation();return true}if(target.closest("[data-bm-account-panel]")){event.stopImmediatePropagation();return true}return false}function halt(manual){if(!manual&&(manualStopped||suspended))return;if(!manual){suspendedPhase=status.phase;suspendedMessage=status.message}manualStopped=manualStopped||manual;suspended=!manual&&!manualStopped;if(!scope&&earlyFavoriteIntents.size){earlyFavoriteIntents.clear();needsFavoriteSync=true}stopped=true;running=false;epoch++;clearTimeout(timer);clearTimeout(recentTimer);timer=null;recentTimer=null;controllers.forEach(function(controller){controller.abort()});controllers.clear();inFlight=null;mutationChain=Promise.resolve();if(manual){status.phase="stopped";status.message="网站账号自动连接已停止";emit()}}function stop(){halt(true)}function resumeFromCache(event){if(!event||event.persisted!==true||!suspended||manualStopped)return Promise.resolve(publicStatus());suspended=false;stopped=false;running=true;epoch++;status.phase=suspendedPhase==="paused"?"paused":"idle";status.message=suspendedPhase==="paused"?suspendedMessage:"返回页面,正在重新连接网站账号…";recentQueue.forEach(function(entry){entry.readyAt=now()+4e3});emit();var token=epoch;return refresh(false).then(function(value){if(token===epoch&&!stopped&&status.phase==="ready")flushRecent();return value})}function bindLifecycle(){if(listenersBound)return;listenersBound=true;if(doc&&doc.addEventListener)doc.addEventListener("visibilitychange",function(){if(!isVisible()){clearTimeout(timer);timer=null;clearTimeout(recentTimer);recentTimer=null}else if(!stopped&&status.phase!=="paused"){refresh(false);flushRecent()}});if(win.addEventListener){win.addEventListener("pagehide",function(){halt(false)});win.addEventListener("pageshow",resumeFromCache)}}function start(){if(running||stopped)return inFlight||Promise.resolve(publicStatus());running=true;bindLifecycle();return refresh(false)}return{start:start,stop:stop,refresh:refresh,installEntry:installEntry,open:open,guard:guard,favoriteChanged:favoriteChanged,progressChanged:progressChanged,syncLocalFavorites:syncLocalFavorites,decorateShelf:decorateShelf,getRemoteResume:getRemoteResume,flushRecent:flushRecent,getStatus:publicStatus,isOpen:function(){var p=doc&&doc.querySelector("[data-bm-account-panel]");return!!p&&!p.hidden}}});(function(root,factory){if(typeof module==="object"&&module.exports)module.exports.createAdapter=factory;else root.createBoluomaoSiteAccountAdapter=factory})(typeof globalThis!=="undefined"?globalThis:this,function createBoluomaoSiteAccountAdapter(options){"use strict";options=options||{};var win=options.window||window;var Parser=options.DOMParser||win.DOMParser;function id(value){if(typeof value==="number"&&Number.isSafeInteger(value)&&value>0)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<matches.length;i++)hide(matches[i])}function refresh(){ensureStyle();cleanTree(document.documentElement)}var api={document:document,refresh:refresh};host[slot]=api;var observer=new MutationObserver(function(records){ensureStyle();for(var i=0;i<records.length;i++){var record=records[i];if(record.type==="attributes")hide(record.target);else for(var j=0;j<record.addedNodes.length;j++)cleanTree(record.addedNodes[j])}});observer.observe(document,{childList:true,subtree:true,attributes:true,attributeFilter:["class","id","src","style"]});window.addEventListener("pageshow",refresh);document.addEventListener("visibilitychange",function(){if(!document.hidden)refresh()});refresh()})();
// END EARLY AD GUARD 1.9.18
// BEGIN PRIVATE ONLINE VERIFICATION 1.9.18
// BEGIN PRIVATE ONLINE CAPTCHA ASSISTANT 1.9.18
var captchaHost=typeof unsafeWindow!=="undefined"&&unsafeWindow?unsafeWindow:window;var captchaSlotKey="__bmPrivateCaptchaAssistantV1";var captchaSlot=captchaHost[captchaSlotKey];var captchaAssistant=captchaSlot&&captchaSlot.version==="1.9.18"&&captchaSlot.document===document&&captchaSlot.api&&typeof captchaSlot.api.getDiagnostics==="function"?captchaSlot.api:(function(){"use strict";var allowedHosts=["boluomao1.com","www.boluomao1.com","rrssk.com","www.rrssk.com"];var captchaPath="/WAF/VERIFY/CAPTCHA";var configKey="bm-private-baidu-ocr-config-v1";var defaultPrivateBaiduApiKey="";var defaultPrivateBaiduSecretKey="";var budgetKey="bm-private-baidu-captcha-guard-v1";var maxAttempts=2,maxRounds=3,retryDelay=0;var retryTimer=null,retryAction="",retryDueAt=0,budgetChecked=false,budgetSnapshot=null;var allowed=location.protocol==="https:"&&!location.port&&allowedHosts.indexOf(location.hostname)>=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;index<words.length;index++)result+=words[index].toString(16).padStart(8,"0")}catch(_){for(var part=0;part<4;part++)result+=Math.floor(Math.random()*4294967296).toString(16).padStart(8,"0")}return result}function safeLogEntry(item,requireId){var now=Date.now();if(!item||typeof item!=="object"||Array.isArray(item)||!Number.isFinite(item.time)||item.time>now||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.time<cutoff||entry.time<clearedAt||entry.time===clearedAt&&entry.clearRevision!==clearRevision)return;var previous=seen[entry.id];if(!previous||JSON.stringify(entry)<JSON.stringify(previous))seen[entry.id]=entry});return Object.keys(seen).map(function(id){return seen[id]}).sort(function(left,right){return left.time-right.time||(left.id<right.id?-1:left.id>right.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.length&&waited>=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;index<list.length;index++){var adapter=list[index],parsed=reads[index];if(!parsed)continue;if(!selected||parsed.updatedAt>selected.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<value.length;index++)result=Math.imul(result^value.charCodeAt(index),16777619);return(result>>>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;index<ordered.length;index++){var box=ordered[index].location;if(Math.abs(box.top+box.height/2-center)>Math.min(box.height,ordered[0].location.height)/2)return"";if(index&&box.left<ordered[index-1].location.left+ordered[index-1].location.width)return""}return sixDigits(ordered.map(function(row){return row.words.trim()}).join(""))}async function ocrRequest(config,pixels,token,method,imageBefore,reserved){var ctx=newBaiduContext(method,function(){return!stopped&&sequence===token&&!submitted},false);logEvent("transport_select","百度识别首先使用 "+(ctx.kind==="gm"?"GM":"fetch")+";鉴权与识别共用总等待期限。");var accessToken=await baiduAccessToken(config,ctx,false);var imageBody=new URLSearchParams({image:pixels.replace(/^data:image\/[A-Za-z0-9.+-]+;base64,/,""),detect_direction:"false"}).toString();var imageSends=0;{if(!ctx.alive())throw new Error("cancelled");if(!sameImage(imageBefore))throw new Error("stale-image");var result=await baiduJsonRequest(ctx,{label:"百度图片识别",url:"https://aip.baidubce.com/rest/2.0/ocr/v1/"+(config.ocrType==="accurate_basic"?"accurate_basic":"numbers")+"?access_token="+encodeURIComponent(accessToken),headers:{"Content-Type":"application/x-www-form-urlencoded","Accept":"application/json"},body:imageBody,canFallback:function(){var budget=readBudget();return!budget.paused&&budget.phase==="attempting"&&budget.cycle===reserved.cycle&&budget.total===reserved.total&&budget.attempts<maxAttempts&&budget.total<maxAttempts*maxRounds},beforeSend:function(){if(!ctx.alive())throw new Error("cancelled");if(!sameImage(imageBefore))throw new Error("stale-image");var budget=readBudget();if(budget.paused||budget.phase!=="attempting"||budget.cycle!==reserved.cycle||budget.total!==reserved.total)throw new Error("BUDGET_PAUSED");if(imageSends){if(budget.attempts>=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;index<list.length;index++){if(id===null||list[index].identifier===id)return list[index]}return null}function guard(event){var target=event.target;var inside=target&&target.closest&&target.closest("[data-bm-captcha-panel], [data-bm-captcha-config-panel], [data-bm-captcha-config]");if(!inside||inside.__bmCaptchaControlOwner!==controlOwner){if(event.type==="touchstart"||event.type==="touchend"||event.type==="touchcancel")controlTouch.control=null;return false}var control=target.closest("button"),now=Date.now(),point;if(control&&control.__bmCaptchaControlOwner!==controlOwner)return false;if(event.type==="touchstart"){controlTouch.control=null;if(control&&!control.disabled&&event.touches&&event.touches.length===1){point=event.touches[0];if(Number.isFinite(point.clientX)&&Number.isFinite(point.clientY)){controlTouch.control=control;controlTouch.id=point.identifier;controlTouch.x=point.clientX;controlTouch.y=point.clientY;controlTouch.at=now;controlTouch.moved=false}}}else if(event.type==="touchmove"&&controlTouch.control){point=touchPoint(event.touches,controlTouch.id);if(!point||event.touches.length!==1||Math.abs(point.clientX-controlTouch.x)>12||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()<uiHoldUntil)return;
if(!readingNow())return;
if(!toolbarCollapsed&&Date.now()>readingIntentUntil)return;
if(Date.now()<wakeUntil&&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/2<b.left+b.width/2?'left':'right';
docked=true;paint();if(!position)position={x:edge==='left'?0:1,y:1};remember();clearTimeout(idle);idle=null;
}
function activate(){
if(!button||!node.isConnected)return;
readingIntentUntil=0;uiHoldUntil=Date.now()+350;
var wasDocked=docked;docked=false;wakeUntil=0;paint();if(wasDocked)remember();
setToolbarCollapsed(false);
}
function resetDrag(){
var previous=drag;drag=null;clearTimeout(dragTimer);dragTimer=null;
if(previous&&previous.id!==undefined&&button&&button.releasePointerCapture)try{button.releasePointerCapture(previous.id);}catch(_){}
}
function mount(handle){
resetDrag();cancelPaint();lastSkin='';clearTimeout(idle);idle=null;button=handle;
var reader=handle.closest('.via-reader-body');if(reader)reader.addEventListener('scroll',readingScroll,{passive:true});
node=document.createElement('div');node.setAttribute('data-bm-reader-widget','');
node.style.cssText='position:fixed;z-index:6;touch-action:none;user-select:none;-webkit-user-select:none;';
handle.parentNode.insertBefore(node,handle);node.appendChild(handle);
handle.style.position='static';handle.style.right=handle.style.bottom='auto';handle.style.touchAction='none';
var icon=handle.querySelector('span[aria-hidden]');if(icon)icon.setAttribute('data-bm-widget-icon','');paint();
}
function toolbarChanged(){
if(!node)return;
uiHoldUntil=Date.now()+350;readingIntentUntil=0;
node.hidden=!toolbarCollapsed;node.style.display=toolbarCollapsed?'block':'none';paint();armIdle();
}
function point(e,touchId){
var touches=/end|cancel/.test(e.type)?e.changedTouches:e.touches;
if(touches){for(var i=0;i<touches.length;i++)if(touchId===undefined||touches[i].identifier===touchId)return{x:touches[i].clientX,y:touches[i].clientY,id:touches[i].identifier};return null;}
return{x:e.clientX,y:e.clientY};
}
function begin(e){
if(!button||(e.button!==undefined&&e.button!==0))return;
if(e.isPrimary===false){resetDrag();clickFence=Date.now()+750;armIdle();return;}
if(e.type==='mousedown'&&Date.now()<touchFence)return;
if(e.touches&&e.touches.length!==1){resetDrag();clickFence=Date.now()+750;armIdle();return;}
if(drag){if(drag.type!==e.type)return;resetDrag();}
var p=point(e),r=node.getBoundingClientRect();if(!p||!Number.isFinite(p.x)||!Number.isFinite(p.y))return;
clearTimeout(idle);idle=null;clickFence=0;
if(e.type==='touchstart'||e.pointerType==='touch')touchFence=Date.now()+800;
drag={id:e.pointerId,touchId:p.id,type:e.type,x:p.x,y:p.y,left:r.left,top:r.top,moved:false,at:Date.now()};
dragTimer=setTimeout(function(){resetDrag();armIdle();},15000);
if(e.pointerId!==undefined&&button.setPointerCapture)try{button.setPointerCapture(e.pointerId);}catch(_){}
}
function matching(e){return drag&&(drag.id===undefined||drag.id===e.pointerId)&&(drag.type==='pointerdown'?/^pointer/.test(e.type):drag.type==='touchstart'?/^touch/.test(e.type):/^mouse/.test(e.type));}
function move(e){
if(!matching(e))return;
var p=point(e,drag.touchId);if(e.touches&&e.touches.length!==1){resetDrag();clickFence=Date.now()+750;armIdle();return;}if(!p)return;
var dx=p.x-drag.x,dy=p.y-drag.y;if(!Number.isFinite(dx)||!Number.isFinite(dy))return;
if(dx*dx+dy*dy>144)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<d.texts.length;i++){var t=d.texts[i],before=t;for(var j=0;j<r.length;j++){var x=r[j];if(!x.enabled)continue;if(x.type==='paragraph'){if(t.trim()===x.pattern.trim())t='';}else if(x.type==='literal'){t=t.split(x.pattern).join('');}else{var q=new RegExp(x.pattern,'g');if(q.test(''))throw Error('正则不能匹配空字符串');q.lastIndex=0;t=t.replace(q,function(m){if(!m.length)throw Error('正则不能匹配空字符串');return ''});}}if(t!==before)hits++;o.push(t)}postMessage({texts:o,hits:hits})}catch(x){postMessage({error:String(x.message||'规则无效').slice(0,120)})}}";function load(){try{var value=JSON.parse(localStorage.getItem(key)||"[]");if(Array.isArray(value))rules=value.filter(function(r){return r&&["literal","paragraph","regex"].indexOf(r.type)>=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<buttons.length;i++){var selected=buttons[i].getAttribute("data-bm-theme-choice")===theme.id;buttons[i].setAttribute("aria-pressed",selected?"true":"false");buttons[i].style.setProperty("border-color",selected?theme.id==="night"?"#eee":"#48423a":"#aaa","important");buttons[i].style.setProperty("box-shadow",selected?"0 0 0 2px "+theme.bar:"none","important")}var fontButtons=state.toolbar.querySelectorAll("[data-bm-font-step], [data-bm-toc-open], [data-bm-search-open], [data-bm-resume], [data-bm-reader-exit], [data-bm-shelf-add], [data-bm-shelf-open], [data-bm-tools-open]");for(var j=0;j<fontButtons.length;j++){fontButtons[j].style.setProperty("color",theme.text,"important");fontButtons[j].style.setProperty("border-color",theme.text,"important");fontButtons[j].style.setProperty("background-color",theme.id==="night"?"rgba(255,255,255,.1)":"rgba(255,255,255,.22)","important")}var headerShelf=state.toolbar.querySelector("[data-bm-toolbar-heading] [data-bm-shelf-open]");if(headerShelf)headerShelf.style.setProperty("background-color","transparent","important");var fontValue=state.toolbar.querySelector("[data-bm-font-value]");if(fontValue)fontValue.style.setProperty("color",theme.text,"important");var collapse=state.toolbar.querySelector("[data-bm-toolbar-collapse]");if(collapse)collapse.style.setProperty("color",theme.text,"important");if(state.toolbarHandle){state.toolbarHandle.style.setProperty("background-color",readerWidget.isDocked()?"transparent":theme.bar,"important");state.toolbarHandle.style.setProperty("color",theme.text,"important")}[state.tocPanel,state.progressPanel].forEach(function(panel){if(panel){panel.style.setProperty("background-color",theme.bg,"important");panel.style.setProperty("background-image",themeBackground(theme),"important");panel.style.setProperty("color",theme.text,"important");var panelHeader=panel.querySelector("[data-bm-toc-header], [data-bm-progress-header]");if(panelHeader)panelHeader.style.setProperty("background-color",theme.bar,"important");var rangeBar=panel.querySelector("[data-bm-toc-pages]");if(rangeBar)rangeBar.style.setProperty("background-color",theme.bar,"important");var panelButtons=panel.querySelectorAll("button");for(var k=0;k<panelButtons.length;k++){panelButtons[k].style.setProperty("color",theme.text,"important")}}});applyShelfTheme();updateFavoriteButtons()}
function installReaderStyle(){if(document.getElementById("bm-reader-theme-style"))return;var style=document.createElement("style");style.id="bm-reader-theme-style";style.textContent="@media(max-width:400px){.via-reader-body [data-bm-toolbar] [data-bm-theme-palette]{gap:1px!important}"+".via-reader-body [data-bm-toolbar] [data-bm-theme-choice]{flex-basis:20px!important;width:20px!important}"+".via-reader-body [data-bm-toolbar] [data-bm-font-value]{flex-basis:14px!important;width:14px!important}}"+"@media(max-width:340px){.via-reader-body [data-bm-toolbar]{gap:1px!important}"+".via-reader-body [data-bm-toolbar] [data-bm-search-open],.via-reader-body [data-bm-toolbar] [data-bm-toc-open],.via-reader-body [data-bm-toolbar] [data-bm-resume],.via-reader-body [data-bm-toolbar] [data-bm-reader-exit]{flex-basis:28px!important;width:28px!important}"+".via-reader-body [data-bm-toolbar] [data-bm-shelf-add-icon]{flex-basis:22px!important;width:22px!important}"+".via-reader-body [data-bm-toolbar] [data-bm-font-step]{flex-basis:24px!important;width:24px!important;font-size:15px!important}"+".via-reader-body [data-bm-toolbar] [data-bm-toolbar-collapse]{flex-basis:18px!important;width:18px!important}"+".via-reader-body [data-bm-toolbar] [data-bm-theme-choice]{flex-basis:18px!important;width:18px!important}"+".via-reader-body [data-bm-toolbar] [data-bm-font-value]{flex-basis:12px!important;width:12px!important}}"+".via-reader-body[data-bm-auto-reader]{position:fixed;top:0;right:0;bottom:0;left:0;"+"overflow:auto;z-index:2147483647!important;overscroll-behavior:contain}"+".via-reader-body[data-bm-auto-reader] .via-reader-header{margin:0 16px 24px;text-align:left}"+".via-reader-body[data-bm-auto-reader] .via-reader-header h1{padding:24px 0 0;margin:0;font-size:1.4em;line-height:1.6}"+".via-reader-body[data-bm-auto-reader] .via-reader-content{margin:10px 16px 0;overflow-wrap:break-word}"+".via-reader-body[data-bm-reader-theme] .via-reader-content{"+"text-align:left!important;color:inherit!important}"+".via-reader-body[data-bm-reader-theme] .via-reader-header,"+".via-reader-body[data-bm-reader-theme] .via-reader-header h1,"+".via-reader-body[data-bm-reader-theme] .via-reader-content h1,"+".via-reader-body[data-bm-reader-theme] .via-reader-content h2,"+".via-reader-body[data-bm-reader-theme] .via-reader-content p{"+"color:inherit!important}"+'.via-reader-body[data-bm-reader-theme] .via-reader-content p:not([style*="text-align"]){'+"font-size:inherit!important;line-height:1.7!important;padding:0!important;"+"margin:0 0 .45em!important;text-align:left!important;text-indent:2em!important;"+"overflow-wrap:break-word}"+".via-reader-body[data-bm-reader-theme] .via-reader-content h1,"+".via-reader-body[data-bm-reader-theme] .via-reader-content h2{"+"line-height:1.5!important;text-indent:0!important;margin:1em 0 .6em!important}"+'.via-reader-body[data-bm-reader-theme="night"] .via-reader-content a,'+'.via-reader-body[data-bm-reader-theme="night"] .via-reader-header a{'+"color:#9ec8ff!important}"+".via-reader-body [data-bm-toolbar-expand]:active,"+".via-reader-body [data-bm-toolbar-expand]:focus-visible{"+"opacity:.72!important}";(document.head||document.documentElement).appendChild(style)}
function installProgressUiStyle() {
if (document.getElementById('bm-progress-ui-v195-style')) return;
var style = document.createElement('style'); style.id = 'bm-progress-ui-v195-style';
style.textContent = '[data-bm-progress-panel] [data-bm-progress-sheet]{max-width:560px;margin:0 auto;box-sizing:border-box;}' +
'[data-bm-progress-panel] [data-bm-progress-tool-grid]{display:grid!important;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px!important;}' +
'[data-bm-progress-panel] [data-bm-progress-tool-grid] button{min-width:0!important;min-height:44px!important;width:100%;box-sizing:border-box;' +
'padding:10px 8px!important;border:1px solid rgba(110,101,90,.25);border-radius:10px!important;background:transparent!important;' +
'font-size:12px!important;line-height:1.3!important;white-space:normal!important;touch-action:manipulation;}' +
'[data-bm-progress-panel] [data-bm-progress-tools-summary]{list-style:none;cursor:pointer;touch-action:manipulation;}' +
'[data-bm-progress-panel] [data-bm-progress-tools-summary]::-webkit-details-marker{display:none;}' +
'[data-bm-progress-panel] [data-bm-progress-tools][open] [data-bm-progress-tools-chevron]{transform:rotate(180deg);}' +
'[data-bm-progress-panel] [data-bm-progress-card]:active{opacity:.7;}' +
'[data-bm-progress-panel] button:focus-visible,[data-bm-progress-panel] summary:focus-visible{outline:2px solid currentColor;outline-offset:3px;}' +
'@media(max-width:340px){[data-bm-progress-panel] [data-bm-progress-main]{padding-right:12px!important;padding-left:12px!important;}' +
'[data-bm-progress-panel] [data-bm-progress-card]{padding:13px!important;}' +
'[data-bm-progress-panel] [data-bm-progress-tool-grid]{gap:6px!important;}}';
(document.head || document.documentElement).appendChild(style);
}
function progressUiGuard(event) {
var target = event.target;
if (!target || !target.closest || !target.closest('[data-bm-progress-panel]')) return false;
event.stopImmediatePropagation(); return true;
}
function installReaderChromeStyle(){if(document.getElementById("bm-reader-chrome-v195-style"))return;var style=document.createElement("style");style.id="bm-reader-chrome-v195-style";style.textContent=".via-reader-body [data-bm-toolbar-dock]{max-width:560px;margin:8px auto 0;border:1px solid rgba(100,90,75,.2);border-radius:16px;box-shadow:0 5px 20px rgba(0,0,0,.12);overflow:hidden;}"+".via-reader-body [data-bm-toolbar]{display:flex!important;flex-direction:column!important;gap:10px!important;padding:8px 10px 12px!important;}"+".via-reader-body [data-bm-toolbar-actions]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;width:100%;}"+".via-reader-body [data-bm-toolbar-actions] button{width:100%!important;min-width:0!important;height:42px!important;padding:0 4px!important;border:0!important;border-radius:10px!important;font:600 13px/1.3 sans-serif!important;touch-action:manipulation;}"+".via-reader-body [data-bm-toolbar-heading]{display:grid;grid-template-columns:44px minmax(0,1fr) minmax(0,1fr) 44px;align-items:center;width:100%;min-height:44px;}"+".via-reader-body [data-bm-toolbar-heading] [data-bm-settings-label],.via-reader-body [data-bm-toolbar-heading] [data-bm-shelf-open]{box-sizing:border-box;display:flex;align-items:center;justify-content:center;width:100%;min-width:0;height:44px;padding:0!important;border:0!important;border-radius:9px!important;color:inherit;background:transparent!important;font:600 14px/1.4 sans-serif!important;opacity:.8;text-align:center;}"+".via-reader-body [data-bm-toolbar-heading] [data-bm-toolbar-collapse]{box-sizing:border-box;width:44px;height:44px;min-width:44px;padding:0!important;border:0!important;border-radius:9px!important;color:inherit;background:transparent;font:24px/1 sans-serif;touch-action:manipulation;}"+".via-reader-body [data-bm-toolbar-progress-open]{box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;min-height:44px;padding:6px 10px;border:0!important;border-radius:10px;color:inherit;font:13px/1.4 sans-serif;touch-action:manipulation;}"+".via-reader-body [data-bm-toolbar-progress-open] [data-bm-book-progress-toolbar]{flex:0 0 auto;opacity:1!important;padding:0!important;font:600 18px/1.4 sans-serif!important;}"+".via-reader-body [data-bm-toolbar-progress-hint]{font-size:12px;opacity:.65;white-space:nowrap;}"+".via-reader-body [data-bm-toolbar-appearance]{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;}"+".via-reader-body [data-bm-toolbar] [data-bm-theme-palette]{flex:0 1 auto!important;min-width:0!important;gap:6px!important;overflow:visible!important;}"+".via-reader-body [data-bm-toolbar] [data-bm-theme-choice]{flex:0 0 32px!important;width:32px!important;height:32px!important;border-radius:9px!important;}"+".via-reader-body [data-bm-toolbar] [data-bm-font-controls]{gap:5px!important;margin-left:0!important;}"+".via-reader-body [data-bm-toolbar] [data-bm-font-step]{flex:0 0 36px!important;width:36px!important;height:36px!important;border:0!important;border-radius:9px!important;font-size:17px!important;}"+".via-reader-body [data-bm-toolbar] [data-bm-font-value]{flex:0 0 22px!important;width:22px!important;font-size:13px!important;}"+".via-reader-body [data-bm-toolbar-expand]{display:flex;align-items:center;justify-content:center;gap:6px;box-sizing:border-box;}"+".via-reader-body [data-bm-toolbar-expand] [data-bm-book-progress]{margin:0;padding:0!important;font:600 11px/1 sans-serif!important;opacity:1!important;white-space:nowrap;}"+"@media(max-width:340px){.via-reader-body [data-bm-toolbar] [data-bm-theme-palette]{gap:4px!important;}"+".via-reader-body [data-bm-toolbar] [data-bm-theme-choice]{flex-basis:29px!important;width:29px!important;height:30px!important;}"+".via-reader-body [data-bm-toolbar-appearance]{gap:8px!important;}}";(document.head||document.documentElement).appendChild(style)}
function appendRecentProgressRows() {
var rows=state.progressRows;if(!rows||!state.progressList)return;
if(state.progressMore){state.progressMore.remove();state.progressMore=null;}
var end=Math.min(rows.items.length,rows.shown+20);
while(rows.shown<end)addProgressChoice('阅读记录',rows.items[rows.shown++],'recent');
if(rows.shown<rows.items.length){var more=document.createElement('button');more.type='button';more.setAttribute('data-bm-progress-more','');more.textContent='再显示20条 · 剩余'+(rows.items.length-rows.shown)+'条';more.style.cssText='width:100%;min-height:44px;padding:10px;border:1px solid currentColor;border-radius:10px;background:transparent;color:inherit;font:14px/1.5 sans-serif;';state.progressList.appendChild(more);state.progressMore=more;}
}
function installThemeControls(reader){var frame=reader.firstElementChild;if(!frame||!frame.querySelector(".via-reader-header"))return;installReaderChromeStyle();reader.style.setProperty("position","fixed","important");["top","right","bottom","left"].forEach(function(side){reader.style.setProperty(side,"0","important")});reader.style.setProperty("width","100%","important");reader.setAttribute("data-bm-reader-version","1.9.19");reader.style.setProperty("overflow-anchor","none","important");var paper=document.createElement("div");paper.setAttribute("data-bm-theme-paper","");paper.style.cssText="position:fixed;inset:0;z-index:0;pointer-events:none;background-position:center;background-size:cover;background-repeat:no-repeat;";reader.insertBefore(paper,frame);frame.style.setProperty("position","relative");frame.style.setProperty("z-index","1");frame.style.setProperty("width","100%","important");var dock=document.createElement("div");dock.setAttribute("data-bm-toolbar-dock","");dock.style.cssText="position:sticky;top:max(8px,env(safe-area-inset-top));z-index:4;display:flex;flex-direction:column;box-sizing:border-box;width:calc(100% - 24px);";var bar=document.createElement("div");bar.setAttribute("data-bm-theme-bar","");bar.setAttribute("data-bm-toolbar","");bar.setAttribute("role","group");bar.setAttribute("aria-label","阅读设置");dock.appendChild(bar);var heading=document.createElement("div");heading.setAttribute("data-bm-toolbar-heading","");bar.appendChild(heading);var spacer=document.createElement("span");spacer.setAttribute("aria-hidden","true");heading.appendChild(spacer);var title=document.createElement("strong");title.setAttribute("data-bm-settings-label","");title.textContent="设置";heading.appendChild(title);var shelf=document.createElement("button");shelf.type="button";shelf.setAttribute("data-bm-shelf-open","");shelf.textContent="书架";shelf.setAttribute("aria-label","打开我的书架");heading.appendChild(shelf);var collapse=document.createElement("button");collapse.type="button";collapse.setAttribute("data-bm-toolbar-collapse","");collapse.setAttribute("aria-label","收起阅读菜单并贴边");collapse.textContent="×";heading.appendChild(collapse);var progressRow=document.createElement("button");progressRow.type="button";progressRow.setAttribute("data-bm-toolbar-progress-open","");progressRow.setAttribute("data-bm-resume","");progressRow.setAttribute("aria-label","整本阅读进度,查看阅读记录");bar.appendChild(progressRow);var progressLabel=document.createElement("span");progressLabel.textContent="整本进度";progressRow.appendChild(progressLabel);var menuPercent=bookProgress.mountToolbar(progressRow);menuPercent.setAttribute("data-bm-progress-bare","");var progressHint=document.createElement("span");progressHint.setAttribute("data-bm-toolbar-progress-hint","");progressHint.textContent="阅读记录 ›";progressRow.appendChild(progressHint);var actions=document.createElement("div");actions.setAttribute("data-bm-toolbar-actions","");bar.appendChild(actions);[["search-open","搜索","搜索小说"],["toc-open","目录","打开章节目录"],["tools-open","工具","打开阅读工具"],["reader-exit","退出","退出阅读模式"]].forEach(function(item){var button=document.createElement("button");button.type="button";button.setAttribute("data-bm-"+item[0],"");button.setAttribute("aria-label",item[2]);button.title=item[2];button.textContent=item[1];if(item[0]==="shelf-add")button.setAttribute("data-bm-shelf-add-icon","");actions.appendChild(button)});var appearance=document.createElement("div");appearance.setAttribute("data-bm-toolbar-appearance","");bar.appendChild(appearance);var palette=document.createElement("div");palette.setAttribute("data-bm-theme-palette","");palette.style.cssText="display:flex;align-items:center;";appearance.appendChild(palette);themes.forEach(function(theme){var button=document.createElement("button");button.type="button";button.setAttribute("data-bm-theme-choice",theme.id);button.setAttribute("aria-label",theme.label);button.title=theme.label;button.style.cssText="padding:0;border:2px solid #aaa;background-position:center;background-size:cover;background-repeat:no-repeat;cursor:pointer;";button.style.setProperty("background-color",theme.bg,"important");button.style.setProperty("background-image",themeBackground(theme),"important");palette.appendChild(button)});var fontControls=document.createElement("div");fontControls.setAttribute("data-bm-font-controls","");fontControls.style.cssText="display:flex;align-items:center;flex:0 0 auto;";appearance.appendChild(fontControls);[{step:-1,text:"A−",label:"缩小字号"},{step:1,text:"A+",label:"放大字号"}].forEach(function(item,index){if(index){var value=document.createElement("span");value.setAttribute("data-bm-font-value","");value.setAttribute("role","status");value.setAttribute("aria-live","polite");value.style.textAlign="center";fontControls.appendChild(value)}var button=document.createElement("button");button.type="button";button.setAttribute("data-bm-font-step",String(item.step));button.setAttribute("aria-label",item.label);button.title=item.label;button.textContent=item.text;fontControls.appendChild(button)});frame.insertBefore(dock,frame.firstChild);var handle=document.createElement("button");handle.type="button";handle.setAttribute("data-bm-toolbar-expand","");handle.setAttribute("aria-label","展开阅读工具栏");handle.title="阅读设置与整本进度";handle.style.cssText="position:fixed;right:10px;bottom:max(12px,env(safe-area-inset-bottom));z-index:5;opacity:.68;height:40px;min-width:80px;padding:0 10px;border:1px solid rgba(95,85,70,.4);border-radius:999px;box-shadow:0 2px 8px rgba(0,0,0,.12);font:16px/1 sans-serif;color:inherit;cursor:pointer;touch-action:manipulation;";var icon=document.createElement("span");icon.setAttribute("aria-hidden","true");icon.textContent="☰";handle.appendChild(icon);reader.appendChild(handle);state.paper=paper;state.toolbar=dock;state.toolbarHandle=handle;var percent=bookProgress.mount(handle);percent.setAttribute("data-bm-progress-bare","");readerWidget.mount(handle);installCataloguePanel(reader);installProgressPanel(reader);installShelfPanel(reader);applyTheme(currentTheme);applyFontSize(fontSize);updateResumeButton();toolbarCollapsed=true;setToolbarCollapsed(true);bookProgress.update()}
function installCataloguePanel(reader){var panel=document.createElement("div");panel.setAttribute("data-bm-toc-panel","");panel.setAttribute("role","dialog");panel.setAttribute("aria-label","章节目录");panel.hidden=true;panel.style.cssText="position:fixed;top:0;right:0;bottom:0;left:0;z-index:6;"+"box-sizing:border-box;width:100%;overflow-y:auto;overscroll-behavior:contain;"+"background-position:center;background-size:cover;background-repeat:no-repeat;";panel.style.setProperty("display","none","important");var header=document.createElement("div");header.setAttribute("data-bm-toc-header","");header.style.cssText="position:sticky;top:0;z-index:1;display:flex;"+"align-items:center;justify-content:space-between;box-sizing:border-box;"+"min-height:52px;padding:8px 14px;border-bottom:1px solid rgba(80,70,60,.2);";var title=document.createElement("strong");title.textContent="章节目录";title.style.cssText="font-size:18px;line-height:1.4;";var close=document.createElement("button");close.type="button";close.setAttribute("data-bm-toc-close","");close.setAttribute("aria-label","返回阅读");close.textContent="返回阅读 ×";close.style.cssText="padding:6px 8px;border:1px solid currentColor;"+"border-radius:5px;background:none;font-size:14px;";header.appendChild(title);header.appendChild(close);panel.appendChild(header);var status=document.createElement("div");status.setAttribute("data-bm-toc-status","");status.setAttribute("role","status");status.style.cssText="padding:12px 16px;font-size:14px;line-height:1.5;";status.hidden=true;panel.appendChild(status);var pages=document.createElement("div");pages.setAttribute("data-bm-toc-pages","");pages.setAttribute("aria-label","目录分页");pages.style.cssText="position:sticky;top:52px;z-index:1;display:flex;gap:5px;overflow-x:auto;padding:10px 12px;"+"box-sizing:border-box;white-space:nowrap;";panel.appendChild(pages);var list=document.createElement("div");list.setAttribute("data-bm-toc-list","");list.style.cssText="padding:0 8px 24px;box-sizing:border-box;";panel.appendChild(list);reader.appendChild(panel);state.tocPanel=panel;state.tocStatus=status;state.tocPages=pages;state.tocList=list}
function installProgressPanel(reader){installProgressUiStyle();var panel=document.createElement("div");panel.setAttribute("data-bm-progress-panel","");panel.setAttribute("role","dialog");panel.setAttribute("aria-label","阅读进度");panel.setAttribute("aria-modal","true");panel.hidden=true;panel.style.cssText="position:fixed;inset:0;z-index:6;box-sizing:border-box;width:100%;overflow-y:auto;"+"overscroll-behavior:contain;background-position:center;background-size:cover;background-repeat:no-repeat;font-size:16px;";panel.style.setProperty("display","none","important");var sheet=document.createElement("div");sheet.setAttribute("data-bm-progress-sheet","");panel.appendChild(sheet);var header=document.createElement("div");header.setAttribute("data-bm-progress-header","");header.style.cssText="position:sticky;top:0;z-index:2;display:flex;align-items:center;justify-content:space-between;gap:12px;"+"box-sizing:border-box;min-height:68px;padding:10px 16px;border-bottom:1px solid rgba(110,101,90,.14);";var titles=document.createElement("div");titles.style.cssText="min-width:0;flex:1;";var title=document.createElement("strong");title.setAttribute("data-bm-panel-title","");title.textContent="阅读进度";title.style.cssText="display:block;font-size:20px;line-height:1.4;";var book=document.createElement("div");book.setAttribute("data-bm-progress-book-name","");book.textContent=currentBookName()||"这本书";book.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:1.5;opacity:.6;";titles.appendChild(title);titles.appendChild(book);header.appendChild(titles);var close=document.createElement("button");close.type="button";close.setAttribute("data-bm-progress-close","");close.setAttribute("aria-label","返回阅读");close.textContent="×";close.style.cssText="flex:0 0 44px;width:44px;height:44px;box-sizing:border-box;border:0;border-radius:50%;background:transparent;"+"font:28px/1 sans-serif;color:inherit;cursor:pointer;touch-action:manipulation;";header.appendChild(close);sheet.appendChild(header);var main=document.createElement("div");main.setAttribute("data-bm-progress-main","");main.style.cssText="padding:20px 16px max(24px,env(safe-area-inset-bottom));box-sizing:border-box;";sheet.appendChild(main);var summary=document.createElement("section");summary.setAttribute("data-bm-progress-summary","");summary.setAttribute("aria-label","当前整本阅读进度");summary.style.cssText="padding:0 2px 20px;";main.appendChild(summary);var summaryTop=document.createElement("div");summaryTop.style.cssText="display:flex;align-items:baseline;justify-content:space-between;gap:12px;";var summaryLabel=document.createElement("span");summaryLabel.textContent="整本书";summaryLabel.style.cssText="font-size:13px;opacity:.65;";summaryTop.appendChild(summaryLabel);summary.appendChild(summaryTop);var percentage=bookProgress.mountPanel(summaryTop);percentage.setAttribute("data-bm-progress-bare","");percentage.style.cssText="flex:0 0 auto;padding:0;font-size:30px;line-height:1.4;font-weight:700;letter-spacing:-.5px;";var meter=document.createElement("div");meter.setAttribute("data-bm-progress-meter","");meter.setAttribute("role","progressbar");meter.setAttribute("aria-label","整本书阅读进度");meter.setAttribute("aria-valuemin","0");meter.setAttribute("aria-valuemax","100");meter.style.cssText="height:4px;margin:12px 0 10px;border-radius:4px;overflow:hidden;background:rgba(110,101,90,.17);";var fill=document.createElement("div");fill.setAttribute("data-bm-progress-meter-fill","");fill.style.cssText="height:100%;width:0;border-radius:inherit;background:currentColor;opacity:.7;";meter.appendChild(fill);summary.appendChild(meter);var current=document.createElement("div");current.setAttribute("data-bm-progress-current-title","");current.style.cssText="font-size:13px;line-height:1.6;opacity:.7;overflow-wrap:anywhere;";summary.appendChild(current);var status=document.createElement("div");status.setAttribute("data-bm-progress-status","");status.setAttribute("role","status");status.style.cssText="padding:10px 2px 14px;font-size:14px;line-height:1.5;";status.hidden=true;main.appendChild(status);var list=document.createElement("div");list.setAttribute("data-bm-progress-list","");list.style.cssText="box-sizing:border-box;";main.appendChild(list);var tools=document.createElement("section");tools.setAttribute("data-bm-progress-tools","");tools.style.cssText="margin:0 0 18px;padding:0 0 16px;border-bottom:1px solid rgba(110,101,90,.17);";var grid=document.createElement("div");grid.setAttribute("data-bm-progress-tool-grid","");tools.appendChild(grid);[["add","☆ 收藏本书"]].forEach(function(item){var button=document.createElement("button");button.type="button";button.textContent=item[1];button.setAttribute("data-bm-shelf-"+item[0],"");grid.appendChild(button)});scriptDownloader.installEntry(grid);captchaAutomation.installEntry(grid);purifier.installEntry(grid);if(siteAccount)siteAccount.installEntry(grid);var toolsContent=document.createElement("div");toolsContent.setAttribute("data-bm-progress-tools-content","");tools.appendChild(toolsContent);installBackupControls(toolsContent);var backup=toolsContent.querySelector("[data-bm-backup-area]");if(backup){backup.style.padding="6px 0 0";var oldRow=backup.firstElementChild;var log=oldRow.querySelector("[data-bm-repeat-log-toggle]");if(log)grid.insertBefore(log,grid.firstChild);while(oldRow.firstChild)grid.appendChild(oldRow.firstChild);oldRow.remove();state.backupStatus.textContent=""}var toc=document.createElement("button");toc.type="button";toc.textContent="章节目录";toc.setAttribute("data-bm-toc-open","");grid.appendChild(toc);var exit=document.createElement("button");exit.type="button";exit.textContent="退出阅读";exit.setAttribute("data-bm-reader-exit","");grid.appendChild(exit);tools.hidden=true;tools.style.setProperty("display","none","important");var toolHeading=document.createElement("h3");toolHeading.textContent="常用工具";toolHeading.style.cssText="margin:0 0 12px;font-size:13px;opacity:.65;";tools.insertBefore(toolHeading,grid);var backupHeading=document.createElement("h3");backupHeading.textContent="备份与恢复";backupHeading.style.cssText="margin:22px 0 12px;font-size:13px;opacity:.65;";tools.insertBefore(backupHeading,toolsContent);var logArea=toolsContent.querySelector("[data-bm-repeat-log]");if(logArea)tools.insertBefore(logArea,backupHeading);var backupGrid=document.createElement("div");backupGrid.setAttribute("data-bm-progress-tool-grid","");tools.insertBefore(backupGrid,toolsContent);["export","import","toggle-text"].forEach(function(name){var b=grid.querySelector("[data-bm-backup-"+name+"]");if(b)backupGrid.appendChild(b)});main.insertBefore(tools,list);reader.appendChild(panel);state.progressPanel=panel;state.progressList=list;state.progressStatus=status;bookProgress.update()}
function loadLibrary() {
var text = localStorage.getItem(libraryKey);
if (text === null) return { version: 1, books: [] };
var value;
try { value = JSON.parse(text); }
catch (_) { throw new Error('书架记录格式异常,现有记录已保留。'); }
return validateBackupLibrary(value);
}
function currentBookName() {
if (!libraryBookId) return '';
if (/^\/book\//.test(location.pathname)) {
var heading = document.querySelector('.bookDetail h1');
if (heading && heading.textContent.trim()) return heading.textContent.trim().slice(0, 120);
}
var anchors = document.querySelectorAll('.conC .position a[href]');
for (var i = 0; i < anchors.length; i++) {
var url = siteUrl(anchors[i].getAttribute('href'));
if (url && url.pathname === '/book/' + libraryBookId + '.html') {
var name = anchors[i].textContent.trim();
if (name) return name.slice(0, 120);
}
}
var description = document.querySelector('meta[name="description"], meta[property="og:description"]');
var found = description && (description.getAttribute('content') || '').match(/^(?:菠萝猫提供)?《([^》]+)》/);
if (found) return found[1].trim().slice(0, 120);
return '';
}
function rememberLibraryBook() {
if (!libraryBookId || !/^\d{1,18}$/.test(libraryBookId)) return;
try {
var library = loadLibrary(); var name = currentBookName();
var entry = library.books.find(function (book) { return book.id === libraryBookId; });
if (entry) {
if (!name || name === entry.name) return;
entry.name = name; entry.updatedAt = Date.now();
} else {
if (library.books.length >= 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<merged.length;i++){if(merged[i].id!==bookId)continue;state.chapterOrders=new Map(merged[i].chapterOrders);state.chapterTitles=new Map(merged[i].chapterTitles);state.savedProgress=merged[i].latest;state.progressHistory=merged[i].history;state.tocPage=merged[i].catalogPage;state.progressTouched=false;if(state.progressTimer){clearTimeout(state.progressTimer);state.progressTimer=null}break}applyTheme(checked.settings.theme);applyFontSize(checked.settings.fontSize);setToolbarCollapsed(checked.settings.toolbarCollapsed);updateResumeButton();renderProgressHistory();if(restoredLibrary){updateFavoriteButtons();if(state.shelfOpen)renderShelf()}setBackupStatus("已合并 "+checked.books.length+" 本书的进度,背景、字号和工具栏设置已恢复。");return checked}
function setBackupStatus(message) {
if (state.backupStatus) state.backupStatus.textContent = message || '';
}
function toggleBackupText(force) {
if (!state.backupTextPanel) return;
var show = typeof force === 'boolean' ? force : state.backupTextPanel.hidden;
state.backupTextPanel.hidden = !show;
state.backupTextPanel.style.setProperty('display', show ? 'block' : 'none', 'important');
}
function exportBackup() {
try {
if (state.progressTouched) saveProgressNow(true);
var data = collectBackup(); var text = JSON.stringify(data, null, 2);
if (text.length > 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<anchors.length;i++){var url=readUrl(anchors[i].getAttribute("href"),pageUrl.href);if(!url||url.search||seen.has(url.href))continue;var label=anchors[i].textContent.trim();if(!label)continue;var order=(cp-1)*100+i+1;chapters.push({url:url,label:label,order:order});seen.add(url.href);state.chapterOrders.set(url.pathname,order);state.chapterTitles.set(url.pathname,cleanChapterTitle(label))}try{persistTextIfChanged(chapterOrderKey,JSON.stringify(Array.from(state.chapterOrders)))}catch(_){}try{persistTextIfChanged(chapterTitleKey,JSON.stringify(Array.from(state.chapterTitles)))}catch(_){}reconcileProgressOrders();if(!chapters.length)throw new Error("这一页没有可用章节,当前阅读已保留");var ranges={};ranges[cp]=String((cp-1)*100+1)+"-"+String((cp-1)*100+chapters.length);var max=cp;var rangeLinks=doc.querySelectorAll("#chapters .page-range-list a[href]");for(var j=0;j<rangeLinks.length;j++){var rangeUrl=siteUrl(rangeLinks[j].getAttribute("href"),pageUrl.href);if(!rangeUrl||rangeUrl.pathname!=="/book/"+bookId+".html")continue;var page=Number(rangeUrl.searchParams.get("cp"));if(!(page>=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<text.length;i++){var code=text.charCodeAt(i);first=Math.imul(first^code,16777619);second=Math.imul(second,33)^code}return text.length+":"+(first>>>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<count;offset++){var index=start+offset;if(!removed.has(index)){var target=targets(offset);removed.set(index,{from:index,toUrl:target.toUrl,toIndex:target.toIndex})}}}(references||[]).forEach(function(reference){if(!reference||!Array.isArray(reference.paragraphs)&&!Array.isArray(reference.keys))return;var hashed=Array.isArray(reference.keys);var texts=hashed?reference.keys:reference.paragraphs.map(function(text){return String(text).replace(/[\s\u200b-\u200d\ufeff]+/g,"")});var compared=hashed?digests:normalized;var referencePositions=paragraphPositions(texts);var indexes=reference.indexes||reference.paragraphIndexes||[];if(reference.url)texts.forEach(function(text,index){var key=hashed?text:digest(text);var originalIndex=indexes[index];if(!knownParagraphs.has(key))knownParagraphs.set(key,{toUrl:reference.url,toIndex:Number.isInteger(originalIndex)&&originalIndex>=0?originalIndex:index})});for(var candidate=0;candidate<normalized.length;candidate++){if(!normalized[candidate])continue;var matches=referencePositions.get(compared[candidate])||[];for(var match=0;match<matches.length;match++){var source=matches[match];if(candidate>0&&source>0&&texts[source-1]===compared[candidate-1])continue;var count=0;while(source+count<texts.length&&candidate+count<normalized.length&&texts[source+count]===compared[candidate+count])count++;if(!longEnough(candidate,count))continue;if(reference.url)hasReferenceBlock=true;(function(start,referenceStart,runLength){mark(start,runLength,function(offset){var originalIndex=indexes[referenceStart+offset];return{toUrl:reference.url||null,toIndex:Number.isInteger(originalIndex)&&originalIndex>=0?originalIndex:referenceStart+offset}})})(candidate,source,count)}}});for(var candidate=1;candidate<normalized.length;candidate++){if(!normalized[candidate])continue;var matches=internalPositions.get(normalized[candidate])||[];for(var match=0;match<matches.length&&matches[match]<candidate;match++){var source=matches[match];if(source>0&&normalized[source-1]===normalized[candidate-1])continue;var count=0;while(source+count<candidate&&candidate+count<normalized.length&&normalized[source+count]===normalized[candidate+count])count++;if(!longEnough(candidate,count))continue;(function(start,referenceStart,runLength){mark(start,runLength,function(offset){return targetFor(referenceStart+offset)})})(candidate,source,count)}}if(hasReferenceBlock&&digests.every(function(key){return knownParagraphs.has(key)})){removed.clear();digests.forEach(function(key,index){var target=knownParagraphs.get(key);removed.set(index,{from:index,toUrl:target.toUrl,toIndex:target.toIndex})})}var kept=[];var indexes=[];for(var index=0;index<paragraphs.length;index++){if(removed.has(index))continue;kept.push(paragraphs[index]);indexes.push(index)}return{paragraphs:kept,indexes:indexes,aliases:Array.from(removed.values()).sort(function(a,b){return a.from-b.from}),removed:removed.size}}
function contentFingerprint(paragraphs) {
var text = paragraphs.join('').replace(/[\s\u200b-\u200d\ufeff]+/g, '');
var first = 2166136261, second = 5381;
for (var i = 0; i < text.length; i++) {
var code = text.charCodeAt(i);
first = Math.imul(first ^ code, 16777619);
second = Math.imul(second, 33) ^ code;
}
return text.length + ':' + (first >>> 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<nodes.length;i++){if(hidden(nodes[i],content))continue;var text=nodes[i].textContent;if(nodes[i].hasAttribute("data-obf")){try{text=decode(nodes[i].getAttribute("data-obf"))}catch(_){throw new Error("正文解码失败,自动接续已停止")}}if(text&&text.trim()&&!isChapterArtifact(text))paragraphs.push(text.trim())}if(!paragraphs.length)throw new Error("下一页没有可读取的正文,自动接续已停止");var heading=doc.querySelector(".conC h1.title");var paging=verifyDisplayedPage(heading,url);var links=doc.querySelectorAll(".readPage a[href]");var next=null;var invalidNext="";for(var kind=0;kind<2&&!next;kind++){var pattern=kind===0?/^下一页/:/^下一章/;for(var j=0;j<links.length;j++){if(!pattern.test(links[j].textContent.trim())||hidden(links[j],null))continue;var candidate=readUrl(links[j].getAttribute("href"),url.href);if(!candidate)throw new Error("下一页地址不属于本站这本书或页码无效,自动接续已停止");if(!isForwardReadLink(url,candidate)){invalidNext=candidate.pathname===url.pathname?"下一页链接重复或未向前推进,自动接续已停止":"下一章链接倒退或页码无效,自动接续已停止";continue}next=candidate;break}}var chapterInfo=doc.querySelector(".conC .toolBox > .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;i<paragraphs.length;i++){var paragraph=paragraphs[i];var rect=paragraph.getBoundingClientRect();var url=readUrl(paragraph.getAttribute("data-bm-progress-url"));if(!url||!(rect.height>0)||!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.top<viewport.top+height){visible=paragraph;bounds=rect}}if(!visible)return;var visibleKey=pageKey(readUrl(visible.getAttribute("data-bm-progress-url")));var activeIndex=pages.findIndex(function(page){return pageKey(page.url)===visibleKey});var cut=Math.min(pages.length-8,activeIndex-2);while(cut>0&&!(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;j<updated.length;j++){if(updated[j].getAttribute("data-bm-progress-url")!==visible.getAttribute("data-bm-progress-url")||updated[j].getAttribute("data-bm-progress-index")!==index)continue;var after=updated[j].getBoundingClientRect();if(after.height>0&&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<data.chapters.length)knownNext=data.chapters[index+1].url;else if(cp<data.max){var following=state.tocCache.get(cp+1);if(following&&following.chapters&&following.chapters.length)knownNext=following.chapters[0].url}});if(knownNext&&isForwardReadLink(cursor,knownNext)&&!state.seen.has(pageKey(knownNext)))return knownNext;var order=state.chapterOrders.get(cursor.pathname);var hint=order?Math.floor((order-1)/100)+1:state.tocPage;var queue=[hint,1];var visited=new Set;var max=1;for(var scans=0;scans<100;scans++){var cp=0;while(queue.length&&!cp){var queued=queue.shift();if(queued>=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<chapters.length)return chapters[index+1].url;if(result.cp>=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.pageNumber<page.pageTotal);if(!canAdvancePart)throw recoverablePageError("网站重复返回已显示的正文")}else if(state.contentSeen.has(page.fingerprint))throw recoverablePageError("网站重复返回已显示的正文")}catch(error){checkReadSequence(sequence,false);if(!error.bmRecoverable)throw error;if(/重复返回已显示的正文/.test(error.message))recordRepeatPage(page,"whole");if(attempts<2){attempts++;await waitAutoRecovery(Math.max(0,2e3-(Date.now()-state.lastRequest)),sequence,"正在自动重取正文…");freshRequest=true;continue}if(url.pathname!==previous.pathname)cursor=url;repair=true;continue}checkReadSequence(sequence,state.recovering);if(!page.paragraphs.length){if(++skippedParts>12)throw new Error("本站连续重复分页过多,当前阅读已保留");recordRepeatPage(page,"skip");var followingPart=page.next;if(!followingPart&&page.pageNumber<page.pageTotal){followingPart=new URL(page.url.href);followingPart.searchParams.set("p",String(page.pageNumber+1))}state.seen.add(pageKey(page.url));state.next=followingPart;state.nextIssue="";if(!followingPart)return;state.forwardLinks.set(pageKey(page.url),pageKey(followingPart));url=followingPart;cursor=page.url;freshRequest=false;await waitAutoRecovery(Math.max(0,2e3-(Date.now()-state.lastRequest)),sequence,"正在略过重复段落…");continue}state.forwardLinks.set(pageKey(previous),pageKey(page.url));if(previous.pathname!==page.url.pathname&&state.chapterOrders.has(previous.pathname)&&!state.chapterOrders.has(page.url.pathname)){state.chapterOrders.set(page.url.pathname,state.chapterOrders.get(previous.pathname)+1);try{persistTextIfChanged(chapterOrderKey,JSON.stringify(Array.from(state.chapterOrders)))}catch(_){}}page.chapterStart=previous.pathname!==page.url.pathname;recordRepeatPage(page,"cleaned");rememberChapterTitle(page);delete page.content;state.pages.push(page);state.seen.add(pageKey(page.url));state.contentSeen.add(page.fingerprint);state.next=page.next;state.nextIssue=page.nextIssue||"";rememberParagraphDigests(page);appendPage(state.source,page);if(activeReader()){appendPage(state.content,page);markProgressParagraphs();bookProgress.update()}return}}catch(error){if(sequence===state.readSeq&&!error.bmCancelled){halt(error.name==="AbortError"?"下一页网络超时,自动接续已停止":error.message||"自动接续失败")}}finally{if(sequence===state.readSeq){state.busy=false;state.recovering=false;state.recoveryMessage="";updateStatus();compactReadPages();bookProgress.update();bookProgress.ensure()}}}
function bindReader() {
var reader = document.querySelector('.via-reader-body');
var content = reader && reader.querySelector('.via-reader-content');
if (reader && content) {
state.autoEntryDone = true;
if (state.autoEntryTimer) { clearTimeout(state.autoEntryTimer); state.autoEntryTimer = null; }
}
if (!state.source || !reader || !content) {
bookProgress.stop();
readerEntry.onMissingReader();
cancelAutoRecovery();
if (state.reader) {
state.reader.removeEventListener('scroll', schedule);
state.reader.removeEventListener('scroll', scheduleProgress);
}
state.reader = state.content = state.status = state.paper = state.toolbar = state.toolbarHandle = null;
state.tocPanel = state.tocList = state.tocPages = state.tocStatus = null;
state.tocOpen = false; state.tocSeq++;
state.progressPanel = state.progressList = state.progressStatus = null;
state.repeatLogArea = state.repeatLogList = state.repeatLogButton = null;
if (state.shelfPanel && !state.shelfPanel.isConnected) {
state.shelfPanel = state.shelfList = state.shelfStatus = null;
state.shelfOpen = false; state.shelfSeq++;
}
state.progressOpen = false; state.progressSeq++;
state.retryPending = false;
stopCatalogueRequest();
if (state.timer) { clearTimeout(state.timer); state.timer = null; }
if (state.progressTimer) { clearTimeout(state.progressTimer); state.progressTimer = null; }
return;
}
if (reader === state.reader && content === state.content) return;
if (state.reader) {
state.reader.removeEventListener('scroll', schedule);
state.reader.removeEventListener('scroll', scheduleProgress);
}
state.reader = reader; state.content = content;
state.progressTouched = false;
installThemeControls(reader);
hideReaderNavigation(reader, content);
state.pages.forEach(function (page) { appendPage(state.source, page); appendPage(content, page); });
markProgressParagraphs();
syncVisiblePage(state.first.url);
recordProgress({ url: state.first.url.href, index: 0, ratio: 0,
time: Date.now(), title: state.first.title }, false, false);
var status = document.createElement('div'); status.setAttribute('data-bm-pager-status', '');
status.setAttribute('role', 'status');
status.style.cssText = 'font-size:.85em;line-height:1.8;margin:12px 16px 24px;';
status.hidden = true; content.parentElement.appendChild(status); state.status = status;
reader.addEventListener('scroll', schedule, { passive: true });
reader.addEventListener('scroll', scheduleProgress, { passive: true });
var restoredEntry=readerEntry.onBind(reader,content);
if(!restoredEntry) restorePendingProgress();
updateStatus(); bookProgress.update(); bookProgress.ensure();
if(!restoredEntry) schedule();
}
function start() {
var initial = readUrl(location.href);
var first;
try { first = cleanPageParagraphs(extract(document, initial), []); }
catch (_) {
try { localStorage.__via_reader_enabled__ = null; } catch (ignored) {}
return;
}
state.source = first.content; state.first = first; state.next = first.next;
rememberLibraryBook();
recordRepeatPage(first, 'initial');
state.nextIssue = first.nextIssue || '';
state.contentSeen.add(first.fingerprint);
rememberParagraphDigests(first);
rememberChapterTitle(first);
installReaderStyle();
state.source.setAttribute('data-bm-page', pageKey(initial)); state.seen.add(pageKey(initial));
var nodes = state.source.querySelectorAll('p[data-obf]');
for (var i = 0; i < nodes.length; i++) {
if (!hidden(nodes[i], state.source)) nodes[i].textContent = decode(nodes[i].getAttribute('data-obf'));
}
var sourceParagraphs = state.source.querySelectorAll('p');
var rawIndex = 0;
var keptIndexes = new Set(first.paragraphIndexes);
for (var j = 0; j < sourceParagraphs.length; j++) {
var sourceParagraph = sourceParagraphs[j];
if (isChapterArtifact(sourceParagraph.textContent)) { sourceParagraph.remove(); continue; }
if (hidden(sourceParagraph, state.source) || !sourceParagraph.textContent.trim()) continue;
var originalIndex = rawIndex++;
if (!keptIndexes.has(originalIndex)) sourceParagraph.remove();
else sourceParagraph.setAttribute('data-bm-source-index', String(originalIndex));
}
if (!document.querySelector('.via-reader-body') &&
document.querySelector('.conC h1.title') && document.querySelector('.readPage')) {
try { localStorage.__via_reader_enabled__ = '1'; } catch (_) {}
requestAutoReader();
}
new MutationObserver(function () {
if(state.reader&&state.reader.isConnected&&state.content&&state.content.isConnected)return;
bindReader();
}).observe(document.body, { childList: true, subtree: true });
window.addEventListener('resize', schedule, { passive: true });
document.addEventListener('visibilitychange', function () {
if (document.hidden) bookProgress.stop();
if (document.hidden && (state.progressTouched || !normalizeProgress(state.savedProgress))) saveProgressNow();
else { schedule(); bookProgress.update(); bookProgress.ensure(); }
});
window.addEventListener('pagehide', function () {
bookProgress.stop();
state.tocOpen=false;state.tocSeq++;stopCatalogueRequest();
if (state.progressTouched || !normalizeProgress(state.savedProgress)) saveProgressNow();
});
bindReader();
}
function requestAutoReader() {
if (readerEntry.isExited()) return;
if (state.autoEntryDone || state.autoEntryTimer) return;
state.autoEntryTimer = setTimeout(function () {
state.autoEntryTimer = null;
if (readerEntry.isExited() || state.autoEntryDone || !state.first || !document.body) return;
state.autoEntryDone = true;
if (document.querySelector('.via-reader-body')) { bindReader(); return; }
var reader = document.createElement('div');
reader.className = 'via-reader-body';
reader.setAttribute('data-bm-auto-reader', '');
var frame = document.createElement('div');
frame.style.cssText = 'width:100%;box-sizing:border-box;font-size:' + fontSize + 'px;';
var header = document.createElement('div'); header.className = 'via-reader-header';
var title = document.createElement('h1'); title.textContent = state.first.title;
header.appendChild(title); frame.appendChild(header);
var content = document.createElement('div'); content.className = 'via-reader-content';
state.first.paragraphs.forEach(function (text, index) {
var paragraph = document.createElement('p'); paragraph.textContent = text;
paragraph.setAttribute('data-bm-source-index', String(state.first.paragraphIndexes ? state.first.paragraphIndexes[index] : index));
content.appendChild(paragraph);
});
frame.appendChild(content); reader.appendChild(frame);
var key = 'via-reader-data';
if (!document.body.hasAttribute(key)) document.body.setAttribute(key, document.body.style.overflow);
document.body.style.overflow = 'hidden';
var viewport = document.querySelector('meta[name="viewport"]');
if (viewport && !viewport.hasAttribute(key)) {
viewport.setAttribute(key, viewport.getAttribute('content') || '');
viewport.setAttribute('content', 'width=device-width');
}
try { localStorage.__via_reader_enabled__ = null; } catch (_) {}
document.body.appendChild(reader);
bindReader();
}, 500);
}
function exitAssistedReader() { bookProgress.stop(); return readerEntry.exit(); }
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start, { once: true });
else start();
})();
$ tail -f comments.log
Comments [0]
// no comments yet