pixivflow 2.39.0 → 2.40.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/delivery/TelePressRichNovel.js +46 -16
- package/dist/download/NovelDownloader.js +9 -2
- package/dist/download/novelMarkers.d.ts +14 -0
- package/dist/download/novelMarkers.js +48 -0
- package/dist/package.json +1 -1
- package/dist/version.js +1 -1
- package/dist/webui/package.json +1 -1
- package/package.json +1 -1
|
@@ -40,6 +40,7 @@ const fs = __importStar(require("node:fs"));
|
|
|
40
40
|
const path = __importStar(require("node:path"));
|
|
41
41
|
const node_crypto_1 = require("node:crypto");
|
|
42
42
|
const MediaAsset_1 = require("../domain/media/MediaAsset");
|
|
43
|
+
const novelMarkers_1 = require("../download/novelMarkers");
|
|
43
44
|
const logger_1 = require("../logger");
|
|
44
45
|
const redact_1 = require("../utils/redact");
|
|
45
46
|
/**
|
|
@@ -59,6 +60,26 @@ function interpolateEnv(value) {
|
|
|
59
60
|
function isDownloadedPixivAsset(asset) {
|
|
60
61
|
return Boolean(asset && asset.status === 'downloaded' && asset.url && asset.localPath);
|
|
61
62
|
}
|
|
63
|
+
function isPendingPixivAsset(asset) {
|
|
64
|
+
return Boolean(asset && asset.status === 'pending' && asset.url && asset.sourceId && !asset.localPath);
|
|
65
|
+
}
|
|
66
|
+
/** Build a canonical MediaAsset without requiring a local file. */
|
|
67
|
+
function mediaAssetFrom(asset, workId, localPath) {
|
|
68
|
+
const kind = asset.kind === 'uploadedimage' || asset.kind === 'pixivimage'
|
|
69
|
+
? asset.kind
|
|
70
|
+
: undefined;
|
|
71
|
+
const source = String(asset.url);
|
|
72
|
+
if (!source || !kind)
|
|
73
|
+
return undefined;
|
|
74
|
+
return (0, MediaAsset_1.buildMediaAsset)({
|
|
75
|
+
workId,
|
|
76
|
+
kind,
|
|
77
|
+
sourceId: asset.sourceId ? String(asset.sourceId) : undefined,
|
|
78
|
+
marker: asset.marker ? String(asset.marker) : undefined,
|
|
79
|
+
sourceUrl: source,
|
|
80
|
+
artifactId: localPath,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
62
83
|
/**
|
|
63
84
|
* Read the novel metadata sidecar into canonical `MediaAsset` values, plus the
|
|
64
85
|
* legacy `{local, source}` manifest projection (local is only a render hint for
|
|
@@ -76,24 +97,14 @@ function readNovelMediaAssets(artifact) {
|
|
|
76
97
|
const workId = meta.pixiv_id ? String(meta.pixiv_id) : artifact.pixivId;
|
|
77
98
|
const mediaAssets = [];
|
|
78
99
|
const manifest = [];
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
continue;
|
|
82
|
-
const kind = asset.kind === 'uploadedimage' || asset.kind === 'pixivimage'
|
|
83
|
-
? asset.kind
|
|
84
|
-
: undefined;
|
|
100
|
+
const downloaded = meta.assets.filter(isDownloadedPixivAsset);
|
|
101
|
+
for (const asset of downloaded) {
|
|
85
102
|
const source = String(asset.url);
|
|
86
103
|
const localPath = String(asset.localPath);
|
|
87
|
-
|
|
104
|
+
const media = mediaAssetFrom(asset, workId, localPath);
|
|
105
|
+
if (!media)
|
|
88
106
|
continue;
|
|
89
|
-
mediaAssets.push(
|
|
90
|
-
workId,
|
|
91
|
-
kind,
|
|
92
|
-
sourceId: asset.sourceId ? String(asset.sourceId) : undefined,
|
|
93
|
-
marker: asset.marker ? String(asset.marker) : undefined,
|
|
94
|
-
sourceUrl: source,
|
|
95
|
-
artifactId: localPath,
|
|
96
|
-
}));
|
|
107
|
+
mediaAssets.push(media);
|
|
97
108
|
manifest.push({
|
|
98
109
|
local: `images/${path.basename(localPath)}`,
|
|
99
110
|
source,
|
|
@@ -101,6 +112,25 @@ function readNovelMediaAssets(artifact) {
|
|
|
101
112
|
sourceUrl: source,
|
|
102
113
|
});
|
|
103
114
|
}
|
|
115
|
+
// On-demand previews: no local downloads for this work, but the metadata
|
|
116
|
+
// carries resolvable Pixiv CDN sources. Pass those through as pending
|
|
117
|
+
// references (no artifactId) so TelePress can proxy them without files.
|
|
118
|
+
if (mediaAssets.length === 0) {
|
|
119
|
+
for (const asset of meta.assets.filter(isPendingPixivAsset)) {
|
|
120
|
+
const source = String(asset.url);
|
|
121
|
+
const refName = (0, novelMarkers_1.novelReferenceFileName)(source, String(asset.sourceId));
|
|
122
|
+
const media = mediaAssetFrom(asset, workId);
|
|
123
|
+
if (!media)
|
|
124
|
+
continue;
|
|
125
|
+
mediaAssets.push(media);
|
|
126
|
+
manifest.push({
|
|
127
|
+
local: refName,
|
|
128
|
+
source,
|
|
129
|
+
assetId: mediaAssets[mediaAssets.length - 1]?.id,
|
|
130
|
+
sourceUrl: source,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
104
134
|
return { mediaAssets, manifest };
|
|
105
135
|
}
|
|
106
136
|
catch {
|
|
@@ -145,7 +175,7 @@ async function publishRichNovelPreview(artifact, options) {
|
|
|
145
175
|
const sources = findRichNovelSources(artifact);
|
|
146
176
|
if (!sources)
|
|
147
177
|
return { url: '', retryable: false, operatorHint: 'no_rich_novel_assets' };
|
|
148
|
-
if (sources.imagePaths.length === 0) {
|
|
178
|
+
if (sources.imagePaths.length === 0 && sources.manifest.length === 0) {
|
|
149
179
|
return { url: '', retryable: false, operatorHint: 'no_rich_novel_assets' };
|
|
150
180
|
}
|
|
151
181
|
const mdName = path.basename(sources.mdPath);
|
|
@@ -230,9 +230,16 @@ class NovelDownloader {
|
|
|
230
230
|
// (compat format stays), inline images become relative 
|
|
231
231
|
// refs so a later TelePress/TelePost phase can render the Telegraph page.
|
|
232
232
|
let richMediaPath;
|
|
233
|
-
|
|
233
|
+
const hasDownloadedImages = assets.some((a) => a.status === 'downloaded');
|
|
234
|
+
// On-demand mode (no local images) still emits a resolvable md sidecar from
|
|
235
|
+
// media references so TelePress can render via proxy without a download.
|
|
236
|
+
const hasResolvablePending = assets.some((a) => a.status === 'pending' && Boolean(a.url));
|
|
237
|
+
if (hasDownloadedImages || hasResolvablePending) {
|
|
234
238
|
try {
|
|
235
|
-
const
|
|
239
|
+
const mdContent = hasDownloadedImages
|
|
240
|
+
? (0, novelMarkers_1.renderNovelMarkdown)(text, assets)
|
|
241
|
+
: (0, novelMarkers_1.renderNovelMarkdownReference)(text, assets);
|
|
242
|
+
const mdPath = await this.fileService.saveText(`${header}\n${mdContent}`, fileName.replace(/\.txt$/, '.md'), metadata);
|
|
236
243
|
richMediaPath = mdPath;
|
|
237
244
|
logger_1.logger.info(`Saved novel ${detail.id} rich-media markdown sidecar`, { filePath: mdPath });
|
|
238
245
|
}
|
|
@@ -75,4 +75,18 @@ export declare function demo(): void;
|
|
|
75
75
|
* original text so nothing is silently dropped (partial success).
|
|
76
76
|
*/
|
|
77
77
|
export declare function renderNovelMarkdown(source: string, assets: NovelAsset[]): string;
|
|
78
|
+
/**
|
|
79
|
+
* Stable relative filename for an inline novel media reference without a local
|
|
80
|
+
* download. Uses the sourceId plus the URL extension so the manifest can point
|
|
81
|
+
* at a proxy URL while the rendered Markdown still resolves to
|
|
82
|
+
* `images/<sourceId>.jpg`.
|
|
83
|
+
*/
|
|
84
|
+
export declare function novelReferenceFileName(url: string | undefined, sourceId: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* Markdown sidecar for on-demand (non-downloading) previews. Renders
|
|
87
|
+
* `` for every resolvable asset so the manifest can be
|
|
88
|
+
* rehydrated by TelePress via proxy; markers without a resolvable URL stay as
|
|
89
|
+
* their original text.
|
|
90
|
+
*/
|
|
91
|
+
export declare function renderNovelMarkdownReference(source: string, assets: NovelAsset[]): string;
|
|
78
92
|
//# sourceMappingURL=novelMarkers.d.ts.map
|
|
@@ -4,6 +4,8 @@ exports.scanNovelMarkers = scanNovelMarkers;
|
|
|
4
4
|
exports.extractNovelAssets = extractNovelAssets;
|
|
5
5
|
exports.demo = demo;
|
|
6
6
|
exports.renderNovelMarkdown = renderNovelMarkdown;
|
|
7
|
+
exports.novelReferenceFileName = novelReferenceFileName;
|
|
8
|
+
exports.renderNovelMarkdownReference = renderNovelMarkdownReference;
|
|
7
9
|
const node_path_1 = require("node:path");
|
|
8
10
|
const linkRegex = /https?:\/\/\S+/;
|
|
9
11
|
function parseMarker(span) {
|
|
@@ -141,4 +143,50 @@ function renderNovelMarkdown(source, assets) {
|
|
|
141
143
|
})
|
|
142
144
|
.join('');
|
|
143
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Stable relative filename for an inline novel media reference without a local
|
|
148
|
+
* download. Uses the sourceId plus the URL extension so the manifest can point
|
|
149
|
+
* at a proxy URL while the rendered Markdown still resolves to
|
|
150
|
+
* `images/<sourceId>.jpg`.
|
|
151
|
+
*/
|
|
152
|
+
function novelReferenceFileName(url, sourceId) {
|
|
153
|
+
let ext = '';
|
|
154
|
+
if (url) {
|
|
155
|
+
try {
|
|
156
|
+
const path = new URL(url).pathname;
|
|
157
|
+
const match = /\.(jpg|jpeg|png|gif|webp)$/i.exec(path);
|
|
158
|
+
if (match)
|
|
159
|
+
ext = match[1].toLowerCase();
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// non-URL fall through to default ext
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const safeId = sourceId.replace(/[^\w.-]+/g, '_');
|
|
166
|
+
return `images/${safeId}${ext ? `.${ext}` : '.jpg'}`;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Markdown sidecar for on-demand (non-downloading) previews. Renders
|
|
170
|
+
* `` for every resolvable asset so the manifest can be
|
|
171
|
+
* rehydrated by TelePress via proxy; markers without a resolvable URL stay as
|
|
172
|
+
* their original text.
|
|
173
|
+
*/
|
|
174
|
+
function renderNovelMarkdownReference(source, assets) {
|
|
175
|
+
return scanNovelMarkers(source)
|
|
176
|
+
.map((marker) => {
|
|
177
|
+
if (marker.type === 'text')
|
|
178
|
+
return marker.value;
|
|
179
|
+
const asset = assets.find((a) => a.marker === marker.raw);
|
|
180
|
+
if (!asset)
|
|
181
|
+
return marker.raw;
|
|
182
|
+
if (asset.status === 'downloaded' && asset.localPath) {
|
|
183
|
+
return `(asset.localPath)})`;
|
|
184
|
+
}
|
|
185
|
+
if (asset.status === 'pending' && asset.url) {
|
|
186
|
+
return `})`;
|
|
187
|
+
}
|
|
188
|
+
return marker.raw;
|
|
189
|
+
})
|
|
190
|
+
.join('');
|
|
191
|
+
}
|
|
144
192
|
//# sourceMappingURL=novelMarkers.js.map
|
package/dist/package.json
CHANGED
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.40.0', commit: '8d72f5f5761a' };
|
|
6
6
|
//# sourceMappingURL=version.js.map
|
package/dist/webui/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixivflow",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.40.0",
|
|
4
4
|
"description": "🎨 Pixiv 下载、筛选与自动收集工具 - 批量下载插画和小说、按标签/热度/日期筛选、定时任务与可靠 HTTP 交付 | Pixiv downloader and automation toolkit with filtering, scheduling and reliable HTTP delivery",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|