检查、构建与拆解 EPUB 电子书结构
30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add yuehancn/dsh-tool-epub该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · tool
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 33 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件
查看扫描证据(共 8 处,展示前 8 条)
- 写入本地文件lib/index.js:623
await mkdir(outputDir, { recursive: true }); - 读取本地文件lib/index.js:637
const info = await stat(source).catch(() => undefined); - 读取本地文件lib/index.js:641
const buffer = await readFile(source); - 读取本地文件lib/index.js:1039
const bytes = await readFile(args.coverImagePath).catch(() => { - 写入本地文件lib/index.js:1102
await writeFile(destination, archive); - 写入本地文件lib/index.js:1190
await mkdir(target, { recursive: true }); - 写入本地文件lib/index.js:1217
await writeFile(writtenPath, contents, "utf8"); - 写入本地文件lib/index.js:1230
await writeFile(indexPath, `<!DOCTYPE html>\n<html lang="${escapeXml(book.pkg.meta.language ?? "en")}">\n<head>\n<meta charset="utf-8"/>\n<title>${esc
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/cordis@deepseek-ai/dsh-tools给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 检查EPUB结构
- 构建EPUB电子书
- 拆解EPUB电子书
README
由 DeepSeek 最新模型翻译生成dsh-tool-epub
在 dsh 里读、造、拆 EPUB 电子书 —— 先把一本书拆开看清楚它的元数据、阅读顺序和目录到底对不对,再从章节拼出一本合规的书,或者把一本已有的书拆回逐章的 HTML。
给 DeepSeek Harness 用的自建工具插件:ZIP 读写器和 XML 扫描器都是自己写的,不装 epubjs、不装 jszip,在一台干净的机器上就能用。
Compatibility: built and tested against dsh 0.2.0-rc.2 (preview).
The apply(ctx) plugin spec is stable; verify against your own dsh version if newer.
一行安装
dsh plugin --profile desktop add github:yuehancn/dsh-tool-epub
支持的 profile:desktop(桌面版)/ web(Web 版)。装完重启 dsh 即可用。
为什么需要它
EPUB 不是「一个 Word 文档换个后缀」,它是一个有强制骨架的 ZIP: mimetype 必须是第一个条目且不压缩存储,META-INF/container.xml 指向一个 OPF 包文档,包文档里再声明 spine(阅读顺序)和 nav(目录)。
展开完整文档(README 剩余 1 节)
dsh-tool-epub
在 dsh 里读、造、拆 EPUB 电子书 —— 先把一本书拆开看清楚它的元数据、阅读顺序和目录到底对不对,再从章节拼出一本合规的书,或者把一本已有的书拆回逐章的 HTML。
给 DeepSeek Harness 用的自建工具插件:ZIP 读写器和 XML 扫描器都是自己写的,不装 epubjs、不装 jszip,在一台干净的机器上就能用。
Compatibility: built and tested against dsh 0.2.0-rc.2 (preview).
The apply(ctx) plugin spec is stable; verify against your own dsh version if newer.
一行安装
dsh plugin --profile desktop add github:yuehancn/dsh-tool-epub
支持的 profile:desktop(桌面版)/ web(Web 版)。装完重启 dsh 即可用。
为什么需要它
EPUB 不是「一个 Word 文档换个后缀」,它是一个有强制骨架的 ZIP: mimetype 必须是第一个条目且不压缩存储,META-INF/container.xml 指向一个 OPF 包文档,包文档里再声明 spine(阅读顺序)和 nav(目录)。
缺一环,阅读器就直接拒收。
真正容易出错的是判断而不是管道:
- 一本书的章节被声明了两次 —— spine 一次、nav 一次。它们比你想的更常不一致,而「书坏了」绝大多数时候就是这两处对不上。
- 把一个大 HTML 拆成章节要靠标题层级猜边界。猜错了是静默的:书还是能打开,只是内容错位了。
- mimetype 要是被压缩了(绝大多数 ZIP 库的默认行为),严格的阅读器直接拒绝整本书。
这个插件把「看清楚」和「动手」分开:
epub_inspect(path="book.epub") → 元数据 / 阅读顺序 / 目录 / 逐章字数 / 结构问题
epub_build(title="…", html="…") → 合规的 EPUB 3,并告诉你用了哪条拆分规则
关键设计:epub_inspect 是独立的工具,不是 epub_build 的前置步骤。
因为「这本书现在长什么样」和「我要造一本什么样的书」是两件事 ——先拿到体检报告,再决定动不动手。
四个工具
epub_status
报这里有哪些能力:读、造、拆都内置,externalDependency 一栏明确写着 none。另外报出章节数上限和单次调用预算。
长任务前先问一句,避免做到一半才发现超限。
epub_inspect(path)
只读不写。不解包就能拿到:
| 返回项 | 说明 |
| title / creator / language / identifier | OPF 里的元数据 |
| epubVersion | 从 package 元素的 version 属性读,通常是 3.0 |
| entryCount | ZIP 里的条目数 |
| chapters[] | 按 spine 顺序(不是文件名顺序)排好的逐章记录,含 words 和 bytes |
| toc[] | nav 文档里的目录树 |
| totalWords | 全书字数 |
| images[] | 书里引用的图片资源 |
| warnings[] | 结构问题清单 |
warnings 是重点,它会把下面这些明确点出来而不是含糊带过:
- mimetype 被压缩了,或者不是第一个条目
- spine 里声明了某个文件,但 ZIP 里没有这个文件
- 目录(nav)里的 href 指向一个不存在的路径
- 某个 spine 文档从目录里根本走不到(读者会看得见但翻不到)
- linear="no" 的文档(不算正文顺序)
- manifest 里没有 properties="nav" 的条目 → 这本书没有目录
epub_build(outputName, …)
造一本书。两条路径,二选一:
| 方式 | 参数 | 用途 |
| 显式章节 | chapters: [{title, html}, …] | 边界已经知道了 |
| 自动推断 | html: "<一个大 HTML>" + splitLevel | 边界要从标题层级推 |
| 参数 | 说明 |
| outputName | 必填,产出文件名,如 my-book.epub |
| title / language / author / identifier | 元数据;identifier 不给就按内容生成一个稳定的 URN |
| splitLevel | auto(默认,取最浅的标题层级)或 h1–h6 |
| coverImagePath | 封面图路径 |
返回里带 splitRule 字段 —— 明确告诉你边界是怎么切出来的 (例如 split on 3 h2 headings)。推断出来的东西必须可解释,否则错了没人知道。第 0 章以外每一章的字数都从生成的 XHTML 里数(包含注入的 ),所以 build 和 inspect 数出来的字数是对得上的。
epub_split(path, …)
把一本书拆回逐章文件,再写一份清单。
| 参数 | 说明 |
| path | 必填,.epub 路径 |
| outputDir | 输出目录,默认走配置 |
| format | html(保真)或 txt(纯文本),默认 html |
| prefix | 文件名前缀,默认用书名/文件名生成的 slug(支持中文) |
| includeToc | 是否额外写一份 index.html 目录页 |
txt 模式和字数统计走的是同一个标签剥离器,所以两者对「正文是什么」永远不会打架。章节序号按 spine 顺序补零到三位(-001、-002),保证文件名的字典序和阅读顺序一致。
配置
~/.dsh/profiles/<profile>/cordis.patch.yml
- id: tool-epub
config:
outputDir: C:/Users/you/Documents/epub-output
defaultTitle: Untitled
defaultLanguage: zh-CN
defaultAuthor: 你的名字 maxChapters: 500 timeoutMs: 60000
| 字段 | 默认 | 说明 |
| outputDir | epub-output | 产物目录 |
| defaultTitle | Untitled | 调用方不给标题时的兜底 |
| defaultLanguage | en | BCP 47 语言标签,中文书改成 zh-CN |
| defaultAuthor | "" | 写进生成书的作者 |
| maxChapters | 500 | 超过就拒绝构建(防止一次请求把内存吃光) |
| timeoutMs | 60000 | 单次调用预算 |
| status/inspect/build/split | true | 按需关掉某个工具 |
安全说明
- Zip-slip 防护:书里的 href 一律过 resolvePath 归一化, ../../etc/passwd 这类路径会被夹在压缩包根目录内,逃不出去。
- 产物文件名同样净化:outputName 里的 ../ 会被剥掉再做替换,不会往产物目录外面写文件。
- 不联网、不执行书里的任何东西 —— 只解析文本和 XML。
- 确定性:固定 DOS 时间戳 + 按内容推导的 identifier + 固定的 dcterms:modified。同一份输入永远产出字节一致的书,所以两次构建可以直接 diff。
实现说明:两个必须自己写的地方
EPUB 是 ZIP + OCF + OPF + XHTML,全是开放标准,但细节全是坑。
这里没有引第三方库,所以下面这几件事必须做对:
1. mimetype 必须是第一个条目,且 STORED(不压缩)。
大多数 ZIP 库默认压缩所有条目 —— 那是错的,严格的阅读器会直接拒收整本书。
所以 writeZip 里 store: true 走的是方法 0 的独立分支。
2. 读 ZIP 时必须用「本地头」的长度,不能用目录里的。
中央目录和本地头各自记录了自己的 name/extra 长度,两者可以不一样。
用目录里的长度去算数据偏移,遇到带额外字段的条目就会读出乱码。
3. EOCD 签名要从后往前扫,且窗口要留出 65535 字节的注释空间。
因为 ZIP 注释里可以包含 0x06054b50 这四个字节,从前往后扫会命中假签名。
4. 目录里指向的 href 是相对路径,要相对nav 文档自己的位置解析,不是相对包根、也不是相对当前工作目录。
验证方式:_test/verify-epub.py 用两条独立路径校验 —— Python 标准库 zipfile 检查容器合法性(mimetype 首位、STORED、CRC 全对,
testzip() 能跑通),再用 ebooklib 做一次真正的 EPUB 解析
(元数据、spine、目录)。
这个双路校验当场抓到了一个我自己的 bug:我的校验脚本没做 HTML 实体解码,把书里的 & 和 & 当成不相等,误报标题错误 —— 书是对的,校验器是错的。
自己写的校验器犯的错,只有另一个实现才能发现。
当前结果:4/4 本书通过校验(含中文书、实体转义书、单章书、多章书)。
跑测试
mkdir -p node_modules/@deepseek-ai
cp -r "$HOME/.dsh/profiles/desktop/node_modules/@deepseek-ai/." node_modules/@deepseek-ai/
node _test/run-all.mjs
三个套件,356 条断言全绿(对着真实 @deepseek-ai/dsh-tools 跑,不 mock):
| 套件 | 断言 | 内容 |
| test-logic.mjs | 168 | Config 默认值与覆盖、注册开关、schema 归一化、XML 转义、标签剥离(含多行 )、路径解析(含 zip-slip 夹取)、标题推断、字数统计(CJK 逐字计)、字节嗅探、CRC-32 已知向量("123456789" = 0xcbf43926)、ZIP 写出布局与确定性、ZIP 读入健壮性、OPF/nav 解析 |
| test-integration.mjs | 125 | status、显式章节构建、自动推断构建、8 条构建错误路径、章节上限、封面嵌入、真书 inspect、4 条 inspect 错误路径、5 个结构问题 fixture(mimetype 被压缩 / spine 文件缺失 / 目录悬空 / 孤儿章节 / linear="no")、split 的 html 与 txt 两种模式、自定义输出目录 + 中文 slug、split 错误路径、确定性、注册开关 |
| test-e2e.mjs | 63 | 构建真书 → 断言结构自洽(assertCoherent)→ XHTML 良构性 → 中文往返 → 实体转义 → 产物文件名净化(zip-slip)→ 封面 → split 能还原正文 → build 与 split 对字数的口径一致 |
再跑一次独立校验(需要 Python + ebooklib):
python _test/verify-epub.py _test/samples
test-e2e.mjs 会把样本写到 _test/samples/,正是给这个脚本消费的 ——所以 e2e 是「自己验自己」,而这个脚本是「别人验自己」。
许可
MIT预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
yuehancn/dsh-tool-epub
检查、构建与拆解 EPUB 电子书结构
相关插件 · 工具 / 效率
破甲管控台kingcharleslzy-ai/dsh-purge
dsh-purge Community · DeepSeek Harness 社区维护版:宿主兼容、状态诊断、提示词管理、更新与本机备份回滚。基于小杨 / YuJunZhiXue 的 dsh-purge。
★ 4,330已汉化DeepSeek 客户端
74.5
网文长卷lingfengQAQ/webnovel-writer
基于 Claude Code 的长篇网文辅助创作系统,解决 AI 写作中的「遗忘」和「幻觉」问题,支持 200…
★ 7,636已汉化DSH
72.2
多智能体协作HKUDS/AutoSwarm
AutoSwarm: Unify Your Agents. Unify Your Context (Codex · Claude Code · DeepSeek Harness · Muse · Grok · WorkBuddy· nanobot · and more)
★ 2,349已汉化DSH
71.6
Vox 拼贴视频生成Alisa0808/vox-director
🎬 Vox Director拼贴动效导演
★ 2,129已汉化DSH
71.4
Agent 演化引擎Q00/ouroboros
智能体 OS:智能体自己变聪明,我们只守住底线:面试准入、分阶段评估、带预算的进化循环。MCP 服务器,14 种运行时:Claude Code、Codex CLI、Gemini CLI、OpenCode、Copilot、Kiro 等。
★ 6,311已汉化DSH
71.4
智能体运行框架路由HarnessRouter/harnessrouter
智能体 harness 的统一接口。
★ 3,984已汉化DSH
71.3
这是你的仓库?认领条目