dsh-weread-gather 1.0.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/README.md +95 -14
  2. package/client.js +2600 -1981
  3. package/index.js +706 -11
  4. package/locale/zh.json +2 -2
  5. package/package.json +7 -5
package/README.md CHANGED
@@ -1,12 +1,25 @@
1
- # 书房 · dsh-weread-gather
1
+ # 数字书房 · dsh-weread-gather
2
2
 
3
- 把微信读书的账号数据同步到本地,在 DSH 侧栏里变成一个能翻、能搜、能回看的私人书房:
4
- 书架按大类陈列,每本书能展开自己的划线笔记与想法,总览页按年份联动展示阅读热力图与表盘。
3
+ 把微信读书的账号数据同步到本地,在 DSH 侧栏里变成一个能翻、能搜、能带走的私人书房:
4
+ 书架按大类陈列,每本书能展开自己的划线笔记与想法,总览页按年份联动展示阅读热力图与表盘,
5
+ 选中的内容可以导出成带 front-matter 的 Markdown,整份书房也能一键备份与恢复。
5
6
 
6
- - 侧栏入口「书房」(`sidebar.panellist`,order 26),主面板同 key 打开
7
+ - 侧栏入口「数字书房」(`sidebar.panellist`,order 26),主面板同 key 打开
7
8
  - 宿主侧持有 API Key 与全部数据,页面只读自己的同源 JSON 接口
8
9
  - 不写回微读,不做任何上报;数据只落在本机 `$DSH_HOME/weread-gather/`
9
10
 
11
+ ## 界面
12
+
13
+ 书架:10 个大类、每行 10 本,按年份与关键词筛选。
14
+
15
+ ![书架](assets/screenshot-1-shelf.png)
16
+
17
+ 总览:置顶的年度阅读热力图,KPI、月度分布与表盘都跟着右上角的年份切。
18
+
19
+ 配色是浅绿纸感(页面 `#F6F8F7`、强调绿 `#4F9274`、热力图五档 `#EDF2EF → #438765`),通过重映射 `--dsw-alias-*` 跟随宿主的亮/暗主题。
20
+
21
+ ![总览](assets/screenshot-2-overview.png)
22
+
10
23
  ## 安装
11
24
 
12
25
  要求 **DSH >= 0.2.2-rc.1**(Web 端),插件是纯 JS、无构建步骤,装完重启即生效。
@@ -25,7 +38,7 @@ dsh plugin --profile <你的 profile> add github:fountainhead-gq/dsh-weread-gath
25
38
  dsh plugin --profile <profile> add link:D:/path/to/dsh-weread-gather
26
39
  ```
27
40
 
28
- 装好后重启 DSH,侧栏出现「书房」。首次打开会让你填 **微信读书 Agent 网关的 API Key**(`wrk-` 开头):
41
+ 装好后重启 DSH,侧栏出现「数字书房」。首次打开会让你填 **微信读书 Agent 网关的 API Key**(`wrk-` 开头):
29
42
 
30
43
  - 优先读进程环境变量 `WEREAD_API_KEY`(DSH 应用进程里配好就不用手填)
31
44
  - 否则在「书房 → 设置」里粘贴,存到本机 `config.json`
@@ -39,9 +52,11 @@ dsh plugin --profile <profile> add link:D:/path/to/dsh-weread-gather
39
52
  | 书架 | 全部书籍按 **10 个大类** 陈列(文学/其他/艺术/科技/成长心理/历史/生活/社科/经管理财/哲学宗教),**每行 10 本**、窄屏自动降为 8/6/4 本;支持按书名/作者/分类搜索、**按最后阅读年份筛选**(也可直接在搜索框输入「2024」)、按最近阅读/笔记数/书名排序,卡片显示封面、分类、评分、出版社、出版年、笔记条数 |
40
53
  | 笔记 | 全部划线按时间倒流,**每页 24 条翻页**、按书筛选、关键词搜索、**单条复制**、**点卡片跳到该书详情** |
41
54
  | 想法 | 想法卡片墙(瀑布流式多列),**每页 20 条翻页**、按书筛选、关键词搜索、**单条复制**、**点卡片跳到该书详情(自动切到想法页)**、星级与章节 |
42
- | 书籍详情 | 封面、作者、大类 + 细分分类/读完/评分/出版社/出版年徽章、简介、该书全部划线 + 想法(每页 24 条翻页、可复制)、一键在微读打开 |
43
- | 全局搜索 | 顶栏搜索同时搜书名、作者、分类、划线正文、想法正文,结果分组呈现 |
44
- | 设置 | 展示 Key 来源(环境变量或手填)、书架上限、每本书同步笔记条数、单次补全书籍信息上限、清空数据 |
55
+ | 书籍详情 | 封面、作者、大类 + 细分分类/读完/评分/出版社/出版年徽章、简介、该书全部划线 + 想法(每页 24 条翻页、可复制)、一键在微读打开、**「导出这本书」直接生成整本 Markdown** |
56
+ | 全局搜索 | 顶栏搜索同时搜书名、作者、分类、划线正文、想法正文;结果按**书籍 / 划线 / 想法**分类切换,命中的关键词在卡片里高亮 |
57
+ | 导出 Markdown | 划线/想法卡片左上角圆圈勾选(悬停出现)→「导出所选」;也可「导出全部结果」或只导出某一本。导出面板可关掉原文/想法/书籍信息/章节标题/星级/微读链接,**先预览前 30 行**,再选择复制、下载 `.md` 或直接保存到导出目录(默认 `$DSH_HOME/weread-gather/exports/`,同名自动加 `-2` 序号) |
58
+ | 同步提示 | 同步进行中在页面顶部显示进度条,完成后停留 8 秒自动消失,也可以随时点右侧 `×` 关掉;数据没变化时同步只花 2~3 秒 |
59
+ | 设置 | 展示 Key 来源(环境变量或手填)、书架上限、每本书同步笔记条数、单次补全书籍信息上限、**Markdown 导出目录**、**下载备份 / 导入备份(两步确认,导入前自动另存当前数据)**、清空数据 |
45
60
 
46
61
  每一项(书架/笔记/想法/搜索/书籍详情内的列表)都有自己的搜索框;顶栏搜索是跨模块的全局搜索。
47
62
 
@@ -49,6 +64,49 @@ dsh plugin --profile <profile> add link:D:/path/to/dsh-weread-gather
49
64
 
50
65
  微读自己的分类是几十个叶子(`文学-文学鉴赏` 这种),直接铺成 chip 没法用。宿主侧按「-」前的上级前缀把每本书折进一个书架大类,`book.group` 存大类,`book.category` 保留叶子值(详情页和搜索仍用得到)。折叠表在 `index.js` 的 `CATEGORY_GROUPS`,改一行就能调;分组是**加载时派生**的,不需要重新同步。
51
66
 
67
+ ## 导出成什么样
68
+
69
+ 导出的 Markdown 是「能进别人工具链」的格式,不只是一坨文字:
70
+
71
+ ```markdown
72
+ ---
73
+ title: "顾随诗词讲记"
74
+ author: "顾随"
75
+ bookId: "3300220853"
76
+ source: "weread"
77
+ sourceUrl: "weread://reading?bId=3300220853"
78
+ category: "文学-文学鉴赏"
79
+ exportedAt: "2026-11-08T19:10:00+08:00"
80
+ excerptCount: 1
81
+ thoughtCount: 1
82
+ containsAI: false
83
+ exportId: "weread:book/3300220853"
84
+ contentHash: "sha256:ece983…"
85
+ tags: ["微信读书"]
86
+ ---
87
+
88
+ # 顾随诗词讲记
89
+
90
+ *顾随*
91
+
92
+ ## 开场白——诗之感发作用
93
+
94
+ > 诗根本不是教训人的,是在感动人,是“推”、是“化”——道理、意思不足以征服人。
95
+ > — 2026-07-28 12:52
96
+
97
+ **想法**(2026-07-28 12:55)
98
+
99
+ 诗的首要目的不是教育人的,是来感动人的,最终让人意悦心服。
100
+ ```
101
+
102
+ 几条约定,都是为了以后能被机器稳定识别:
103
+
104
+ - **块引用 `>` 只用于原文**(作者行用 `*斜体*`,不占用引用),原文末行固定是 `> — 划线时间`
105
+ - **`**想法**(时间)` 只用于你自己写的内容**;AI 生成内容将来会带独立标记,`containsAI` 就是给这件事留的开关(现在恒为 `false`)
106
+ - 字段有就写、没有就省略正文那一行;front-matter 里缺值写 `""`,**绝不猜、不写「未知」**
107
+ - `exportId` 是这条导出在书房里的身份(`weread:book/<bookId>` 或批量 `weread:selection/<hash>`),`contentHash` 是内容指纹(工时不变、换时间导出也不变),为 1.3.0 的「更新已有笔记」做准备
108
+ - 批量导出时一个文件里多本书,每本以 `# 《书名》` 开头、彼此用 `---` 分隔
109
+
52
110
  ## 数据从哪来
53
111
 
54
112
  走微信读书的 Agent 网关(`https://i.weread.qq.com/api/agent/gateway`),全部只读:
@@ -66,14 +124,28 @@ dsh plugin --profile <profile> add link:D:/path/to/dsh-weread-gather
66
124
 
67
125
  这两类结果缓存在 `heat.json`:**已过年份永久缓存**,当年缓存 30 分钟后过期重取,所以切年份几乎瞬时。
68
126
 
127
+ ### 同步是增量的
128
+
129
+ 每次同步先读 `sync-state.json` 这本账,只处理「真的变了」的书:
130
+
131
+ - 书架、分类、笔记本清单每次都读(清单约 5 个请求,且它是未在书架前 400 本里的书的主要来源,所以不能提前停)
132
+ - 四套阅读统计 **6 小时**内直接复用,不重新请求
133
+ - 每本书按 `lastNoteTime / noteCount / reviewCount` 三项比对:没变化的书**不重拉划线**,旧划线/想法直接沿用
134
+ - 单次最多细读 `maxBooks` 本(默认 60);账本会记下还有多少本从未细读过(`pending`),需要补齐时在设置里点「强制全量重建」
135
+
136
+ 实测:本机 779 本 / 694 条划线的库,首次全量约 8.4 秒,之后没有变化的同步 **2~3 秒**(`更新 0 本 · 跳过 470 本`)。
137
+
69
138
  ## 数据落点与隐私
70
139
 
71
140
  ```
72
141
  $DSH_HOME/weread-gather/
73
- config.json 手填的 apiKey、maxBooks、notesPerBook、metaLimit
142
+ config.json 手填的 apiKey、maxBooks、notesPerBook、metaLimit、exportDir
74
143
  data.json 书架 + 分类 + 划线 + 想法 + 阅读统计
75
144
  meta.json 每本书的元信息缓存(bookId -> 分类/简介/出版社/出版年/评分)
76
145
  heat.json 逐年热力图与年度聚合缓存({ "2025": { v, at, days, annual } })
146
+ sync-state.json 同步账本(上次同步时间、每本书的计数与是否细读过、统计缓存时间)
147
+ exports/ Markdown 导出(默认目录,可在设置里改)
148
+ backups/ 导入备份前自动留存的旧数据(<时间戳>/ 五个 JSON)
77
149
  ```
78
150
 
79
151
  - 所有数据都在这一个目录里,插件不写别处;`data.json` 只在同步时整体重写,页面读内存副本
@@ -87,10 +159,15 @@ $DSH_HOME/weread-gather/
87
159
 
88
160
  | 方法 | 路径 | 作用 |
89
161
  | --- | --- | --- |
90
- | GET | `/state` | 是否已配置、Key 掩码、数据量、同步进度、各项上限 |
162
+ | GET | `/state` | 是否已配置、Key 掩码、版本、数据量、同步进度、各项上限、导出目录 |
91
163
  | GET | `/data` | 整份书房数据(含 `years`:可选的年份及各自总时长) |
92
164
  | GET | `/heat?year=2026` | 该年逐日阅读秒数 `days` + 年度聚合 `annual`(带缓存,见上) |
93
- | POST | `/sync` | 触发一次同步(异步,立刻返回进度;用 `/state` 或 `/sync` 轮询) |
165
+ | POST | `/export` | `{ selector, options, mode }`:`selector` 用 `bookIds` / `noteIds` / `thoughtIds`,`mode: 'preview'` 只回 Markdown、`'file'` 落盘;**空 selector 会报错,不会导出全部** |
166
+ | GET | `/export/dir` | 当前导出目录与是否已存在 |
167
+ | POST | `/export/dir` | 改导出目录(会先写探针文件确认可写,失败回滚) |
168
+ | GET | `/backup` | 一次性备份包 `{ format, files: { config/data/meta/heat/sync-state } }`,`config.json` 已剔除 apiKey |
169
+ | POST | `/restore` | `{ bundle }`:校验后先复制旧数据到 `backups/<时间戳>/`,再写回;config 合并时保留本机 apiKey |
170
+ | POST | `/sync` | 触发一次同步(异步,立刻返回进度;用 `/state` 或 `/sync` 轮询)。`{ "full": true }` = 忽略账本强制全量重建 |
94
171
  | POST | `/config` | 改 apiKey / maxBooks / notesPerBook / metaLimit |
95
172
  | POST | `/reset` | 清掉手填的 Key |
96
173
 
@@ -101,15 +178,19 @@ $DSH_HOME/weread-gather/
101
178
  ```bash
102
179
  node --check index.js && node --check client.js # 语法
103
180
  node scripts/check.cjs # 组件引用 / locale 键对称 / 宿主-客户端契约 / 包身份一致性
104
- node scripts/render-smoke.cjs # 用极简 hooks 运行时把 10 种视图各渲染一遍
181
+ node scripts/render-smoke.cjs # 用极简 hooks 运行时把 11 种视图各渲染一遍
182
+ node scripts/export-selftest.cjs # 45 项 Markdown 导出断言(front-matter、哈希、批量、隔离)
183
+ node scripts/sync-selftest.cjs # 10 项增量同步断言(重拉判定、账本搬运),不联网
105
184
  ```
106
185
 
107
- 两个脚本都不依赖浏览器:`render-smoke.cjs` 自建了极简 hooks 运行时,把 `client.js` 放进 `vm` 里逐个视图渲染,断言不会退化成加载态、笔记/想法必须带分页与复制控件。它默认读 `~/.dsh/weread-gather/data.json`,可用 `WEREAD_DATA` 指到别的数据文件(需要一个已经同步过的数据集)。
186
+ 三个脚本都不依赖浏览器:`render-smoke.cjs` 自建了极简 hooks 运行时,把 `client.js` 放进 `vm` 里逐个视图渲染,断言不会退化成加载态、笔记/想法必须带分页与复制控件;`export-selftest.cjs` 直接 `import` 宿主的 `__internals`,拿真实 `data.json` 跑导出断言。两者默认读 `~/.dsh/weread-gather/data.json`,可用 `WEREAD_DATA` 指到别的数据文件(需要一个已经同步过的数据集)。
108
187
 
109
188
  ## 已知限制
110
189
 
111
190
  - 「其他」桶里的书是微读自己的数据缺口:多半是 `CB_` 前缀的导入文档 / PDF,`/book/info` 也不返回分类,无法自动归类。
112
- - 笔记同步按「最近有笔记的 N 本书」(默认 60 本)展开,不是全量;把 `maxBooks` 调大可多抓,代价是请求数线性增长。
191
+ - 笔记同步按「最近有笔记的 N 本书」(默认 60 本)展开,不是全量;账本会记下还有多少本从未细读过(同步结果里的 `pending`)。想补齐就在设置里点「强制全量重建」,或把 `maxBooks` 调大(代价是请求数线性增长)。
192
+ - 增量同步依赖 `sync-state.json` 这本账;删掉它不会丢数据,但下一次同步会按全量走一遍。
193
+ - 主题跟随宿主亮/暗主题的方式是重映射 `--dsw-alias-*` token;深色模式下纸感配色会被压暗,观感与浅色模式不同。
113
194
  - `/book/info` 实测不返回字数(文档有写、回包里没有),所以卡片上用的是出版年而不是字数。
114
195
  - 微读的 `/readdata/detail` 只在 `mode: overall` 时给 24 小时时段分布,年/月/周都返回空数组,所以「阅读时段」那张图只能展示累计值,不随年份切换(标题里有标注)。
115
196
  - 切到往年后,本周/本月两张卡会隐藏——它们本来就是「当前」的概念;往年的信息由年度 KPI 与月度分布承载。