已读完的书挪进「书籍」,抓取上限去掉(8 -> 整架 26 本)

抓取范围
- TOP_N 默认从 8 改成 0=不限制。原来那个 8 是我拍的「最近动过的前 8 本」,
  不是接口限制;整架 26 本也就 1+26 个请求,完全够用
- --top N 仍可用,想只抓最近几本时加

分组调整
- 「最近在读」只留 progress < 100 的(20 本),进度条不再出现 100%
- 新增「书籍」区底部的「微信读书 · 已读完」分组(6 本),卡片结构与手工
  书单一致,左上角带「已读完」徽标;因为微信读书的数据没有书评/简介,
  这一组点击直接跳 weread.qq.com,不弹窗
- 这一组带 data-weread-card + data-category="微信读书",所以搜索框和
  「领域」筛选同样作用得到;领域筛选里会多出一个「微信读书」选项
- 筛选逻辑把 [data-book-card] 的选择器扩成 [data-book-card],
  [data-weread-card],分组计数也一并算上

实测数据分布(整架 26 本)
- 已读完 6 / 在读中 16(1%-97%)/ 没开始 4(0%)

静态验证(dist/bookshelf/index.html)
- 在读区 20 张卡片,进度条 30/0/77/28/13/97/0/5/16/3/10/1/4/13/4/0/1/10/6/0,
  区内无「已读完」字样
- 已读完分组 6 张卡片,6 个徽标、6 个微信读书跳转链接,均带 data-weread-card
- 手工书单 3 张卡片未受影响;领域筛选新增「微信读书」按钮
This commit is contained in:
dsh
2026-09-15 10:45:29 +08:00
parent 95dd0d9552
commit 4df235ea21
5 changed files with 288 additions and 16 deletions
+8 -5
View File
@@ -2,7 +2,7 @@
*
* 用法:
* pnpm reading 手动刷新(失败会 exit 1,明确报错)
* pnpm reading --top 12 抓前 12 本,默认 8
* pnpm reading --top 12 只抓最近动过的 12 本;不加则整架都抓(默认不限)
* node scripts/fetch-reading.mjs --soft 失败时保留旧数据并 exit 0(构建流程用)
*
* 接口是腾讯官方的微信读书 Agent Gateway:
@@ -25,14 +25,15 @@ const ENDPOINT = "https://i.weread.qq.com/api/agent/gateway"
/** 与官方 SKILL.md 顶部的 version 保持一致;服务端会用它对版本 */
const SKILL_VERSION = "1.0.4"
const OUT_FILE = "src/data/reading.json"
const DEFAULT_TOP = 8
/** 0 = 不限制,整架都抓。书架一般也就几十本,1+N 个请求完全够用 */
const DEFAULT_TOP = 0
const REQUEST_TIMEOUT_MS = 15_000
const DELAY_BETWEEN_CALLS_MS = 150
const args = process.argv.slice(2)
const SOFT = args.includes("--soft")
const topIndex = args.indexOf("--top")
const TOP_N = topIndex >= 0 ? Number(args[topIndex + 1]) || DEFAULT_TOP : DEFAULT_TOP
const TOP_N = topIndex >= 0 ? Number(args[topIndex + 1]) || DEFAULT_TOP : DEFAULT_TOP // 0 = 不限
// .env 已 gitignore;Node 22 自带 loadEnvFile,不需要 dotenv
try {
@@ -78,13 +79,15 @@ async function call(apiName, params = {}) {
async function fetchReading() {
const shelf = await call("/shelf/sync")
const candidates = (shelf.books ?? [])
const sorted = (shelf.books ?? [])
// 私密阅读(secret=1)不要发到公开网站上
.filter((book) => book.secret !== 1)
// readUpdateTime 为 0 表示从没打开过,不算「在读」
.filter((book) => Number(book.readUpdateTime) > 0)
.sort((a, b) => Number(b.readUpdateTime) - Number(a.readUpdateTime))
.slice(0, TOP_N)
// TOP_N 为 0 时整架都取;页面再按「读完 / 在读」自己分组
const candidates = TOP_N > 0 ? sorted.slice(0, TOP_N) : sorted
if (candidates.length === 0) {
return { books: [], note: "书架里没有可展示的阅读记录" }