DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

shushuiu /

shushuiu/dsh-bookshelf

Verified

A Douban 想读 bookshelf with covers and ratings, matched against a local ebook folder, with an in-panel EPUB/PDF/TXT reader and public-domain downloads.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@16398fc6

书架(dsh-bookshelf)

DeepSeek Harness 插件:把豆瓣某个用户的「想读」书单变成一个书架页,并把它和你自己目录里的电子书配对,可以在这个面板里直接读;没有的书可以去公版书库下载,或点开正版渠道。

它做什么

  • 豆瓣想读书单:读取 book.douban.com/people/<ID>/wish,列出书名、封面、作者、出版社、年份、评分、ISBN、内容简介,按想读时间排序,支持搜索。
  • 对豆瓣很克制,但会自动补全:刷新一次只发 2 个请求(列表页 + 卡片页拿封面),随后后台每 1.5 秒补一本详情(封面、评分、简介、ISBN),29 本约 1 分钟补齐;面板会自己轮询,不用点。豆瓣对匿名客户端的风控很紧,所以这个队列遇到 302/403 会立刻停手,20 分钟后自动接着补。
  • 封面即继续读:书架上点封面直接进阅读页,并跳回上次读到的章节与页码(存在浏览器 localStorage,按文件记,最多留 120 条);卡片下方的书名/作者区域仍然打开详情页。卡上会出现「续读 第 N 节」的标记。
  • 阅读页按书排版,整本连读:整本书铺成一条连续的双栏流(章节标题只是流里的块),翻到一章末尾会顺势进下一章,页码是全书页码;← → / PageUp PageDown / 滚轮都能翻页(一次滚动手势只翻一页:滚动增量累积到 40px 触发一次,之后同一次手势的余量被锁住,直到滚轮静止 260ms 才解锁,所以触控板的惯性滑不会连翻十几页),右上角的章节下拉退化成「目录跳转」。衬线↔黑体一键切换、字号 13–26px(存 localStorage),段首缩进 2 字、行距 1.95、两端对齐;纯文本单次最多渲染 30 万字。

双语对照(按章翻译)

阅读器右上角有一个 译 开关。打开后,只翻译当前正在读的那一章(微信读书的做法),译文以更小的灰色字排在每段原文下面,方便逐段对照:

  • 翻译由 Harness 自带的 translator 服务完成(匿名 Google / Bing 通道,或在设置里选定的付费通道),插件不自己接任何第三方接口,也不存账号密码

  • 插件自带一条可用通道:宿主直接请求匿名 Google 批量端点 translate_a/t(无需 key),不依赖 Harness 的 translator 服务

    • 一次请求带 最多 20 段 / 5000 字(该端点支持重复 q 参数,返回按段对齐),所以一章通常只有 1 个请求、约 1 秒
    • 不用 translate_a/single:它只收单段 q,突发几个请求就会被 429 限流并跳转错误页
    • 429 会按 700ms / 1.8s / 3.6s 退避重试 3 次;仍被限流则静默 30 秒(期间直接返回「稍后会自动恢复」,不再锤接口),阅读器底部显示原因并支持点击重试
    • 背景一:translator 服务是给 agent 作用域组合的,profile 级插件拿不到(ctx.get('translator') 为 undefined),所以不能用它当唯一通道
    • 背景二:即使拿得到,它的默认通道是 Bing,而 edge.microsoft.com/translate/auth 现在返回 404,默认通道本身就是坏的
    • 因此路由顺序是:能拿到 translator 就先用它(google → 配置通道)→ 否则/失败则走插件内置通道;成功的那条会被记住,不会每段重试
    • 只把当前章的段落文本发给翻译端点,不带任何账号信息;翻译端由 Google 提供
  • 失败时阅读器底部直接显示真实原因(例如 译文失败:TRANSLATION_UNAVAILABLE: google / …(重试)),点一下就重试,不再只给一句笼统的提示

  • 只发哪些内容:当前章的段落,一批最多 18 段 / 9000 字;整本 800 页的书只翻一章,不翻整本

  • 翻页进入新的一章时,才会去请求那一章;把 译 关掉就停止请求

  • 译文有本地缓存($DSH_HOME/storages/bookshelf/translations.json,按「目标语言+原文」的 SHA-1 存),同一段第二次阅读不再走网络

  • 原文是英文则译成中文,原文是中文则译成英文(按字母/汉字比例判断)

  • 只翻译文字:阅读器只把 EPUB 的段落排出来,图片本来就不进正文,所以不存在翻译图片的问题

  • 失败时底部会显示「译文失败,点击重试」,成功部分照常显示

  • 续读靠章节偏移:记录的是「第几章 + 章内第几屏」,换字号后仍然落在同一章同一屏附近,不会跑偏到别的章节。

  • 翻页永远落在栏边界上:栏宽是实测的(拿一段跨栏的正文取 getClientRects() 相邻片段的左边缘差),不是按 clientWidth + gap 推算——Chrome 的栏宽常是小数(实测 709.6667px),按公式累加每页会丢 0.3px,读到几十页后每行开头就被切掉几个字。每次翻页都是从页码反算绝对位置再对齐到实测栏宽,所以 200 页之后仍然不偏;字体换载或滚动停下时都会重新量一次并二次对齐。

  • 不用先配目录就能导入:libraryDir 留空时,导入的书会存到 ~/Documents/书架(自动创建),导入成功立即出现;设置的「书库目录」里还有「用下载目录 / 用文稿目录 / 用桌面」三个快捷键,~/Downloads 这种写法会自动展开成绝对路径。工具栏的「导入本地书」不需要先打开某本书。

  • 可以和对话一起用:装了一个 bookshelf 工具,可以直接在对话里问「我书架上哪本书讲过 X」「读一下《人月神话》第 3 章」——它会检索你本地 EPUB/TXT 的正文并返回原文片段。

  • 本地书库配对:扫描你指定的目录(epub / pdf / txt / mobi / azw3 …),按 ISBN 优先、书名其次和豆瓣书目自动配对。

  • 资料区:和书一样排列:没和豆瓣书目配对上的文件(论文 PDF、说明书、随手丢进来的 TXT)不再隐形,书架下方单独一栏,按修改时间倒序排成封面卡片:PDF 用第一页渲染成封面(pdftoppm,480px 宽,按 2:3 裁切只露页首,所以标题永远在封面上),标题优先取 PDF 自己的 /Title,没有就从第一页正文里挑标题行(跳过作者、邮箱、arXiv 行;识别不了的乱码/模板占位/合同编号一律退回文件名),标题下面一行是「页数 · 大小」。卡片hover 无遮挡,操作在标题下方一行小字:显示 / 重命名 / 删除;区块标题右侧还有「打开文件夹」「导入资料」。封面按文件版本缓存在 $DSH_HOME/storages/bookshelf/covers/papers/,第二次打开不再渲染。默认书库目录是 ~/Documents/书架(面板里直接显示这个路径,「打开文件夹」一键跳过去);从旧版本升级时,第一次扫描会把旧默认目录 ~/.dsh/storages/bookshelf/library 里的文件搬过来(不覆盖同名文件,搬不动就留在原处并在面板上说明)。

    • 重命名只改名字、保留扩展名,会挡掉路径分隔符和开头的 .(否则会被扫描器当成隐藏文件而"消失"),同目录同名会拒绝;重命名后阅读进度跟着走(本地进度按文件 id 存,改名后由面板把旧 id 的记录迁到新 id)。
    • 删除不是真删:文件被移到书库目录下的 .trash/(扫描器会跳过),面板里消失,需要时还能从 Finder 拖回来。
  • 论文 PDF 也能翻译:PDF 阅读页右上角多一个「提取文字」,调用 poppler 的 pdftotext 把文字层抽出来,按页切成章节,换成和 EPUB 一样的双栏阅读/双语对照(只翻当前页,按需请求);「原文 PDF」随时切回原版。两份结果都按文件版本缓存,重新打开不再解析。

    • 整页挤成一段的情况(双栏论文被 pdftotext 压平,一页 4000 字没有空行)会在句号处切成多段,单段上限约 900 字:读起来是段落,翻译时也是合适的单位。
    • 图表不会丢:文本模式右侧默认并排显示当前页的原样扫描图(pdftoppm 渲染,120dpi,按页缓存在 covers/pages/,上限 400 张),跟随翻页自动换页,工具栏「原文页」可收起/展开,选择记在 localStorage。
    • 页面不顶到边框:右栏的灰底上居中放一张整页的「纸」(圆角、细边、投影,按栏高缩放到整页可见),右栏头部有「放大 / 整页」切换(也可以直接点图),放大时按栏宽铺满、上下滚动;窗口窄于 900px 时改成上下堆叠(文本上、原页下 45%)。为什么不做「把图片塞进正文」:论文里的图表大多是矢量绘制,抽出来只能变成低分辨率位图,表头/公式还会被拆散,位置也难对齐;直接展示原页既保真又便宜。文本栏变窄时自动从双栏切单栏。
    • 就算真的收到超长段落(>2000 字),宿主也会在句子边界拆块、逐块翻译再拼回去,不会因为一段太长就让整页翻译失败;只有整批都拿不到译文才算失败,个别段落缺译文时底部显示「N 段未译」并保留其余译文。
  • 面板内阅读:EPUB 在这里解析成章节段落直接渲染(跟随明暗主题);TXT 自动识别 UTF-8 / GB18030;PDF 用浏览器内置阅读器。

  • 公版下载:从 Project Gutenberg 搜同名条目并直接下载 EPUB 到书库目录;Open Library 提供借阅 / 购买入口。

  • 正版渠道:微信读书、豆瓣阅读、京东、当当、Open Library 的搜索链接。

  • 导入自己的文件:选中一本书 → 导入本地文件,文件会存进书库目录并自动配对(适合你已经买过的电子书)。同名文件不会覆盖已有的,会存成 书名 (2).epub。

  • 不太可能拖垮 Host:解析上限 48MB(超了给提示而不是读进内存)、PDF 走 HTTP Range 分段读取、封面只允许 image/* 回源并带 nosniff。

发布到插件市场(可选)

除了发 zip,这个包也能像市场里的插件那样被安装:DSH 的「发现」页读取的是索引文件(format 1),索引里写「包名 + 精确版本 + 来源(npm 或 Git)」。

已经准备好的东西:

  • package.json 去掉了 private、补了 keywords,npm pack 只发 11 个文件(80 KB,无依赖、无构建脚本)
  • publish/build-index.mjs:读 package.json 和 locale/*.json 生成 publish/index.json(中英标题/描述、内联图标、版本、时间戳、engines.dsh),支持 --git <repo> <commit> 走 Git 路线
  • publish/index.json:已用 DSH 自己的校验器(@deepseek-ai/dsh-plugin-market-index)验证通过,安装目标解析为 dsh-bookshelf@1.0.0

四条路:

  • npm:npm publish + 托管 publish/index.json,别人加索引源即可一键装(要 npm 账号)
  • Git:索引里钉 commit,source: { type: "git" }(不要 npm 账号)
  • 官方目录:向 awesome-dsh-plugin 提 PR,一个文件 data/plugins/<owner>__<repo>.yml(模板见 publish/catalog-entry.yml,图标已生成 assets/icon-180.png)。这条路不发 npm 也能收录,但仓库要创建满 1 天、加 dsh-plugin topic;而「发现」里的市场索引只收 npm 版本,GitHub-only 条目只在列表页显示安装命令。
  • 自己留档:zip + install_bundle 指向解压目录

别人那边是「插件 → 发现 → 管理索引源… → 填索引 URL → 安装」。逐步操作见仓库里的 publish/README.md(这个目录只是发布工具,不进 npm 包)。

分享给别人

这个插件是一个标准的 Harness bundle 目录(package.json + cordis.patch.yml + 宿主 index.js + 客户端 client.js + lib/ + locale/ + icon.svg),整个目录拷走就能装。

打包

cd ~/deepseek/workspace/书架
zip -r dsh-bookshelf.zip plugin -x '*.DS_Store' -x '*/node_modules/*'

对方安装

  1. 解压到任意位置,例如 ~/dsh-bookshelf(目录里应当直接看到 package.json、index.js、client.js)。
  2. 在 Harness 里安装这个本地 bundle:把解压后的目录绝对路径作为 target 调 plugin_manager 的 install_bundle(它负责写 profile 的依赖与 patch,不要手改 ~/.dsh/profiles/*)。
  3. 重启 Harness:宿主端(路由/后台任务)只在启动时加载,客户端只刷新页面即可。
  4. 侧边栏出现「书架」后,先点「设置」填自己的豆瓣 ID(豆瓣个人主页地址 /people/<ID>/wish 里那串数字)——插件不预置任何人的 ID。
  5. 书库目录默认是 ~/Documents/书架,导入或把电子书丢进去即可;也可以在设置里换成任意目录。

对方机器需要什么

  • Harness ≥ 0.2.0、Node ≥ 22(package.json 的 engines 会挡住不满足的版本)。
  • 「在 Finder 中显示」用 macOS 的 open,其他系统会提示不支持,不影响阅读。
  • 论文 PDF 的封面缩略图和文字提取都用 poppler:brew install poppler(pdftoppm / pdftotext / pdfinfo 会依次在 /opt/homebrew/bin、/usr/local/bin、/usr/bin 里找——App 从 Finder 启动时 PATH 里没有 Homebrew)。找不到时面板会给出这句安装提示,卡片退回纯文字封面,PDF 仍可原版阅读,只是不能显示第一页/转文本。
  • 翻译走匿名 Google 端点,无需 key,但需要能访问 translate.googleapis.com。

注意

  • 每个人的豆瓣 ID、书库目录、翻译缓存都存在各自的 ~/.dsh/storages/bookshelf/ 下,插件目录本身不含个人数据,可以直接分享。
  • 不要把自己的 ~/.dsh/storages/bookshelf/ 一起打包:那里面有你的书单缓存、翻译缓存和阅读记录。

配置

配置写在 cordis.patch.yml 的 row 里(也可以直接用界面里的 书架 → 设置):

字段 含义 默认
doubanUserId 豆瓣用户 ID(个人主页 /people/ 后面那段) 空(首次运行时在面板里填)
libraryDir 你自己的电子书目录 空 → 用 ~/Documents/书架

界面里改的偏好保存在 $DSH_HOME/storages/bookshelf/prefs.json,优先级高于 patch 里的配置;想回到 patch 的值,删掉这个文件即可。

数据与缓存

全部在 $DSH_HOME/storages/bookshelf/:

  • shelf.json:豆瓣书目 + 你打开过的书的详情缓存(没打开过的书不抓详情)
  • prefs.json:用户在界面里保存的偏好
  • covers/<subjectId>.img:封面缓存(豆瓣图床需要 Referer,由 Host 代取)
  • 书库目录本身不复制、不移动,只是索引并原样读取。

被豆瓣限流时

Douban 对匿名请求有频控。命中后 Host 会返回 豆瓣要求人机验证(HTTP 302) 或 HTTP 403,插件会:

  1. 保留已有书架(不清空),面板顶部显示提示与「重试」;
  2. 20 分钟内不再重试,避免把封禁时间拖长;
  3. 新书详情按需读取,不影响已经在书架上的书。

等一会儿再点「刷新」即可。

不做什么

这个插件不接入 Z-Library 之类的影子书库:那些站点分发的多数是未经授权的版权内容,自动化下载它们属于侵权。它只读取你自己的文件,以及公版书库(Project Gutenberg / Open Library)的合法内容,其余书目给出正版渠道入口。

结构

plugin/
├── index.js            Host 半:/api/bookshelf/* 路由、缓存、后台抓详情
├── client.js           浏览器半:书架页、详情、阅读器、设置
├── lib/douban.js       豆瓣页面抓取与解析
├── lib/library.js      书目扫描、配对、EPUB(ZIP) 解析、纯文本解码
├── lib/sources.js      Gutenberg / Open Library 检索与下载、正版链接
├── cordis.patch.yml    Loader row(配置入口)
├── locale/{zh,en}.json 插件管理器里的标题与描述
└── icon.svg            插件图标

开发提示

安装方式(已在当前 profile 中完成):

plugin_manager install_bundle  target=<这个目录的绝对路径>

改完 client.js / index.js 后:改 Host 半需要重启 Harness;只改浏览器半刷新页面即可(有 client HMR 时更省事)。本地没有打包/构建步骤,源码即产物。

—/ 5

No ratings yet

Verified DSH bundle

Commit 16398fc6a2b8

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout