Compare commits

...
2 Commits
Author SHA1 Message Date
TimeCrackbot ff698be696 修改了微信读书的脚本 2026-10-11 16:52:21 +08:00
TimeCrackbot 4512d3fb24 更新 src/content/posts/思考/博客到底可以用来做什么.md
对文章进行修改

Signed-off-by: 时之刃 <tianxuetao1998@hotmail.com>
2026-10-06 12:36:29 +00:00
8 changed files with 300 additions and 44 deletions
+5
View File
@@ -214,6 +214,11 @@ pnpm reading
读完的书会自动出现在「书籍」区底部的「微信读书 · 已读完」分组里,不用手工搬。 读完的书会自动出现在「书籍」区底部的「微信读书 · 已读完」分组里,不用手工搬。
**有声书(专辑)**:同一次请求里还会抓书架上的专辑,写进 `reading.json` 的 `albums` 字段。
「在读」页会自动多出一个「有声书」区块 —— 但没有专辑时整块不显示,所以现在看不到是正常的。
专辑没有阅读进度也没有跳转链接(微信读书那边就没给),卡片只显示集数、完结状态和最近收听。
`--top` 只作用于书,专辑一次全取;`secret: 1` 的私密收听同样会被过滤掉。
**平时不用管**:`pnpm build` 和 `./deploy.sh` 都会自动先抓一次。抓不到就沿用上一次的数据,**不会让部署失败**。 **平时不用管**:`pnpm build` 和 `./deploy.sh` 都会自动先抓一次。抓不到就沿用上一次的数据,**不会让部署失败**。
**首次配置**(只做一次): **首次配置**(只做一次):
+88 -8
View File
@@ -1,4 +1,4 @@
/* 抓取微信读书「最近在读」,写出 src/data/reading.json /* 抓取微信读书「最近在读」与「有声书专辑」,写出 src/data/reading.json
* *
* 用法: * 用法:
* pnpm reading 手动刷新(失败会 exit 1,明确报错) * pnpm reading 手动刷新(失败会 exit 1,明确报错)
@@ -17,14 +17,24 @@
* 只有「阅读进度」要额外调 /book/getprogress,所以请求数 = 1 + N * 只有「阅读进度」要额外调 /book/getprogress,所以请求数 = 1 + N
* 2. book.recordReadingTime 恒为 0,但 book.readingTime 是真数据,累计时长取后者 * 2. book.recordReadingTime 恒为 0,但 book.readingTime 是真数据,累计时长取后者
* 3. 官方文档没列 book.startReadingTime,但回包里有,是「开始阅读时间」 * 3. 官方文档没列 book.startReadingTime,但回包里有,是「开始阅读时间」
* 4. albums[](专辑/有声书)和 books[] 是平行数组,数据结构也不一样:
* 内容包在 albumInfo / albumInfoExtra 两层里,且**没有进度、没有 deepLink**
* (至少官方文档没写)。--top 只作用于 books,专辑一次全取。
*/ */
import fs from "node:fs" import fs from "node:fs"
import path from "node:path" import path from "node:path"
const ENDPOINT = "https://i.weread.qq.com/api/agent/gateway" const ENDPOINT = "https://i.weread.qq.com/api/agent/gateway"
/** 与官方 SKILL.md 顶部的 version 保持一致;服务端会用它对版本 */ /**
const SKILL_VERSION = "1.0.4" * 与官方 SKILL.md 顶部的 version 保持一致;服务端会用它对版本。
*
* 升级就是改这个数字,**不需要**下载官方的 weread-skills.zip:那个包是给 agent 读的
* 提示词文档(SKILL.md + shelf.md / book.md 等),本项目一个文件都不消费它;
* 网关只拿这里的 skill_version 做比对。2026-10-10 起服务端要求 1.0.5。
* 参考:https://cdn.weread.qq.com/skills/weread-skills.zip
*/
const SKILL_VERSION = "1.0.5"
const OUT_FILE = "src/data/reading.json" const OUT_FILE = "src/data/reading.json"
/** 0 = 不限制,整架都抓。书架一般也就几十本,1+N 个请求完全够用 */ /** 0 = 不限制,整架都抓。书架一般也就几十本,1+N 个请求完全够用 */
const DEFAULT_TOP = 0 const DEFAULT_TOP = 0
@@ -47,6 +57,54 @@ const apiKey = process.env.WEREAD_API_KEY
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
/**
* 时间戳转 ISO。
*
* 微信读书的书架时间戳实测是**秒**;但专辑那边的 lectureReadUpdateTime 官方文档
* 没写单位,万一是毫秒(13 位)就会得到公元 5 万年。用位数兜一下,两边都安全。
*/
function toIso(value) {
const n = Number(value)
if (!Number.isFinite(n) || n <= 0) return null
return new Date(n > 1e12 ? n : n * 1000).toISOString()
}
/**
* 专辑/有声书。和 books[] 平行,但结构完全不同:内容在 albumInfo / albumInfoExtra 两层。
*
* 官方文档给的字段里没有 progress、也没有 deepLink,所以:
* - 不伪造进度,前端只展示「集数 / 完结状态 / 最近收听」;
* - deepLink 按几个可能的位置兜一下,取不到就是空串(卡片退化成不可点击)。
* 私密收听(secret === 1)和书一样必须过滤掉 —— 这是要发到公开网站上的。
*/
function normalizeAlbums(shelf) {
return (shelf.albums ?? [])
.filter((album) => album?.albumInfoExtra?.secret !== 1)
.map((album) => {
const info = album.albumInfo ?? {}
const extra = album.albumInfoExtra ?? {}
return {
albumId: info.albumId ?? "",
name: info.name ?? "",
/** 演播 / 作者 */
author: info.authorName ?? "",
cover: info.cover ?? "",
trackCount: Number(info.trackCount) || 0,
finished: Number(info.finish) === 1,
/** 完结状态原文,如「已完结」 */
finishStatus: info.finishStatus ?? "",
intro: info.intro ?? "",
lastListenedAt: toIso(extra.lectureReadUpdateTime),
deepLink: info.deepLink ?? album.deepLink ?? "",
}
})
.sort((a, b) => {
const at = a.lastListenedAt ? Date.parse(a.lastListenedAt) : 0
const bt = b.lastListenedAt ? Date.parse(b.lastListenedAt) : 0
return bt - at
})
}
async function call(apiName, params = {}) { async function call(apiName, params = {}) {
const res = await fetch(ENDPOINT, { const res = await fetch(ENDPOINT, {
method: "POST", method: "POST",
@@ -61,7 +119,22 @@ async function call(apiName, params = {}) {
const json = await res.json().catch(() => null) const json = await res.json().catch(() => null)
if (json?.upgrade_info) { if (json?.upgrade_info) {
console.warn(`⚠️ 服务端要求升级 Skill:${json.upgrade_info.message ?? "(无说明)"}`) /*
* 服务端只拿 skill_version 做版本比对。原来的写法只打了 message,把
* current_version / latest_version / upgrade_url 一起丢掉了,于是提示退化成
* 一句无解的「请立即升级」—— 而 message 本身是写给 agent 的通用话术
* (「必须立即暂停当前操作…不得忽略该字段」),照做就等于让远端文案驱动本地文件。
* 这里改成把可执行的信息全打出来:差了多少版、去哪儿看、改哪一行。
*/
const { current_version, latest_version, upgrade_url, message } = json.upgrade_info
console.warn(
`⚠️ 微信读书 Skill 版本过期:本地 ${current_version ?? SKILL_VERSION} → 服务端要求 ${latest_version ?? "?"}(接口仍可用,只是提醒)`,
)
if (message) console.warn(` ${message}`)
if (upgrade_url) console.warn(` 升级包(仅供查阅,本项目不消费):${upgrade_url}`)
console.warn(
` 处理方式:把本文件顶部的 SKILL_VERSION 改成 "${latest_version ?? "最新版本"}",无需下载安装任何文件。`,
)
} }
if (res.status === 401 || json?.errcode === -2010) { if (res.status === 401 || json?.errcode === -2010) {
throw new Error( throw new Error(
@@ -80,6 +153,9 @@ async function call(apiName, params = {}) {
async function fetchReading() { async function fetchReading() {
const shelf = await call("/shelf/sync") const shelf = await call("/shelf/sync")
// 专辑一次全取(--top 只管书),所以先算出来,避免下面提前 return 时把它丢了
const albums = normalizeAlbums(shelf)
const sorted = (shelf.books ?? []) const sorted = (shelf.books ?? [])
// 私密阅读(secret=1)不要发到公开网站上 // 私密阅读(secret=1)不要发到公开网站上
.filter((book) => book.secret !== 1) .filter((book) => book.secret !== 1)
@@ -91,7 +167,7 @@ async function fetchReading() {
const candidates = TOP_N > 0 ? sorted.slice(0, TOP_N) : sorted const candidates = TOP_N > 0 ? sorted.slice(0, TOP_N) : sorted
if (candidates.length === 0) { if (candidates.length === 0) {
return { books: [], note: "书架里没有可展示的阅读记录" } return { books: [], albums, note: "书架里没有可展示的阅读记录" }
} }
const books = [] const books = []
@@ -140,7 +216,7 @@ async function fetchReading() {
await sleep(DELAY_BETWEEN_CALLS_MS) await sleep(DELAY_BETWEEN_CALLS_MS)
} }
return { books } return { books, albums }
} }
async function main() { async function main() {
@@ -151,23 +227,27 @@ async function main() {
) )
} }
const { books, note } = await fetchReading() const { books, albums, note } = await fetchReading()
if (note) console.warn(`⚠️ ${note}`) if (note) console.warn(`⚠️ ${note}`)
const payload = { const payload = {
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
source: "weread", source: "weread",
books, books,
albums,
} }
fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true }) fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true })
fs.writeFileSync(OUT_FILE, `${JSON.stringify(payload, null, 2)}\n`) fs.writeFileSync(OUT_FILE, `${JSON.stringify(payload, null, 2)}\n`)
console.log(`✅ 写入 ${books.length} 本到 ${OUT_FILE}`) console.log(`✅ 写入 ${books.length} 本 + ${albums.length} 张有声书专辑到 ${OUT_FILE}`)
for (const book of books) { for (const book of books) {
const progress = book.progress === null ? " ?" : `${String(book.progress).padStart(3)}%` const progress = book.progress === null ? " ?" : `${String(book.progress).padStart(3)}%`
console.log(` ${progress} ${book.title}${book.finished ? "(已读完)" : ""}`) console.log(` ${progress} ${book.title}${book.finished ? "(已读完)" : ""}`)
} }
for (const album of albums) {
console.log(` 🎧 ${album.name}${album.trackCount ? `(${album.trackCount} 集)` : ""}`)
}
} }
main().catch((error) => { main().catch((error) => {
+71
View File
@@ -0,0 +1,71 @@
---
import { formatReadingDate, type ReadingAlbum } from "@/lib/reading"
/**
* 微信读书「专辑 / 有声书」卡片。
*
* 为什么不复用 ReadingCard:两边能拿到的字段不一样。专辑没有 progress(官方结构里
* 就没有),文档也没给 deepLink,还少了分类、开始/读完时间。硬塞进书卡会出现
* 「进度条永远是 0%」这种假信息,所以单独一张,底部换成「集数 / 完结状态 / 最近收听」。
*
* deepLink 拿不到时不渲染成 <a>,只做展示 —— 一个点不动的链接比没有链接更糟。
*/
interface Props {
album: ReadingAlbum
}
const { album } = Astro.props
const Tag = album.deepLink ? "a" : "div"
const linkable = Boolean(album.deepLink)
const lastListened = formatReadingDate(album.lastListenedAt)
const meta = [album.trackCount > 0 ? `${album.trackCount} 集` : "", album.finishStatus]
.filter(Boolean)
.join(" · ")
---
<Tag
href={linkable ? album.deepLink : undefined}
target={linkable ? "_blank" : undefined}
rel={linkable ? "noopener noreferrer" : undefined}
class:list={[
"group flex gap-4 rounded-xl border border-border/50 bg-card p-4",
linkable && "transition-shadow duration-300 hover:shadow-lg",
]}
>
<div class="shrink-0 w-20 aspect-square rounded-lg overflow-hidden bg-surface-subtle">
{album.cover ? (
<img
src={album.cover}
alt={album.name}
loading="lazy"
decoding="async"
referrerpolicy="no-referrer"
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
) : (
<div class="flex h-full w-full items-center justify-center p-2 bg-gradient-to-br from-primary/30 via-primary/20 to-primary/5">
<p class="font-semibold text-[10px] text-foreground text-center line-clamp-4">{album.name}</p>
</div>
)}
</div>
<div class="flex-1 min-w-0 flex flex-col">
<h3
class:list={[
"font-medium text-sm text-foreground line-clamp-2 leading-snug mb-1",
linkable && "group-hover:text-primary transition-colors",
]}
>
{album.name}
</h3>
<p class="text-xs text-muted-foreground line-clamp-1 mb-3">{album.author}</p>
<div class="mt-auto space-y-1.5">
{meta && <p class="text-[11px] text-muted-foreground/80 tabular-nums">{meta}</p>}
<p class="text-[11px] text-muted-foreground/80 tabular-nums">
{lastListened ? `最近收听 ${lastListened}` : "还没有收听记录"}
</p>
</div>
</div>
</Tag>
+27
View File
@@ -4,6 +4,7 @@ import Footer from "@/components/footer.astro"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import booksData from "../../public/data/books.json" import booksData from "../../public/data/books.json"
import ReadingCard from "@/components/reading-card.astro" import ReadingCard from "@/components/reading-card.astro"
import AlbumCard from "@/components/album-card.astro"
import { formatReadingDate, getReadingData } from "@/lib/reading" import { formatReadingDate, getReadingData } from "@/lib/reading"
/** /**
@@ -26,6 +27,11 @@ const inProgressReading = readingBooks.filter((book) => !book.finished)
const finishedReading = readingBooks.filter((book) => book.finished) const finishedReading = readingBooks.filter((book) => book.finished)
const readingCount = inProgressReading.length const readingCount = inProgressReading.length
// 有声书专辑:与 books[] 平行的另一组数据。
// 用 ?? [] 兜住两种情况:老版 reading.json 没有 albums 字段(--soft 回退时),
// 以及你根本没听过有声书(服务端返回空数组)。
const readingAlbums = reading?.albums ?? []
/** 「书籍」区里给微信读书已读书记一个分类名,同时作为可筛选的领域 */ /** 「书籍」区里给微信读书已读书记一个分类名,同时作为可筛选的领域 */
const WEREAD_CATEGORY = "微信读书" const WEREAD_CATEGORY = "微信读书"
@@ -194,6 +200,27 @@ const recommendBadge = (status?: string) => {
</div> </div>
)} )}
{/* ========================= 有声书(微信读书专辑) =========================
与 books[] 平行的一组数据,只有真的听过有声书时服务端才会返回内容。
readingAlbums 为空就整块不渲染 —— 所以在你有第一张专辑之前,这个区块
既不存在也不占位。数据由 scripts/fetch-reading.mjs 一并抓取。 */}
{isReadingView && readingAlbums.length > 0 && (
<div id="albums" class="mb-16 scroll-mt-24">
<div class="flex flex-wrap items-end justify-between gap-x-6 gap-y-2 mb-6">
<div>
<div class="text-[11px] text-primary tracking-[0.22em] font-semibold uppercase mb-1.5">Collection · 02</div>
<h2 class="text-3xl md:text-4xl font-bold font-serif tracking-tight text-foreground mb-1.5">有声书</h2>
<div class="text-sm italic font-serif text-muted-foreground">耳朵也在读书</div>
</div>
<p class="text-xs text-muted-foreground tabular-nums">{readingAlbums.length} 张专辑</p>
</div>
<div class="grid gap-4 sm:grid-cols-2">
{readingAlbums.map((album) => <AlbumCard album={album} />)}
</div>
</div>
)}
{!isReadingView && ( {!isReadingView && (
<> <>
{/* ---------- Header ---------- */} {/* ---------- Header ---------- */}
@@ -28,3 +28,6 @@ Halo 用起来和 Vanblog 相比,功能确实很多,但运行起来也相对
现在我有了自己的这个小站,也可以写自己的一些东西,无论是生活感悟还是技术文章,或者是一些图片之类的,都可以在这个平台上记录,还是那句话,"**现在不缺记录的平台,缺乏记录的热情**。" 现在我有了自己的这个小站,也可以写自己的一些东西,无论是生活感悟还是技术文章,或者是一些图片之类的,都可以在这个平台上记录,还是那句话,"**现在不缺记录的平台,缺乏记录的热情**。"
>更新
今天是2026年10月6日,重新回头思考了一下,我觉得博客或者说自己的小站,更多的是个人的思考展示的一个具体平台。许多自己的想法和认识,用网页的形式公之于众,虽然不会有什么人去看,但是确实有一个独属于自己的赛博空间,用于自己耕耘。
@@ -28,5 +28,13 @@ slug: impact-of-execution
**想到和得到之间还有一个做到** **想到和得到之间还有一个做到**
有的时候我也在想这个问题, 有的时候我也在想这个问题,**做到**突出的是个人实现的过程,是改变这个物质世界的必然。很多时候,我会选择逃避,便会以拖延的形式出现。干工作没有动力,明明知道这件事情很重要了,但是却一拖再拖。
内心的驱动力就是这个中间体,映射到行动上就是执行力。自己需要不断提升自己的内驱动力,在自己的精神世界里里面不断建立一个又一个里程碑,完成一个又一个目标形成正反馈,让自己成为一个高执行力的人。
很多时候,很多事情是很难看到回报的,或者说没有那么明显的正反馈,就容易懈怠,半途而废。
然而每个人之间的差距就会体现在这里。引用一段话:每个人,每个年龄段,每个层次都有不熟悉的领域,不精通的专业。一部分人碍于面子不懂装懂,一部分人放低自己潜心学习。这与你的年龄段,与你的层次没有关系,只与你态度发生转变有关。
人非生而知之,
现在就是感觉自己很迷茫,不想工作,但又不得不工作。
+64 -35
View File
@@ -1,19 +1,75 @@
{ {
"updatedAt": "2026-10-04T12:16:26.963Z", "updatedAt": "2026-10-11T08:52:01.398Z",
"source": "weread", "source": "weread",
"books": [ "books": [
{
"bookId": "3300207468",
"title": "快乐的死(果麦经典)",
"author": "[法]加缪",
"category": "文学-外国文学",
"cover": "https://cdn.weread.qq.com/weread/cover/12/cpplatform_tgx43jt54ezt7bamqcmjbk/t6_cpplatform_tgx43jt54ezt7bamqcmjbk1779878190.jpg",
"progress": 5,
"finished": false,
"lastReadAt": "2026-10-08T15:27:08.000Z",
"startedAt": null,
"finishedAt": null,
"readingSeconds": 262,
"deepLink": "https://weread.qq.com/book-detail?type=1&v=66b32530813abb78ag018a22"
},
{
"bookId": "3300086064",
"title": "河西走廊",
"author": "《河西走廊》摄制组编",
"category": "历史-历史地理",
"cover": "https://cdn.weread.qq.com/weread/cover/89/cpplatform_htt13rz3ky797h8pvypqq1/t6_cpplatform_htt13rz3ky797h8pvypqq11707017069.jpg",
"progress": 0,
"finished": false,
"lastReadAt": "2026-10-08T12:06:21.000Z",
"startedAt": null,
"finishedAt": null,
"readingSeconds": 0,
"deepLink": "https://weread.qq.com/book-detail?type=1&v=de932a80813ab881eg014870"
},
{
"bookId": "3300175025",
"title": "此刻是春天(微信读书特别版)",
"author": "卢思浩",
"category": "精品小说-治愈小说",
"cover": "https://cdn.weread.qq.com/weread/cover/4/cpplatform_3hsoybvrwapq1efqha1mvz/t6_cpplatform_3hsoybvrwapq1efqha1mvz1762325065.jpg",
"progress": 4,
"finished": false,
"lastReadAt": "2026-10-08T12:05:30.000Z",
"startedAt": "2026-01-11T10:32:09.000Z",
"finishedAt": "2026-09-27T16:39:36.000Z",
"readingSeconds": 295,
"deepLink": "https://weread.qq.com/book-detail?type=1&v=b0b32630813abaadeg0157d1"
},
{
"bookId": "3300095415",
"title": "觉醒年代(全集|同名影视原著)",
"author": "龙平平",
"category": "历史-历史小说",
"cover": "https://cdn.weread.qq.com/weread/cover/66/cpplatform_4opftuyz4szxsagdfgsn1i/t6_cpplatform_4opftuyz4szxsagdfgsn1i1713258044.jpg",
"progress": 0,
"finished": false,
"lastReadAt": "2026-10-08T11:44:34.000Z",
"startedAt": null,
"finishedAt": null,
"readingSeconds": 0,
"deepLink": "https://weread.qq.com/book-detail?type=1&v=e6032ec0813ab8bc5g015fd3"
},
{ {
"bookId": "182788", "bookId": "182788",
"title": "北京法源寺(文学性与思想性相结合的经典作品)", "title": "北京法源寺(文学性与思想性相结合的经典作品)",
"author": "李敖", "author": "李敖",
"category": "历史-历史小说", "category": "历史-历史小说",
"cover": "https://cdn.weread.qq.com/weread/cover/16/yuewen_182788/t6_yuewen_1827881778320200.jpg", "cover": "https://cdn.weread.qq.com/weread/cover/16/yuewen_182788/t6_yuewen_1827881778320200.jpg",
"progress": 51, "progress": 100,
"finished": false, "finished": true,
"lastReadAt": "2026-10-04T10:57:59.000Z", "lastReadAt": "2026-10-08T11:42:40.000Z",
"startedAt": "2026-09-21T06:26:14.000Z", "startedAt": "2026-09-21T06:26:14.000Z",
"finishedAt": null, "finishedAt": "2026-10-08T11:30:06.000Z",
"readingSeconds": 4379, "readingSeconds": 11837,
"deepLink": "https://weread.qq.com/book-detail?type=1&v=dcc3296052ca04dccdbef6c" "deepLink": "https://weread.qq.com/book-detail?type=1&v=dcc3296052ca04dccdbef6c"
}, },
{ {
@@ -30,20 +86,6 @@
"readingSeconds": 14442, "readingSeconds": 14442,
"deepLink": "https://weread.qq.com/book-detail?type=1&v=34c329807197141b34cf1db" "deepLink": "https://weread.qq.com/book-detail?type=1&v=34c329807197141b34cf1db"
}, },
{
"bookId": "3300175025",
"title": "此刻是春天(微信读书特别版)",
"author": "卢思浩",
"category": "精品小说-治愈小说",
"cover": "https://cdn.weread.qq.com/weread/cover/4/cpplatform_3hsoybvrwapq1efqha1mvz/t6_cpplatform_3hsoybvrwapq1efqha1mvz1762325065.jpg",
"progress": 4,
"finished": false,
"lastReadAt": "2026-09-27T16:39:50.000Z",
"startedAt": "2026-01-11T10:32:09.000Z",
"finishedAt": "2026-09-27T16:39:36.000Z",
"readingSeconds": 231,
"deepLink": "https://weread.qq.com/book-detail?type=1&v=b0b32630813abaadeg0157d1"
},
{ {
"bookId": "23914527", "bookId": "23914527",
"title": "树莓派开始,玩转Linux", "title": "树莓派开始,玩转Linux",
@@ -58,20 +100,6 @@
"readingSeconds": 14629, "readingSeconds": 14629,
"deepLink": "https://weread.qq.com/book-detail?type=1&v=140329d0716ce81f140468e" "deepLink": "https://weread.qq.com/book-detail?type=1&v=140329d0716ce81f140468e"
}, },
{
"bookId": "3300207468",
"title": "快乐的死(果麦经典)",
"author": "[法]加缪",
"category": "文学-外国文学",
"cover": "https://cdn.weread.qq.com/weread/cover/12/cpplatform_tgx43jt54ezt7bamqcmjbk/t6_cpplatform_tgx43jt54ezt7bamqcmjbk1779878190.jpg",
"progress": 0,
"finished": false,
"lastReadAt": "2026-09-27T10:00:32.000Z",
"startedAt": null,
"finishedAt": null,
"readingSeconds": 38,
"deepLink": "https://weread.qq.com/book-detail?type=1&v=66b32530813abb78ag018a22"
},
{ {
"bookId": "23796324", "bookId": "23796324",
"title": "昨日的世界", "title": "昨日的世界",
@@ -380,5 +408,6 @@
"readingSeconds": 137, "readingSeconds": 137,
"deepLink": "https://weread.qq.com/book-detail?type=1&v=47532ac05c659b47554a825" "deepLink": "https://weread.qq.com/book-detail?type=1&v=47532ac05c659b47554a825"
} }
] ],
"albums": []
} }
+33
View File
@@ -36,6 +36,39 @@ export type ReadingData = {
updatedAt: string updatedAt: string
source: string source: string
books: ReadingBook[] books: ReadingBook[]
/**
* 专辑 / 有声书。
*
* 可选:`--soft` 构建路径下用的可能是更早版本的 reading.json,那时还没有这个字段。
* 调用方一律写 `data.albums ?? []`。
*/
albums?: ReadingAlbum[]
}
/**
* 一张有声书专辑。
*
* 字段刻意比 ReadingBook 少 —— 微信读书给专辑的原始结构里(albumInfo /
* albumInfoExtra)**没有阅读进度,也没有 deepLink**,所以这里没有 progress,
* 前端也不该伪造一条进度条。哪天官方补了字段,先改 scripts/fetch-reading.mjs。
*/
export type ReadingAlbum = {
albumId: string
name: string
/** 演播 / 作者 */
author: string
cover: string
/** 音频集数;取不到时为 0 */
trackCount: number
/** 是否已完结(原始字段 finish === 1) */
finished: boolean
/** 完结状态原文,如「已完结」 */
finishStatus: string
intro: string
/** 最近收听时间,ISO;从没听过时为 null */
lastListenedAt: string | null
/** 跳转链接。官方文档未列此字段,实测可能为空串 */
deepLink: string
} }
const DATA_FILE = path.join(process.cwd(), "src/data/reading.json") const DATA_FILE = path.join(process.cwd(), "src/data/reading.json")