pixivflow 2.25.0 → 2.26.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.
- package/dist/download/NovelDownloader.js +31 -1
- package/dist/package.json +1 -1
- package/dist/utils/zip.d.ts +14 -0
- package/dist/utils/zip.js +44 -0
- package/dist/version.js +1 -1
- package/dist/webui/package.json +1 -1
- package/package.json +3 -2
|
@@ -38,6 +38,7 @@ const logger_1 = require("../logger");
|
|
|
38
38
|
const node_path_1 = require("node:path");
|
|
39
39
|
const language_detection_1 = require("../utils/language-detection");
|
|
40
40
|
const novelMarkers_1 = require("./novelMarkers");
|
|
41
|
+
const zip_1 = require("../utils/zip");
|
|
41
42
|
const LANGUAGE_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
42
43
|
class NovelDownloader {
|
|
43
44
|
client;
|
|
@@ -188,9 +189,11 @@ class NovelDownloader {
|
|
|
188
189
|
// Rich-media markdown sidecar (RFC 1 Phase 2): same path as the .txt
|
|
189
190
|
// (compat format stays), inline images become relative 
|
|
190
191
|
// refs so a later TelePress/TelePost phase can render the Telegraph page.
|
|
192
|
+
let richMediaPath;
|
|
191
193
|
if (assets.some((a) => a.status === 'downloaded')) {
|
|
192
194
|
try {
|
|
193
195
|
const mdPath = await this.fileService.saveText(`${header}\n${(0, novelMarkers_1.renderNovelMarkdown)(text, assets)}`, fileName.replace(/\.txt$/, '.md'), metadata);
|
|
196
|
+
richMediaPath = mdPath;
|
|
194
197
|
logger_1.logger.info(`Saved novel ${detail.id} rich-media markdown sidecar`, { filePath: mdPath });
|
|
195
198
|
}
|
|
196
199
|
catch (error) {
|
|
@@ -243,6 +246,33 @@ class NovelDownloader {
|
|
|
243
246
|
catch (error) {
|
|
244
247
|
logger_1.logger.warn(`Failed to save metadata for novel ${detail.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
245
248
|
}
|
|
249
|
+
// Rich-media ZIP archive (Phase 3): a save-only download package carrying
|
|
250
|
+
// txt + md + metadata + images. The md stays an internal render input, never
|
|
251
|
+
// a user-facing attachment; txt remains the authoritative/compat file.
|
|
252
|
+
let archivePath;
|
|
253
|
+
if (assets.some((a) => a.status === 'downloaded')) {
|
|
254
|
+
const zipName = this.fileService.sanitizeFileName(`${detail.id}_${detail.title}.zip`);
|
|
255
|
+
const dest = (0, node_path_1.join)((0, node_path_1.dirname)(filePath), zipName);
|
|
256
|
+
const entries = [
|
|
257
|
+
{ name: fileName, sourcePath: filePath },
|
|
258
|
+
];
|
|
259
|
+
if (richMediaPath)
|
|
260
|
+
entries.push({ name: `${fileName.replace(/\.txt$/, '.md')}`, sourcePath: richMediaPath });
|
|
261
|
+
if (metadataPath)
|
|
262
|
+
entries.push({ name: `${fileName}.json`, sourcePath: metadataPath });
|
|
263
|
+
for (const a of assets) {
|
|
264
|
+
if (a.status === 'downloaded' && a.localPath) {
|
|
265
|
+
entries.push({ name: `images/${(0, node_path_1.basename)(a.localPath)}`, sourcePath: a.localPath });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
archivePath = await (0, zip_1.createZipArchive)(dest, entries);
|
|
270
|
+
logger_1.logger.info(`Saved novel ${detail.id} zip archive`, { filePath: archivePath });
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
logger_1.logger.warn(`Failed to save zip archive for novel ${detail.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
246
276
|
this.database.insertDownload({
|
|
247
277
|
pixivId: String(detail.id),
|
|
248
278
|
type: 'novel',
|
|
@@ -264,7 +294,7 @@ class NovelDownloader {
|
|
|
264
294
|
type: 'novel',
|
|
265
295
|
title: detail.title,
|
|
266
296
|
tags: tags.map((item) => item.name).filter(Boolean),
|
|
267
|
-
files: [filePath],
|
|
297
|
+
files: archivePath ? [filePath, archivePath] : [filePath],
|
|
268
298
|
cleanupFiles: metadataPath ? [metadataPath] : [],
|
|
269
299
|
spoiler: (detail.x_restrict ?? 0) > 0,
|
|
270
300
|
xRestrict: detail.x_restrict,
|
package/dist/package.json
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ArchiveEntry {
|
|
2
|
+
/** Path the file is stored under inside the zip (e.g. "images/001.jpg"). */
|
|
3
|
+
name: string;
|
|
4
|
+
/** Absolute path of the file on disk. */
|
|
5
|
+
sourcePath: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Create a zip archive at `destPath` containing `entries` (order preserved).
|
|
9
|
+
* Returns destPath on success; on failure removes a partial destination so a
|
|
10
|
+
* caller never hands a half-written archive to a delivery. Pure FS helper — no
|
|
11
|
+
* domain logic.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createZipArchive(destPath: string, entries: ArchiveEntry[]): Promise<string>;
|
|
14
|
+
//# sourceMappingURL=zip.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createZipArchive = createZipArchive;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_fs_2 = require("node:fs");
|
|
6
|
+
const node_module_1 = require("node:module");
|
|
7
|
+
// archiver ships no matching TS factory types; bind it at runtime via
|
|
8
|
+
// createRequire and keep a minimal local contract (nothing Pixiv-specific).
|
|
9
|
+
const archiver = (0, node_module_1.createRequire)(__filename)('archiver');
|
|
10
|
+
/**
|
|
11
|
+
* Create a zip archive at `destPath` containing `entries` (order preserved).
|
|
12
|
+
* Returns destPath on success; on failure removes a partial destination so a
|
|
13
|
+
* caller never hands a half-written archive to a delivery. Pure FS helper — no
|
|
14
|
+
* domain logic.
|
|
15
|
+
*/
|
|
16
|
+
async function createZipArchive(destPath, entries) {
|
|
17
|
+
const output = (0, node_fs_1.createWriteStream)(destPath);
|
|
18
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
19
|
+
const settled = new Promise((resolve, reject) => {
|
|
20
|
+
output.on('close', () => resolve(destPath));
|
|
21
|
+
output.on('error', reject);
|
|
22
|
+
archive.on('error', reject);
|
|
23
|
+
});
|
|
24
|
+
try {
|
|
25
|
+
archive.pipe(output);
|
|
26
|
+
for (const e of entries) {
|
|
27
|
+
archive.file(e.sourcePath, { name: e.name });
|
|
28
|
+
}
|
|
29
|
+
await archive.finalize();
|
|
30
|
+
await settled;
|
|
31
|
+
await node_fs_2.promises.access(destPath);
|
|
32
|
+
return destPath;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
try {
|
|
36
|
+
await node_fs_2.promises.rm(destPath, { force: true });
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
/* best-effort cleanup */
|
|
40
|
+
}
|
|
41
|
+
throw new Error(`Failed to create zip archive: ${error instanceof Error ? error.message : String(error)}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=zip.js.map
|
package/dist/version.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BUILD = void 0;
|
|
4
4
|
// GENERATED by scripts/write-version.js — do not edit manually.
|
|
5
|
-
exports.BUILD = { version: '2.
|
|
5
|
+
exports.BUILD = { version: '2.26.0', commit: '299b0e29cecb' };
|
|
6
6
|
//# sourceMappingURL=version.js.map
|
package/dist/webui/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixivflow",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.26.0",
|
|
4
|
+
"description": "🎨 Pixiv 下载、筛选与自动收集工具 - 批量下载插画和小说、按标签/热度/日期筛选、定时任务与可靠 HTTP 交付 | Pixiv downloader and automation toolkit with filtering, scheduling and reliable HTTP delivery",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/redtidev1918/PixivFlow.git"
|
|
@@ -116,6 +116,7 @@
|
|
|
116
116
|
},
|
|
117
117
|
"dependencies": {
|
|
118
118
|
"@redtidev/pixiv-client": "0.1.0",
|
|
119
|
+
"archiver": "^7.0.1",
|
|
119
120
|
"axios": "^1.20.0",
|
|
120
121
|
"cors": "^2.8.6",
|
|
121
122
|
"cron-parser": "^4.9.0",
|