gudong-content-cli 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,152 @@
1
+ import { type SnapshotFetcher } from './snapshot.js';
2
+ /**
3
+ * 只读查询服务:文章清单/划线/想法/搜索/统计。
4
+ * 输出结构 snake_case(家族 CLI 输出约定)。
5
+ *
6
+ * stats 口径来源(逐字对齐,勿改):content-dong 仓库
7
+ * `ArticleRepository.getReadingStats`(lib/features/library/article_repository.dart):
8
+ * - estimated_hours = finished×0.4 + reading×0.2 + Σ(excerpt.length×8×readProgress 逐篇取整)/24000,
9
+ * 结果 <0.1 且(finished>0 或 reading>0)时取 0.3(仅展示层四舍五入到两位小数);
10
+ * - days_read = lastReadAt 去重自然日数(App 字段名叫 streakDays,语义如此);
11
+ * 无阅读日但有完读时取 1、否则 0(镜像 App 的 readDays.isEmpty 分支);
12
+ * - stale_unread = 未读且 savedAt 距今 ≥14 天(毫秒差取整天数,同 App now.difference().inDays);
13
+ * - 自然日按 timezone 配置折算(默认 Asia/Shanghai,对应 App 设备时区)。
14
+ */
15
+ export declare class ContentService {
16
+ private readonly fetcher;
17
+ private readonly timezone;
18
+ constructor(fetcher: SnapshotFetcher, timezone: string);
19
+ private load;
20
+ /** 收藏流:savedAt 倒序,可按状态/星标过滤。 */
21
+ articles(opts?: {
22
+ status?: string;
23
+ starred?: boolean;
24
+ limit?: number;
25
+ }): Promise<ArticlesReport>;
26
+ /** 划线原文(createdAt 倒序),可按文章/颜色/日期窗口过滤。 */
27
+ highlights(opts?: {
28
+ article?: string;
29
+ color?: string;
30
+ since?: string;
31
+ until?: string;
32
+ limit?: number;
33
+ }): Promise<{
34
+ count: number;
35
+ highlights: HighlightItem[];
36
+ }>;
37
+ /** 划线想法(带 quote_text 上下文)+ 文章级批注,createdAt 倒序。 */
38
+ notes(opts?: {
39
+ article?: string;
40
+ limit?: number;
41
+ }): Promise<{
42
+ count: number;
43
+ notes: NoteItem[];
44
+ }>;
45
+ /** 标题+摘要+划线原文+想法全文的子串搜索(大小写不敏感),命中项标明命中字段。 */
46
+ search(query: string, limit?: number): Promise<SearchReport>;
47
+ stats(): Promise<StatsReport>;
48
+ /** 文章过滤:精确 id → 精确标题(大小写不敏感)→ 标题子串包含,全部不命中抛错。 */
49
+ private filterArticles;
50
+ private articleOut;
51
+ }
52
+ export interface ArticleItem {
53
+ id: string;
54
+ title: string;
55
+ author: string;
56
+ url: string;
57
+ source: string;
58
+ status: string;
59
+ read_progress: number;
60
+ starred: boolean;
61
+ saved_at: number;
62
+ saved_date: string;
63
+ last_read_at: number | null;
64
+ last_read_date: string | null;
65
+ last_read_time: string | null;
66
+ excerpt: string;
67
+ highlights_count: number;
68
+ notes_count: number;
69
+ article_notes_count: number;
70
+ }
71
+ export interface ArticlesReport {
72
+ count: number;
73
+ articles: ArticleItem[];
74
+ }
75
+ export interface HighlightItem {
76
+ id: string;
77
+ article_id: string;
78
+ article_title: string;
79
+ date: string;
80
+ time: string;
81
+ text: string;
82
+ type: string;
83
+ color: string;
84
+ percentage: number;
85
+ notes_count: number;
86
+ created_at: number;
87
+ }
88
+ export interface NoteItem {
89
+ id: string;
90
+ /** highlight_note = 划线想法(带 quote_text);article_note = 文章级批注 */
91
+ kind: 'highlight_note' | 'article_note';
92
+ article_id: string;
93
+ article_title: string;
94
+ date: string;
95
+ time: string;
96
+ content: string;
97
+ quote_text: string | null;
98
+ highlight_id: string | null;
99
+ created_at: number;
100
+ updated_at: number | null;
101
+ }
102
+ export interface SearchArticleHit {
103
+ id: string;
104
+ title: string;
105
+ matched_fields: ('title' | 'excerpt')[];
106
+ excerpt: string;
107
+ saved_at: number;
108
+ }
109
+ export interface SearchHighlightHit {
110
+ id: string;
111
+ article_id: string;
112
+ article_title: string;
113
+ date: string;
114
+ text: string;
115
+ matched_fields: ['text'];
116
+ }
117
+ export interface SearchNoteHit {
118
+ id: string;
119
+ kind: 'highlight_note' | 'article_note';
120
+ article_id: string;
121
+ article_title: string;
122
+ date: string;
123
+ content: string;
124
+ quote_text: string | null;
125
+ matched_fields: ['content'];
126
+ }
127
+ export interface SearchReport {
128
+ query: string;
129
+ articles: SearchArticleHit[];
130
+ highlights: SearchHighlightHit[];
131
+ notes: SearchNoteHit[];
132
+ }
133
+ export interface StatsReport {
134
+ timezone: string;
135
+ snapshot_exported_at: number;
136
+ total_articles: number;
137
+ status_counts: {
138
+ unread: number;
139
+ reading: number;
140
+ finished: number;
141
+ };
142
+ total_highlights: number;
143
+ total_notes: {
144
+ highlight_notes: number;
145
+ article_notes: number;
146
+ total: number;
147
+ };
148
+ starred: number;
149
+ days_read: number;
150
+ estimated_hours: number;
151
+ stale_unread: number;
152
+ }
@@ -0,0 +1,281 @@
1
+ import { calendarDateFor, timeLabelFor } from './snapshot.js';
2
+ const DAY_MS = 86_400_000;
3
+ /**
4
+ * 只读查询服务:文章清单/划线/想法/搜索/统计。
5
+ * 输出结构 snake_case(家族 CLI 输出约定)。
6
+ *
7
+ * stats 口径来源(逐字对齐,勿改):content-dong 仓库
8
+ * `ArticleRepository.getReadingStats`(lib/features/library/article_repository.dart):
9
+ * - estimated_hours = finished×0.4 + reading×0.2 + Σ(excerpt.length×8×readProgress 逐篇取整)/24000,
10
+ * 结果 <0.1 且(finished>0 或 reading>0)时取 0.3(仅展示层四舍五入到两位小数);
11
+ * - days_read = lastReadAt 去重自然日数(App 字段名叫 streakDays,语义如此);
12
+ * 无阅读日但有完读时取 1、否则 0(镜像 App 的 readDays.isEmpty 分支);
13
+ * - stale_unread = 未读且 savedAt 距今 ≥14 天(毫秒差取整天数,同 App now.difference().inDays);
14
+ * - 自然日按 timezone 配置折算(默认 Asia/Shanghai,对应 App 设备时区)。
15
+ */
16
+ export class ContentService {
17
+ fetcher;
18
+ timezone;
19
+ constructor(fetcher, timezone) {
20
+ this.fetcher = fetcher;
21
+ this.timezone = timezone;
22
+ }
23
+ async load() {
24
+ return this.fetcher.fetch();
25
+ }
26
+ // ---------- 文章清单 ----------
27
+ /** 收藏流:savedAt 倒序,可按状态/星标过滤。 */
28
+ async articles(opts = {}) {
29
+ const snap = await this.load();
30
+ let list = [...snap.articles];
31
+ if (opts.status)
32
+ list = list.filter((a) => a.status === opts.status);
33
+ if (opts.starred)
34
+ list = list.filter((a) => a.starred);
35
+ list.sort((a, b) => b.savedAt - a.savedAt);
36
+ const limit = opts.limit ?? 50;
37
+ return {
38
+ count: list.length,
39
+ articles: list.slice(0, limit).map((a) => this.articleOut(a, this.timezone)),
40
+ };
41
+ }
42
+ // ---------- 划线 ----------
43
+ /** 划线原文(createdAt 倒序),可按文章/颜色/日期窗口过滤。 */
44
+ async highlights(opts = {}) {
45
+ const snap = await this.load();
46
+ const matched = this.filterArticles(snap.articles, opts.article);
47
+ const items = [];
48
+ for (const a of matched) {
49
+ for (const h of a.highlights) {
50
+ if (opts.color && h.color !== opts.color)
51
+ continue;
52
+ const date = calendarDateFor(h.createdAt, this.timezone);
53
+ if (opts.since && date < opts.since)
54
+ continue;
55
+ if (opts.until && date > opts.until)
56
+ continue;
57
+ items.push({
58
+ id: h.id,
59
+ article_id: a.id,
60
+ article_title: a.title,
61
+ date,
62
+ time: timeLabelFor(h.createdAt, this.timezone),
63
+ text: h.text,
64
+ type: h.type,
65
+ color: h.color,
66
+ percentage: h.percentage,
67
+ notes_count: h.notes.length,
68
+ created_at: h.createdAt,
69
+ });
70
+ }
71
+ }
72
+ items.sort((a, b) => b.created_at - a.created_at);
73
+ const limit = opts.limit ?? 50;
74
+ return { count: items.length, highlights: items.slice(0, limit) };
75
+ }
76
+ // ---------- 想法(两类合一) ----------
77
+ /** 划线想法(带 quote_text 上下文)+ 文章级批注,createdAt 倒序。 */
78
+ async notes(opts = {}) {
79
+ const snap = await this.load();
80
+ const matched = this.filterArticles(snap.articles, opts.article);
81
+ const items = [];
82
+ for (const a of matched) {
83
+ for (const h of a.highlights) {
84
+ for (const n of h.notes) {
85
+ items.push({
86
+ id: n.id,
87
+ kind: 'highlight_note',
88
+ article_id: a.id,
89
+ article_title: a.title,
90
+ date: calendarDateFor(n.createdAt, this.timezone),
91
+ time: timeLabelFor(n.createdAt, this.timezone),
92
+ content: n.content,
93
+ quote_text: h.text,
94
+ highlight_id: h.id,
95
+ created_at: n.createdAt,
96
+ updated_at: n.updatedAt,
97
+ });
98
+ }
99
+ }
100
+ for (const an of a.articleNotes) {
101
+ items.push({
102
+ id: an.id,
103
+ kind: 'article_note',
104
+ article_id: a.id,
105
+ article_title: a.title,
106
+ date: calendarDateFor(an.createdAt, this.timezone),
107
+ time: timeLabelFor(an.createdAt, this.timezone),
108
+ content: an.content,
109
+ quote_text: null,
110
+ highlight_id: null,
111
+ created_at: an.createdAt,
112
+ updated_at: an.updatedAt,
113
+ });
114
+ }
115
+ }
116
+ items.sort((a, b) => b.created_at - a.created_at);
117
+ const limit = opts.limit ?? 50;
118
+ return { count: items.length, notes: items.slice(0, limit) };
119
+ }
120
+ // ---------- 搜索 ----------
121
+ /** 标题+摘要+划线原文+想法全文的子串搜索(大小写不敏感),命中项标明命中字段。 */
122
+ async search(query, limit = 20) {
123
+ const q = query.trim().toLowerCase();
124
+ if (!q)
125
+ return { query, articles: [], highlights: [], notes: [] };
126
+ const snap = await this.load();
127
+ const articles = snap.articles
128
+ .filter((a) => a.title.toLowerCase().includes(q) || a.excerpt.toLowerCase().includes(q))
129
+ .slice(0, limit)
130
+ .map((a) => {
131
+ const fields = [];
132
+ if (a.title.toLowerCase().includes(q))
133
+ fields.push('title');
134
+ if (a.excerpt.toLowerCase().includes(q))
135
+ fields.push('excerpt');
136
+ return {
137
+ id: a.id,
138
+ title: a.title,
139
+ matched_fields: fields,
140
+ excerpt: a.excerpt,
141
+ saved_at: a.savedAt,
142
+ };
143
+ });
144
+ const highlights = [];
145
+ const notes = [];
146
+ for (const a of snap.articles) {
147
+ for (const h of a.highlights) {
148
+ if (highlights.length < limit && h.text.toLowerCase().includes(q)) {
149
+ highlights.push({
150
+ id: h.id,
151
+ article_id: a.id,
152
+ article_title: a.title,
153
+ date: calendarDateFor(h.createdAt, this.timezone),
154
+ text: h.text,
155
+ matched_fields: ['text'],
156
+ });
157
+ }
158
+ for (const n of h.notes) {
159
+ if (notes.length < limit && n.content.toLowerCase().includes(q)) {
160
+ notes.push({
161
+ id: n.id,
162
+ kind: 'highlight_note',
163
+ article_id: a.id,
164
+ article_title: a.title,
165
+ date: calendarDateFor(n.createdAt, this.timezone),
166
+ content: n.content,
167
+ quote_text: h.text,
168
+ matched_fields: ['content'],
169
+ });
170
+ }
171
+ }
172
+ }
173
+ for (const an of a.articleNotes) {
174
+ if (notes.length < limit && an.content.toLowerCase().includes(q)) {
175
+ notes.push({
176
+ id: an.id,
177
+ kind: 'article_note',
178
+ article_id: a.id,
179
+ article_title: a.title,
180
+ date: calendarDateFor(an.createdAt, this.timezone),
181
+ content: an.content,
182
+ quote_text: null,
183
+ matched_fields: ['content'],
184
+ });
185
+ }
186
+ }
187
+ }
188
+ return { query, articles, highlights, notes };
189
+ }
190
+ // ---------- 统计 ----------
191
+ async stats() {
192
+ const snap = await this.load();
193
+ const now = Date.now();
194
+ const statusCounts = { unread: 0, reading: 0, finished: 0 };
195
+ let starredCount = 0;
196
+ let staleUnread = 0;
197
+ let totalChars = 0;
198
+ const readDays = new Set();
199
+ for (const a of snap.articles) {
200
+ if (a.status === 'unread') {
201
+ statusCounts.unread++;
202
+ if (Math.floor((now - a.savedAt) / DAY_MS) >= 14)
203
+ staleUnread++;
204
+ }
205
+ else if (a.status === 'reading') {
206
+ statusCounts.reading++;
207
+ }
208
+ else if (a.status === 'finished') {
209
+ statusCounts.finished++;
210
+ }
211
+ if (a.starred)
212
+ starredCount++;
213
+ if (a.lastReadAt != null) {
214
+ readDays.add(calendarDateFor(a.lastReadAt, this.timezone));
215
+ }
216
+ // 估算字数:同 App 的 (excerpt.length × 8 × readProgress).toInt(),逐篇取整后累加
217
+ totalChars += Math.trunc(a.excerpt.length * 8 * a.readProgress);
218
+ }
219
+ let hours = statusCounts.finished * 0.4 + statusCounts.reading * 0.2 + totalChars / 24000;
220
+ if (hours < 0.1 && (statusCounts.finished > 0 || statusCounts.reading > 0))
221
+ hours = 0.3;
222
+ const highlightNotes = snap.articles.reduce((s, a) => s + a.highlights.reduce((hs, h) => hs + h.notes.length, 0), 0);
223
+ const articleNotes = snap.articles.reduce((s, a) => s + a.articleNotes.length, 0);
224
+ const totalHighlights = snap.articles.reduce((s, a) => s + a.highlights.length, 0);
225
+ return {
226
+ timezone: this.timezone,
227
+ snapshot_exported_at: snap.exportedAt,
228
+ total_articles: snap.articles.length,
229
+ status_counts: statusCounts,
230
+ total_highlights: totalHighlights,
231
+ total_notes: {
232
+ highlight_notes: highlightNotes,
233
+ article_notes: articleNotes,
234
+ total: highlightNotes + articleNotes,
235
+ },
236
+ starred: starredCount,
237
+ days_read: readDays.size === 0 ? (statusCounts.finished > 0 ? 1 : 0) : readDays.size,
238
+ estimated_hours: Math.round(hours * 100) / 100,
239
+ stale_unread: staleUnread,
240
+ };
241
+ }
242
+ // ---------- 内部 ----------
243
+ /** 文章过滤:精确 id → 精确标题(大小写不敏感)→ 标题子串包含,全部不命中抛错。 */
244
+ filterArticles(articles, articleArg) {
245
+ if (!articleArg)
246
+ return articles;
247
+ const q = articleArg.trim().toLowerCase();
248
+ const matched = articles.filter((a) => a.id.toLowerCase() === q).length > 0
249
+ ? articles.filter((a) => a.id.toLowerCase() === q)
250
+ : articles.filter((a) => a.title.toLowerCase() === q).length > 0
251
+ ? articles.filter((a) => a.title.toLowerCase() === q)
252
+ : articles.filter((a) => a.title.toLowerCase().includes(q));
253
+ if (matched.length === 0) {
254
+ throw new Error(`找不到匹配的文章:${articleArg}(先用 articles 命令看全部文章)`);
255
+ }
256
+ return matched;
257
+ }
258
+ articleOut(a, tz) {
259
+ const notesCount = a.highlights.reduce((s, h) => s + h.notes.length, 0);
260
+ return {
261
+ id: a.id,
262
+ title: a.title,
263
+ author: a.author,
264
+ url: a.url,
265
+ source: a.source,
266
+ status: a.status,
267
+ read_progress: a.readProgress,
268
+ starred: a.starred,
269
+ saved_at: a.savedAt,
270
+ saved_date: calendarDateFor(a.savedAt, tz),
271
+ last_read_at: a.lastReadAt,
272
+ last_read_date: a.lastReadAt ? calendarDateFor(a.lastReadAt, tz) : null,
273
+ last_read_time: a.lastReadAt ? timeLabelFor(a.lastReadAt, tz) : null,
274
+ excerpt: a.excerpt,
275
+ highlights_count: a.highlights.length,
276
+ notes_count: notesCount,
277
+ article_notes_count: a.articleNotes.length,
278
+ };
279
+ }
280
+ }
281
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/content/service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAwB,MAAM,eAAe,CAAC;AAEpF,MAAM,MAAM,GAAG,UAAU,CAAC;AAE1B;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,cAAc;IAEN;IACA;IAFnB,YACmB,OAAwB,EACxB,QAAgB;QADhB,YAAO,GAAP,OAAO,CAAiB;QACxB,aAAQ,GAAR,QAAQ,CAAQ;IAChC,CAAC;IAEI,KAAK,CAAC,IAAI;QAChB,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IAC9B,CAAC;IAED,6BAA6B;IAE7B,gCAAgC;IAChC,KAAK,CAAC,QAAQ,CAAC,OAA+D,EAAE;QAC9E,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,IAAI,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9B,IAAI,IAAI,CAAC,MAAM;YAAE,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC,CAAC;QACrE,IAAI,IAAI,CAAC,OAAO;YAAE,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACvD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QAC/B,OAAO;YACL,KAAK,EAAE,IAAI,CAAC,MAAM;YAClB,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;SAC7E,CAAC;IACJ,CAAC;IAED,2BAA2B;IAE3B,yCAAyC;IACzC,KAAK,CAAC,UAAU,CACd,OAA6F,EAAE;QAE/F,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACjE,MAAM,KAAK,GAAoB,EAAE,CAAC;QAClC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;gBAC7B,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK;oBAAE,SAAS;gBACnD,MAAM,IAAI,GAAG,eAAe,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACzD,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,GAAG,IAAI,CAAC,KAAK;oBAAE,SAAS;gBAC9C,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,GAAG,IAAI,CAAC,KAAK;oBAAE,SAAS;gBAC9C,KAAK,CAAC,IAAI,CAAC;oBACT,EAAE,EAAE,CAAC,CAAC,EAAE;oBACR,UAAU,EAAE,CAAC,CAAC,EAAE;oBAChB,aAAa,EAAE,CAAC,CAAC,KAAK;oBACtB,IAAI;oBACJ,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC;oBAC9C,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,KAAK,EAAE,CAAC,CAAC,KAAK;oBACd,UAAU,EAAE,CAAC,CAAC,UAAU;oBACxB,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM;oBAC3B,UAAU,EAAE,CAAC,CAAC,SAAS;iBACxB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC;QAClD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QAC/B,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;IACpE,CAAC;IAED,iCAAiC;IAEjC,kDAAkD;IAClD,KAAK,CAAC,KAAK,CAAC,OAA6C,EAAE;QAIzD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACjE,MAAM,KAAK,GAAe,EAAE,CAAC;QAC7B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;gBAC7B,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;oBACxB,KAAK,CAAC,IAAI,CAAC;wBACT,EAAE,EAAE,CAAC,CAAC,EAAE;wBACR,IAAI,EAAE,gBAAgB;wBACtB,UAAU,EAAE,CAAC,CAAC,EAAE;wBAChB,aAAa,EAAE,CAAC,CAAC,KAAK;wBACtB,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC;wBACjD,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC;wBAC9C,OAAO,EAAE,CAAC,CAAC,OAAO;wBAClB,UAAU,EAAE,CAAC,CAAC,IAAI;wBAClB,YAAY,EAAE,CAAC,CAAC,EAAE;wBAClB,UAAU,EAAE,CAAC,CAAC,SAAS;wBACvB,UAAU,EAAE,CAAC,CAAC,SAAS;qBACxB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YACD,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC;gBAChC,KAAK,CAAC,IAAI,CAAC;oBACT,EAAE,EAAE,EAAE,CAAC,EAAE;oBACT,IAAI,EAAE,cAAc;oBACpB,UAAU,EAAE,CAAC,CAAC,EAAE;oBAChB,aAAa,EAAE,CAAC,CAAC,KAAK;oBACtB,IAAI,EAAE,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC;oBAClD,IAAI,EAAE,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC;oBAC/C,OAAO,EAAE,EAAE,CAAC,OAAO;oBACnB,UAAU,EAAE,IAAI;oBAChB,YAAY,EAAE,IAAI;oBAClB,UAAU,EAAE,EAAE,CAAC,SAAS;oBACxB,UAAU,EAAE,EAAE,CAAC,SAAS;iBACzB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC;QAClD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QAC/B,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;IAC/D,CAAC;IAED,2BAA2B;IAE3B,8CAA8C;IAC9C,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,KAAK,GAAG,EAAE;QACpC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,IAAI,CAAC,CAAC;YAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;QAClE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAE/B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ;aAC3B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;aACvF,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;aACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACT,MAAM,MAAM,GAA4B,EAAE,CAAC;YAC3C,IAAI,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC5D,IAAI,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAChE,OAAO;gBACL,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,cAAc,EAAE,MAAM;gBACtB,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,QAAQ,EAAE,CAAC,CAAC,OAAO;aACpB,CAAC;QACJ,CAAC,CAAC,CAAC;QAEL,MAAM,UAAU,GAAyB,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAoB,EAAE,CAAC;QAClC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC9B,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;gBAC7B,IAAI,UAAU,CAAC,MAAM,GAAG,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;oBAClE,UAAU,CAAC,IAAI,CAAC;wBACd,EAAE,EAAE,CAAC,CAAC,EAAE;wBACR,UAAU,EAAE,CAAC,CAAC,EAAE;wBAChB,aAAa,EAAE,CAAC,CAAC,KAAK;wBACtB,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC;wBACjD,IAAI,EAAE,CAAC,CAAC,IAAI;wBACZ,cAAc,EAAE,CAAC,MAAM,CAAC;qBACzB,CAAC,CAAC;gBACL,CAAC;gBACD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;oBACxB,IAAI,KAAK,CAAC,MAAM,GAAG,KAAK,IAAI,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;wBAChE,KAAK,CAAC,IAAI,CAAC;4BACT,EAAE,EAAE,CAAC,CAAC,EAAE;4BACR,IAAI,EAAE,gBAAgB;4BACtB,UAAU,EAAE,CAAC,CAAC,EAAE;4BAChB,aAAa,EAAE,CAAC,CAAC,KAAK;4BACtB,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC;4BACjD,OAAO,EAAE,CAAC,CAAC,OAAO;4BAClB,UAAU,EAAE,CAAC,CAAC,IAAI;4BAClB,cAAc,EAAE,CAAC,SAAS,CAAC;yBAC5B,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;YACD,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC;gBAChC,IAAI,KAAK,CAAC,MAAM,GAAG,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;oBACjE,KAAK,CAAC,IAAI,CAAC;wBACT,EAAE,EAAE,EAAE,CAAC,EAAE;wBACT,IAAI,EAAE,cAAc;wBACpB,UAAU,EAAE,CAAC,CAAC,EAAE;wBAChB,aAAa,EAAE,CAAC,CAAC,KAAK;wBACtB,IAAI,EAAE,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC;wBAClD,OAAO,EAAE,EAAE,CAAC,OAAO;wBACnB,UAAU,EAAE,IAAI;wBAChB,cAAc,EAAE,CAAC,SAAS,CAAC;qBAC5B,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IAChD,CAAC;IAED,2BAA2B;IAE3B,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,YAAY,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;QAC5D,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;QAEnC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC9B,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAC1B,YAAY,CAAC,MAAM,EAAE,CAAC;gBACtB,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE;oBAAE,WAAW,EAAE,CAAC;YAClE,CAAC;iBAAM,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAClC,YAAY,CAAC,OAAO,EAAE,CAAC;YACzB,CAAC;iBAAM,IAAI,CAAC,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBACnC,YAAY,CAAC,QAAQ,EAAE,CAAC;YAC1B,CAAC;YACD,IAAI,CAAC,CAAC,OAAO;gBAAE,YAAY,EAAE,CAAC;YAC9B,IAAI,CAAC,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;gBACzB,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC7D,CAAC;YACD,mEAAmE;YACnE,UAAU,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;QAClE,CAAC;QAED,IAAI,KAAK,GAAG,YAAY,CAAC,QAAQ,GAAG,GAAG,GAAG,YAAY,CAAC,OAAO,GAAG,GAAG,GAAG,UAAU,GAAG,KAAK,CAAC;QAC1F,IAAI,KAAK,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,GAAG,CAAC,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAAC;YAAE,KAAK,GAAG,GAAG,CAAC;QAExF,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CACzC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,EACpE,CAAC,CACF,CAAC;QACF,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAClF,MAAM,eAAe,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAEnF,OAAO;YACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,oBAAoB,EAAE,IAAI,CAAC,UAAU;YACrC,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM;YACpC,aAAa,EAAE,YAAY;YAC3B,gBAAgB,EAAE,eAAe;YACjC,WAAW,EAAE;gBACX,eAAe,EAAE,cAAc;gBAC/B,aAAa,EAAE,YAAY;gBAC3B,KAAK,EAAE,cAAc,GAAG,YAAY;aACrC;YACD,OAAO,EAAE,YAAY;YACrB,SAAS,EAAE,QAAQ,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI;YACpF,eAAe,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG;YAC9C,YAAY,EAAE,WAAW;SAC1B,CAAC;IACJ,CAAC;IAED,2BAA2B;IAE3B,iDAAiD;IACzC,cAAc,CAAC,QAAuB,EAAE,UAAmB;QACjE,IAAI,CAAC,UAAU;YAAE,OAAO,QAAQ,CAAC;QACjC,MAAM,CAAC,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC1C,MAAM,OAAO,GACX,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC;YACzD,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;YAClD,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC;gBAC9D,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;gBACrD,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAClE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,YAAY,UAAU,uBAAuB,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,UAAU,CAAC,CAAc,EAAE,EAAU;QAC3C,MAAM,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACxE,OAAO;YACL,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,aAAa,EAAE,CAAC,CAAC,YAAY;YAC7B,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,QAAQ,EAAE,CAAC,CAAC,OAAO;YACnB,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;YAC1C,YAAY,EAAE,CAAC,CAAC,UAAU;YAC1B,cAAc,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI;YACvE,cAAc,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI;YACpE,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,gBAAgB,EAAE,CAAC,CAAC,UAAU,CAAC,MAAM;YACrC,WAAW,EAAE,UAAU;YACvB,mBAAmB,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;SAC3C,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,34 @@
1
+ import type { CloudProvider } from '../cloud/types.js';
2
+ import { type Snapshot } from './types.js';
3
+ /** 毫秒时间戳 → 指定时区的 yyyy-MM-dd。 */
4
+ export declare function calendarDateFor(millis: number, timezone: string): string;
5
+ /** 毫秒时间戳 → 指定时区的 HH:mm。 */
6
+ export declare function timeLabelFor(millis: number, timezone: string): string;
7
+ export declare function isValidDate(date: string): boolean;
8
+ /** yyyy-MM-dd → UTC 当日零点的毫秒数(纯日历运算,不受本机时区影响)。 */
9
+ export declare function dateToMs(date: string): number;
10
+ /** 日期加/减 N 天(DST 无关:纯日历日)。 */
11
+ export declare function addDays(date: string, days: number): string;
12
+ export declare function todayIn(timezone: string): string;
13
+ export interface SnapshotFetcherOptions {
14
+ timezone: string;
15
+ /** 缓存目录(~/.gudong-content);不传则不缓存 */
16
+ cacheDir?: string;
17
+ }
18
+ export declare class SnapshotFetcher {
19
+ private readonly cloud;
20
+ private readonly opts;
21
+ private readonly cachePath;
22
+ constructor(cloud: CloudProvider, opts: SnapshotFetcherOptions);
23
+ /**
24
+ * 拉取快照:先列 rootDir 拿 etag/lastModified 指纹,命中缓存直接用,
25
+ * 否则下载解析。快照文件缺失时抛出带引导语义的错误。
26
+ */
27
+ fetch(): Promise<Snapshot>;
28
+ private optsDirLabel;
29
+ private fingerprint;
30
+ private fingerprintOf;
31
+ private loadCache;
32
+ private saveCache;
33
+ }
34
+ export declare function isMissingObjectError(e: unknown): boolean;
@@ -0,0 +1,138 @@
1
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { SNAPSHOT_FILE } from '../cloud/factory.js';
4
+ import { parseSnapshot } from './types.js';
5
+ // ---------- 日期时间助手(与 App 设备本地自然日对齐,时区可配) ----------
6
+ /** 毫秒时间戳 → 指定时区的 yyyy-MM-dd。 */
7
+ export function calendarDateFor(millis, timezone) {
8
+ const parts = new Intl.DateTimeFormat('en-CA', {
9
+ timeZone: timezone,
10
+ year: 'numeric',
11
+ month: '2-digit',
12
+ day: '2-digit',
13
+ }).format(new Date(millis));
14
+ return parts; // en-CA 直接给 yyyy-MM-dd
15
+ }
16
+ /** 毫秒时间戳 → 指定时区的 HH:mm。 */
17
+ export function timeLabelFor(millis, timezone) {
18
+ return new Intl.DateTimeFormat('en-GB', {
19
+ timeZone: timezone,
20
+ hour: '2-digit',
21
+ minute: '2-digit',
22
+ hour12: false,
23
+ }).format(new Date(millis));
24
+ }
25
+ export function isValidDate(date) {
26
+ return /^\d{4}-\d{2}-\d{2}$/.test(date) && !Number.isNaN(Date.parse(`${date}T00:00:00Z`));
27
+ }
28
+ const DAY_MS = 86_400_000;
29
+ /** yyyy-MM-dd → UTC 当日零点的毫秒数(纯日历运算,不受本机时区影响)。 */
30
+ export function dateToMs(date) {
31
+ return Date.parse(`${date}T00:00:00Z`);
32
+ }
33
+ /** 日期加/减 N 天(DST 无关:纯日历日)。 */
34
+ export function addDays(date, days) {
35
+ return new Date(dateToMs(date) + days * DAY_MS).toISOString().slice(0, 10);
36
+ }
37
+ export function todayIn(timezone) {
38
+ return calendarDateFor(Date.now(), timezone);
39
+ }
40
+ export class SnapshotFetcher {
41
+ cloud;
42
+ opts;
43
+ cachePath;
44
+ constructor(cloud, opts) {
45
+ this.cloud = cloud;
46
+ this.opts = opts;
47
+ this.cachePath = opts.cacheDir ? join(opts.cacheDir, 'cache-snapshot.json') : null;
48
+ }
49
+ /**
50
+ * 拉取快照:先列 rootDir 拿 etag/lastModified 指纹,命中缓存直接用,
51
+ * 否则下载解析。快照文件缺失时抛出带引导语义的错误。
52
+ */
53
+ async fetch() {
54
+ const key = SNAPSHOT_FILE;
55
+ let remote;
56
+ try {
57
+ const files = await this.cloud.listFiles('');
58
+ remote = files.find((f) => f.id === key.replace(/\.json$/, ''));
59
+ }
60
+ catch {
61
+ // 列目录失败不致命——直接尝试下载
62
+ }
63
+ const cache = this.loadCache();
64
+ if (remote && cache && this.fingerprint(remote) === this.fingerprintOf(cache)) {
65
+ return cache.snapshot;
66
+ }
67
+ let buf;
68
+ try {
69
+ buf = await this.cloud.read(key);
70
+ }
71
+ catch (e) {
72
+ if (cache && isMissingObjectError(e)) {
73
+ // 云端被清空但本地有缓存:用缓存并提示可能过期
74
+ process.stderr.write('[warn] 云端快照不存在,使用本地缓存(可能在 App 侧重新配置过云盘目录)\n');
75
+ return cache.snapshot;
76
+ }
77
+ throw new Error(`读取云端快照失败:${e.message}。` +
78
+ `请确认「好内容」App 已配置云盘同步并推送过快照(rootDir=${this.optsDirLabel()})`);
79
+ }
80
+ let parsedJson;
81
+ try {
82
+ parsedJson = JSON.parse(buf.toString('utf-8'));
83
+ }
84
+ catch {
85
+ throw new Error('云端快照不是合法 JSON(可能上传中断),请稍后在 App 里重新推送');
86
+ }
87
+ const snapshot = parseSnapshot(parsedJson);
88
+ this.saveCache({
89
+ savedAt: Date.now(),
90
+ etag: remote?.etag,
91
+ lastModified: remote?.lastModified?.getTime(),
92
+ snapshot,
93
+ });
94
+ return snapshot;
95
+ }
96
+ optsDirLabel() {
97
+ return this.cachePath ? '(见 ~/.gudong-content/config.json 的 root_dir)' : '';
98
+ }
99
+ fingerprint(f) {
100
+ return `${f.etag ?? ''}|${f.lastModified?.getTime() ?? ''}`;
101
+ }
102
+ fingerprintOf(c) {
103
+ return `${c.etag ?? ''}|${c.lastModified ?? ''}`;
104
+ }
105
+ loadCache() {
106
+ if (!this.cachePath || !existsSync(this.cachePath))
107
+ return null;
108
+ try {
109
+ const parsed = JSON.parse(readFileSync(this.cachePath, 'utf-8'));
110
+ if (parsed && parsed.snapshot && Array.isArray(parsed.snapshot.articles))
111
+ return parsed;
112
+ }
113
+ catch {
114
+ // 缓存损坏就当没有
115
+ }
116
+ return null;
117
+ }
118
+ saveCache(cache) {
119
+ if (!this.cachePath)
120
+ return;
121
+ try {
122
+ writeFileSync(this.cachePath, JSON.stringify(cache), 'utf-8');
123
+ }
124
+ catch {
125
+ // 缓存失败不影响功能
126
+ }
127
+ }
128
+ }
129
+ export function isMissingObjectError(e) {
130
+ const err = e;
131
+ const status = err.status ?? err.statusCode;
132
+ if (status === 404)
133
+ return true;
134
+ if (err.code === 'NoSuchKey' || err.code === 'ENOENT' || err.code === 'NotFound')
135
+ return true;
136
+ return (err.message ?? '').toLowerCase().includes('not found');
137
+ }
138
+ //# sourceMappingURL=snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/content/snapshot.ts"],"names":[],"mappings":"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"}