Skip to content

v0.1.5

Choose a tag to compare

@github-actions github-actions released this 19 Sep 06:07
· 36 commits to main since this release

下载与安装

本页为 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,key 32 字节、iv 12 字节,未给 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)与书源
    持久化抽成独立 crate src-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… 这种非法地址。已补回归测试。