v0.1.5
下载与安装
本页为 Android 安装包,按设备 CPU 架构拆分为 4 个 APK,选择匹配的一个下载即可:
- arm64-v8a:近几年的主流手机 / 平板(64 位),绝大多数设备选这个
- armeabi-v7a:仅支持 32 位应用的老设备
- x86_64 / x86:Android 模拟器(分别对应 64 / 32 位镜像)
系统要求 Android 7.0(API 24)及以上。
直接覆盖安装即可升级,书架、书源、设置等数据保留在设备本地,不受影响。
使用中遇到问题欢迎到 Issues 反馈,请附上设备型号与 Android 版本。
以下为本版本更新内容:
Added
-
设置 → 关于新增「开源库声明」:随包使用的第三方开源库(前端 solid-js / pdf.js / opencc-js
等,后端 Rust 的 tauri / boa / reqwest / RustCrypto 等)连同各自许可与在 ReaderX 中的用途,
整理成独立的THIRD-PARTY-NOTICES.md,与LICENSE一样作为 Tauri resource 打进安装包,
由 Rust 侧新命令readerx_third_party_notices读入(Android 走 asset 资源目录),
在设置页最后一栏点开即可查看全文;两份随包文本(许可 / 声明)共用同一个全屏文本弹层组件。 -
支持导入 PDF:本地导入(书架
+)与 WebDAV 导入都能选.pdf了,导入后与其他本地书一样
进书架、可阅读 / 加书签 / 加入分组。解析走 pdf.js(只在真正导入 PDF 时才载入,不进首屏):- 文字层优先:按文字片段的坐标还原行与段落(中文不插空格、西文按间距补空格、行尾连字符
合并回一个词),页眉页脚按「跨页重复出现 + 位于版心之外」两个条件一起判定后剔除,
正文里引用的书名不会被误删; - 分章跟随 PDF 自带书签(多级大纲逐层纳入,条目过多时只保留上层),没有可用大纲时按累计
正文字数分章(整本扫描件按固定页数分章);章节标题带「第 N–M 页」便于定位,
大纲第一条之前的书名页 / 版权页并进第一章; - 扫描件兜底:整本几乎没有文字层时,每一页渲染成图片阅读;文字很少但画了图的页
(封面、影印插页)同样按图渲染。页面图在导入时落盘到应用数据目录(images/),
书籍 JSON 里只留文件名,渲染经既有的readerx-img协议直读,删除书籍时一并清理; - 多了一本 PDF 不影响已有书籍:只新增
format: "pdf"这一种取值,旧数据照常读取。
- 文字层优先:按文字片段的坐标还原行与段落(中文不插空格、西文按间距补空格、行尾连字符
-
段内插图(文字内嵌图片):段落里的图片不再被拆出来独占一行,而是留在文字流里与文字同排 ——
EPUB 里写在段落中的<img>(含 SVG<image xlink:href>)与书源 HTML 里穿插在文字间的<img>
都保留原位置:图片插在段落文本的第几个字符之前(ChapterBlock.imgs的at锚点,图片本身不占
字符),小图随文字同行、图比栏宽时按栏宽等比缩放并在段内自然换行,<p>他指着<img>说道</p>
不再被切成「文字段 + 图片块 + 文字段」;一段里只有图没有文字时仍按整行插图排版(漫画 / 扫图章节
的既有口径不变)。分页引擎按同一套行内样式测量含图段落(前缀高度对字符数仍单调,二分切分照旧),
段内图与整行图共用全部图片基础设施:按书源会话下载、落成本地文件、readerx-img协议直读、
未就绪转圈占位、失败可就地「重试」、窗口预取与「下载全部正文」一并覆盖,删除书籍时一起清理。
段落内图片不计入字符,书签 / 进度 / 搜索 / 朗读的偏移与旧版完全一致(旧进度仍指向同一段文字);
文本替换与简繁转换会带着锚点一起重定位。兼容性:书源schemaVersion仍为 1(老书源零改动,
返回纯文本 / 对象{ text, images }的行为不变),纯文字段落不写imgs字段,旧版客户端忽略它
即可;旧数据里以 data URL 内嵌的段内图在打开书籍时同样自动迁移成图片文件(book_images
的迁移与自愈现在覆盖 img 块与 p 块的imgs锚点);本地 EPUB 需重新导入才按新口径排版
(已导入的书不保留原文件,旧数据照原样渲染)。书源侧的写法见
docs/book-source-image.md。 -
书源登录支持「不用 Cookie 记登录信息」的站点(localStorage / sessionStorage / IndexedDB):
一部分站点把登录凭证(JWT、uid+token)写在 localStorage 里而不是 Cookie 里,此前只抓
Cookie 的登录浮层在这些站点上等于没登录。现在点「完成」时(以及自动网页认证刷新时)会顺带
采集存储快照:Android 应用内浮层用页面探针抓 localStorage / sessionStorage 与 IndexedDB 的
库名 / 对象仓清单,CLI 的--auth cdp走 CDP 原生DOMStorage逐 origin 读取(localStorage
被页面改写也读得到),--auth webkit走同一份探针;快照与 Cookie 一起写进该书源的登录态文件
(不随书源 JSON 导出/分享,应用重启自动注入),书源代码用新接口webview.storage()
读取({ url, origin, localStorage: {…}, sessionStorage: {…}, indexedDb: […], updatedAt },
只读、不触发认证,没有快照时返回空对象),把 token 显式放进请求头 / 参数即可。
IndexedDB 只记结构不记记录内容(记录往往是整表业务数据,体积与隐私都不适合放进登录态),
每个 origin 每类最多 500 条、单值最多 8192 字符(超出截断)。新增 CLI
auth storage [--reveal]查看快照(默认只显示值的前 8 个字符,避免 token 进了终端与 CI 日志),
auth show也会提示已保存多少项;书源编辑页的登录提示同时报告 Cookie 与存储条数。
登录态文件是增量扩展的(旧文件只有url/cookie/updated_at也照常读写,App 与 CLI
仍能交替使用同一份数据目录),且「只导入 Cookie」的操作不会抹掉已抓到的存储快照(反之亦然)。 -
主 Tab 与 WebDAV 导入页改为常驻(保活)页面,切页不再丢状态:书架 / 发现 / 设置三个主 Tab
与「WebDAV 导入」页现在由页面栈按KEPT_PAGES注册表(src/App.tsx)直接挂载并常驻 DOM,
切走只是隐藏、再进入复用同一层 —— 搜索词、结果列表、分组筛选、勾选与滚动位置全部原样保留,
不再重新挂载:此前每次切 Tab、或从阅读页返回 WebDAV 导入页,页面都会被卸载重建,跑到一半的
搜索被丢弃、浏览到的目录回到根目录、勾选与滚动位置一并清零。这些路径因此不再出现在路由表里
(路由只声明真正会 push / pop 的次级页面)。其余页面(阅读页、书源、详情等)仍按原来的
推入 / 弹出逻辑卸载,它们离场时改用一份滚动区快照参与退出动画,滑出 / 淡出看到的仍是离开时的
那一页(此前过渡中两层会互相抢同一份路由内容,旧页时而变成新页的副本、时而空白)。
挂在<Portal>上的弹层(导入菜单、WebDAV 配置抽屉、在线书详情抽屉、重新导入弹窗)
在离开所属页面时自动收起,不会残留在别的页面上。 -
书源支持粘贴导入:书源管理页右上角新增「粘贴」(剪贴板)图标,书源列表为空时的
空状态里也有「粘贴导入」按钮——打开抽屉即静默读一次剪贴板预填,也可以直接在输入框里
粘贴 / 手打 JSON(单条或数组);输入框里放的是 http(s) 网址时按钮变为「拉取并导入」,
联网取回即可,手里是网址还是 JSON 不必先分辨。JSON 解析不了 / 没识别到书源 / 拉取失败
都在抽屉里就地提示,不再出现「点了导入却什么都没发生」。此前粘贴入口只藏在「确认导入」
弹层底部,必须先选文件或拉一次网址才能看到,等于进不去。 -
书源分组:书源管理页顶部新增分组筛选条(全部 / 未分组 / 各自定义分组,带数量),
长按分组标签或点尾部「分组」进入分组管理(新建 / 重命名 / 删除);书源行右侧的文件夹按钮
(编辑页「书源信息 → 分组」同理)把书源归入分组,分组管理里还能对整组一键启用 / 停用,
只改写状态不一致的书源。删除分组时由 Rust 侧整批清掉组内书源的归属(退回未分组),
不留指向已删分组的悬空引用。 -
书源管理页支持长按多选:长按书源行进入多选(该行随即选中),此后点击行即勾选 / 取消勾选,
页头显示已选数量与「全选」——全选按当前筛选来,只作用于此时可见的书源(配合分组筛选即可
「先筛出一组再全选」)。选中后底部操作条可批量启用 / 停用(只改写状态不一致的书源)、
一起归入 / 移出分组、导出为一个 JSON 数组(复制到剪贴板,便于分享 / 备份)与删除
(连点两次确认);操作完成后自动退出多选。 -
发现页按分组限定书源:筛选条与书源管理页共用同一个(会被记住的)选中值,搜索与「发现」
都只在该分组的已启用书源里跑,数量按当前模式统计;切分组后当前发现源不在范围内时
自动收起它的分类与结果,避免「加载更多」继续向范围外的书源要数据。 -
分组随书源 JSON 导入 / 导出:备份 / 分享只带可读的
groupName(不带本机分组 id),
导入时按名字匹配本机分组(没有则新建),确认页列出将「新建 / 已有」的分组并可整体关闭
「按分组名导入分组」——关闭后新导书源落未分组,覆盖已有书源时保留本机原有的分组。 -
在线书图片预取:正文按「当前章 ±5 章」窗口预取,这一圈章节的正文都取回之后,
阅读器接着把窗口内已下载章节里还没取过的图片也取回来(读到哪一章就先取哪一章),
顺序往下读时图片通常已经在本地;预取失败即放弃(不在后台反复重试、不计入章节失败),
用户读到该章时阅读路径会再取一次 —— 预取失败的图片不会被失败冷却挡住,
占位框上还有「重试」。图片预取与正文预取共用同一个下载闸与同址去重,不会重复请求;
用户跳章 / 听书跨章时立即改以新章为中心,批量下载开始时让位,「停止下载」即时停下
(同一章不会刚停就自动重启),下载面板按阶段显示图片进度。图片预取在正文预取结束后
才开跑,因此不会挤占正在读的那一章的正文加载。 -
书源加密工具补齐 SHA-256 / HMAC / AES-256-GCM:
cryptoUtil在原有的 MD5 / SHA-1
之上新增sha256、hmac(algorithm, key, data, encoding?)(MD5 / SHA-1 / SHA-256)与
aesGcmEncrypt/aesGcmDecrypt(AES-256-GCM,key32 字节、iv12 字节,未给 IV 时
随机生成,支持 AAD,密文尾部按 WebCrypto 约定带 16 字节认证标签),摘要与 HMAC 可输出
hex(默认)或 base64;另加hexEncode/hexDecode两个十六进制工具。base64.decode与
hexDecode改为字节保留字符串(1 字符 = 1 字节),解码出的密钥 / 密文可以直接喂回
cryptoUtil或拼进请求体,不再被 UTF-8 往返改写;密钥 / IV 支持 base64、hex、UTF-8
字面量三种写法(按字节数精确匹配,长度不对直接报错,不做静默填充或截断)。
规范见docs/book-source-api.md。 -
书源引擎可编译为独立二进制
readerx-source:书源引擎(Boa 沙箱 + 宿主 API)与书源
持久化抽成独立 cratesrc-tauri/crates/readerx-source,App 与命令行共用同一份代码;
不启动应用即可跑规则——离线检查书源文件、调单个入口函数、端到端跑「搜索 → 目录 → 正文」。
子命令:sources/call/run/test/auth/login,支持--json机器输出与
--verbose打印书源console日志;--data-dir可指向 App 的数据目录,直接跑已安装的书源
与其登录态。用法见 docs/book-source-cli.md。 -
独立二进制的浏览器标志与登录态支持:
--ua/--header/--cookie/--cookie-file
(Netscape cookies.txt、EditThisCookie / Cookie-Editor JSON、Playwright storageState、
DevTools 请求头文本,按内容自动识别),以及带域名作用域的 Cookie 导入——浏览器里存着
几十个站点的 Cookie,只按请求域名 / 路径 / https 逐条筛选发送,不再整行无条件外发。 -
独立二进制可在真实浏览器内核里过 Cloudflare 挑战 / 登录:
auth webkit用系统
WebKitGTK 打开页面(与 App 同内核,无头可用xvfb-run),auth cdp连已有 Chrome 的
DevTools 端口取 Cookie(--browser也可由本工具拉起浏览器)。抓到的 Cookie(含 httpOnly 的
cf_clearance)覆盖式保存为该源登录态并立即注入会话;auth show/auth clear查看与清理。
Changed
- 「确认导入」弹层底部的「从剪贴板粘贴导入」改为「继续粘贴导入」按钮:点它退回粘贴抽屉换一份
内容重新解析,不必先关掉弹层再从头进。 - 导入相关的界面拆成独立组件:
SourcePasteImportSheet(粘贴抽屉)与
SourceImportConfirmSheet(确认导入),三条导入路径共用同一份确认界面,书源管理页只留状态与流程。 - 图片下载失败(或被清理掉的本地副本读不出来)时的占位框改为把「重试」按钮放在
占位文字的右边(同一行),不再另起一行。 - 构建产物瘦身:简繁转换词典改为按需取用的压缩资源(新增
scripts/han-dict.mjs与
src/lib/hanDict.ts):opencc-js 把词典以 JS 字符串字面量发布,简→繁那一份打出来是
1.1 MB 的 JS chunk(构建时触发 Vite「chunk 大于 500 kB」告警),WebView 每次都要下载并解析
这么大一坨模块。词典本质是纯数据,现改为构建期 gzip 成src/generated/han-dict-<方向>.bin
(简→繁 450 KB、繁→简 28 KB),运行时按方向取回、解压(优先内核DecompressionStream,
老内核回退 fflate)后再用 opencc-js 建树;未开启简繁转换的用户依旧完全不下载。
转换结果与opencc-js/cn2t、opencc-js/t2cn逐字一致(已用约 900 万字语料双向比对)。
词典产物由vite.config.ts的hanDict插件在 dev / build 启动时自动生成、不入库,
词典未变不重复压缩;两个方向共用同一个 4 KB 小 chunk(opencc 核心 + 词典地址),
不再各自复制一份核心代码。词典相关产物共省下约 700 KB。 - 首屏不再下载 EPUB 解压库:
src/lib/books.ts对./epub改为按需import(),fflate 与
EPUB 解析器移出首屏(入口 chunk 56.9 KB → 44.3 KB,gzip 21.9 KB → 16.4 KB),
两者都只在真正导入电子书时才加载。 - 在线书入架后可直接加入分组:在线书加入书架后,提示条改为「已放入书架」+ 橙色「加入分组」
按钮,点它当场弹出「移入分组」抽屉(选已有分组、就地新建,也可移出分组或归入「隐藏」),
不选分组就只是提示、什么都不改。抽屉挂在应用外壳上而不是发起入架的组件里,因此入架后
已经跳到阅读页 / 书架、或书籍详情抽屉已换过书,按钮依然可用;分组结果照旧走
readerx_book_patch_meta就地落盘,书架按分组筛选时立刻能看到。此前「已加入书架」的提示
被书籍详情抽屉的遮罩压在下面,在「发现 → 搜到书 → 加入书架」这条最常用路径上根本看不见;
提示层级现在提到抽屉之上,且带按钮的提示停留更久(6 秒)并改用卡片样式(原深色胶囊上
橙色按钮字看不清)。抽屉标题随「加入分组」的说法统一为「移入分组」(书架多选入口同样用它)。 - 听书定时不再被「停止朗读」一并清掉:定时(分钟 / 本章结束)此前绑在单次朗读会话上,
stop()会把它复位 —— 正在定时朗读时用选中文字菜单点「朗读」,内部动作是「先停旧会话、
再从句首起播」,于是刚设好的定时被静默取消(打开面板已是「关闭」)。现在停止朗读只结束
这一轮播放,定时原样保留:分钟倒计时在停止期间冻结、重新起播接着走(剩余时间照常显示),
「本章结束」留给下一次朗读;只有面板里选「关闭」或真的到点才解除。
Removed
- 阅读菜单进度条上的「逐页刻度」:进度条上按本章页数等分的灰色圆点与阅读设置里的对应
开关一并移除(进度条本身、拖动跳页与「x/y页」提示都不受影响)。旧版本存过的
readerx.menuSliderNodes偏好会在启动时由后端删掉,不在应用数据目录里留下没人再读的
状态文件;迁移幂等,删不掉也只是留个文件,不影响其它偏好。
Fixed
- 登录态(Cookie 与存储快照)现在登录完立刻生效,不再需要重启:写入登录态的入口
(http.setCookie/ 存储快照缓存)要求「本书源会话已建立」,而应用内「网页登录」这条入口
从不建会话 —— 于是「本进程还没调用过该书源就先登录」时,抓到的 Cookie 与 localStorage 快照
只写进了文件、内存会话里没有,表现就是要重启一次才生效(重启后由「注入已保存登录态」补上)。
现在这些写入入口会按需建立会话(已有会话不动,不会覆盖 CLI--ua/ 身份文件 / 抓到的
作用域 Cookie),认证入口也先把会话准备好;同一次登录的 Cookie 与快照一律先注入内存、
再落盘(写盘失败只影响「重启后是否还在」,不影响本次登录),并补了两条回归测试。 - 在线书「重新拉取书籍信息」现在也同步标签:该按钮此前只写回简介与封面,书源在
bookDetail里新增 / 改动的tags点刷新也不会同步(详情页标签区一直停在入架那一刻)。
现按「书源来源 + 手编」两笔账合并:书籍记录新增sourceTags(上次由书源写回的标签),
刷新时保留手编标签、补上书源新增的、去掉书源已删除的,现有标签的相对顺序不变;
入架时随书保存的标签一并记为书源来源。老数据没有这笔记录(无从区分手编与书源),
首次刷新退化为并集 —— 只追加不删除,绝不丢用户手编的标签;bookDetail这次没返回
tags(或返回空数组)时不动书架上的标签。刷新结果提示与按钮说明同步改为「简介、封面与标签」。 - 元信息补丁里的「清除」操作此前根本没落到磁盘:
readerx_book_patch_meta的双层 Option 字段
(简介 / 封面 / 标签 / 分组)按 serde 默认行为解析时,显式null与「字段缺省」都会被解析成
外层None(= 不改动),于是「移除封面」「清空标签」「移出分组 / 移入隐藏」只改了 WebView
内存里的那份,磁盘上的书籍 JSON 原封不动,重启后全部回退。现在用自定义反序列化区分两者:
缺省 = 不改动,显式null= 清除,空数组同样按清除处理;已补回归测试(含「老书籍 JSON
没有新字段也能照常载入、且不被回写」)。 readerx-source auth clear清不掉用auth cookie/auth webkit/auth cdp导入的登录态:
只删了整行的source_sessions/<id>.json,漏了带作用域的profiles/<id>.json,而
call/run/auth show每次都会重新套用后者,表现为「提示已清空、Cookie 却还在」。
现在两处文件都删,报告改为「整行 N + 作用域 M」的文件条数口径(并列出实际删除的文件,
无登录态时明确提示),可重复执行。readerx-source的显示环境预检失效:DISPLAY=(空串,容器里常见)被is_some()当成
「有显示」,于是无头环境一路走到 GTK 初始化才报错。现在空串按「没有显示」处理,
在打开窗口前就给出「用 xvfb-run 或 --auth cdp」的可读提示。readerx-source auth show与sources的信息口径:show现在套用与call/run完全相同的身份(含--profile/--ua),打印的
头与 Cookie 就是这次请求真正会带上的(此前无视--profile的 UA);sources的「登录态 N 条」此前只统计整行文件,用带域名的 Cookie 文件导入后显示为 0,
现在整行与作用域一起统计。
- 采集登录态时不再把书源自带的 User-Agent 复制进登录态文件:它是外部覆盖(
--ua/
身份文件)时才记录。此前一律写入,会在之后盖住书源自身对 UA 的修改。 util.urlJoin两处拼接缺陷:目录地址的末尾斜杠被规范化吃掉(/book/562822/变成
/book/562822,按路由区分的站点直接 404);相对地址带 query 时被重复追加
(/x?q=1→/x?q=1?q=1);协议相对地址(//cdn.example.com/a.js)拼出
https:////cdn…这种非法地址。已补回归测试。