pixivflow 2.12.2 → 2.13.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/README.md +12 -4
- package/dist/commands/DoctorCommand.d.ts +20 -0
- package/dist/commands/DoctorCommand.d.ts.map +1 -0
- package/dist/commands/DoctorCommand.js +107 -0
- package/dist/commands/DoctorCommand.js.map +1 -0
- package/dist/commands/ReconcileCommand.d.ts +24 -0
- package/dist/commands/ReconcileCommand.d.ts.map +1 -0
- package/dist/commands/ReconcileCommand.js +70 -0
- package/dist/commands/ReconcileCommand.js.map +1 -0
- package/dist/commands/SchedulerCommand.d.ts.map +1 -1
- package/dist/commands/SchedulerCommand.js +5 -0
- package/dist/commands/SchedulerCommand.js.map +1 -1
- package/dist/commands/SchedulerRunOnceCommand.d.ts.map +1 -1
- package/dist/commands/SchedulerRunOnceCommand.js +6 -0
- package/dist/commands/SchedulerRunOnceCommand.js.map +1 -1
- package/dist/commands/index.d.ts +3 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +7 -1
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/scheduler-runtime.d.ts +10 -1
- package/dist/commands/scheduler-runtime.d.ts.map +1 -1
- package/dist/commands/scheduler-runtime.js +169 -47
- package/dist/commands/scheduler-runtime.js.map +1 -1
- package/dist/config/types.d.ts +10 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/delivery/DeliveryAck.d.ts +70 -0
- package/dist/delivery/DeliveryAck.d.ts.map +1 -0
- package/dist/delivery/DeliveryAck.js +72 -0
- package/dist/delivery/DeliveryAck.js.map +1 -0
- package/dist/delivery/DeliveryDispatcher.js +1 -1
- package/dist/delivery/DeliveryDispatcher.js.map +1 -1
- package/dist/delivery/DeliveryService.d.ts +74 -0
- package/dist/delivery/DeliveryService.d.ts.map +1 -0
- package/dist/delivery/DeliveryService.js +207 -0
- package/dist/delivery/DeliveryService.js.map +1 -0
- package/dist/delivery/HttpMultipartDelivery.d.ts +10 -1
- package/dist/delivery/HttpMultipartDelivery.d.ts.map +1 -1
- package/dist/delivery/HttpMultipartDelivery.js +40 -65
- package/dist/delivery/HttpMultipartDelivery.js.map +1 -1
- package/dist/delivery/LegacyOutboxMigration.d.ts +21 -0
- package/dist/delivery/LegacyOutboxMigration.d.ts.map +1 -0
- package/dist/delivery/LegacyOutboxMigration.js +125 -0
- package/dist/delivery/LegacyOutboxMigration.js.map +1 -0
- package/dist/delivery/OutboxWorker.d.ts +69 -0
- package/dist/delivery/OutboxWorker.d.ts.map +1 -0
- package/dist/delivery/OutboxWorker.js +194 -0
- package/dist/delivery/OutboxWorker.js.map +1 -0
- package/dist/delivery/types.d.ts +11 -0
- package/dist/delivery/types.d.ts.map +1 -1
- package/dist/download/DownloadManager.d.ts +17 -12
- package/dist/download/DownloadManager.d.ts.map +1 -1
- package/dist/download/DownloadManager.js +31 -53
- package/dist/download/DownloadManager.js.map +1 -1
- package/dist/download/NovelDownloader.d.ts +3 -1
- package/dist/download/NovelDownloader.d.ts.map +1 -1
- package/dist/download/NovelDownloader.js +30 -2
- package/dist/download/NovelDownloader.js.map +1 -1
- package/dist/download/handlers/IllustrationTargetHandler.d.ts +18 -8
- package/dist/download/handlers/IllustrationTargetHandler.d.ts.map +1 -1
- package/dist/download/handlers/IllustrationTargetHandler.js +100 -74
- package/dist/download/handlers/IllustrationTargetHandler.js.map +1 -1
- package/dist/download/handlers/NovelTargetHandler.d.ts +8 -8
- package/dist/download/handlers/NovelTargetHandler.d.ts.map +1 -1
- package/dist/download/handlers/NovelTargetHandler.js +64 -83
- package/dist/download/handlers/NovelTargetHandler.js.map +1 -1
- package/dist/download/plan/DownloadPlanner.d.ts +6 -1
- package/dist/download/plan/DownloadPlanner.d.ts.map +1 -1
- package/dist/download/plan/DownloadPlanner.js +28 -5
- package/dist/download/plan/DownloadPlanner.js.map +1 -1
- package/dist/notification/NotificationPolicy.d.ts +38 -0
- package/dist/notification/NotificationPolicy.d.ts.map +1 -0
- package/dist/notification/NotificationPolicy.js +89 -0
- package/dist/notification/NotificationPolicy.js.map +1 -0
- package/dist/package.json +1 -1
- package/dist/pixiv/PixivClient.d.ts +4 -0
- package/dist/pixiv/PixivClient.d.ts.map +1 -1
- package/dist/pixiv/PixivClient.js +15 -2
- package/dist/pixiv/PixivClient.js.map +1 -1
- package/dist/pixiv/RateLimitCoordinator.d.ts +30 -0
- package/dist/pixiv/RateLimitCoordinator.d.ts.map +1 -0
- package/dist/pixiv/RateLimitCoordinator.js +66 -0
- package/dist/pixiv/RateLimitCoordinator.js.map +1 -0
- package/dist/pixiv/client/PixivApiCore.d.ts +5 -0
- package/dist/pixiv/client/PixivApiCore.d.ts.map +1 -1
- package/dist/pixiv/client/PixivApiCore.js +31 -9
- package/dist/pixiv/client/PixivApiCore.js.map +1 -1
- package/dist/scheduler/ScheduleTriggerServer.d.ts +7 -0
- package/dist/scheduler/ScheduleTriggerServer.d.ts.map +1 -1
- package/dist/scheduler/ScheduleTriggerServer.js +17 -0
- package/dist/scheduler/ScheduleTriggerServer.js.map +1 -1
- package/dist/scheduler/SlotCoordinator.d.ts +15 -0
- package/dist/scheduler/SlotCoordinator.d.ts.map +1 -1
- package/dist/scheduler/SlotCoordinator.js +74 -0
- package/dist/scheduler/SlotCoordinator.js.map +1 -1
- package/dist/scheduler/SlotStateMachine.d.ts +32 -0
- package/dist/scheduler/SlotStateMachine.d.ts.map +1 -0
- package/dist/scheduler/SlotStateMachine.js +65 -0
- package/dist/scheduler/SlotStateMachine.js.map +1 -0
- package/dist/scheduler/TargetOutcome.d.ts +63 -0
- package/dist/scheduler/TargetOutcome.d.ts.map +1 -0
- package/dist/scheduler/TargetOutcome.js +28 -0
- package/dist/scheduler/TargetOutcome.js.map +1 -0
- package/dist/storage/Database.d.ts +16 -0
- package/dist/storage/Database.d.ts.map +1 -1
- package/dist/storage/Database.js +29 -0
- package/dist/storage/Database.js.map +1 -1
- package/dist/storage/DatabaseMigration.d.ts.map +1 -1
- package/dist/storage/DatabaseMigration.js +66 -0
- package/dist/storage/DatabaseMigration.js.map +1 -1
- package/dist/storage/repositories/DeliveryRepository.d.ts +73 -0
- package/dist/storage/repositories/DeliveryRepository.d.ts.map +1 -0
- package/dist/storage/repositories/DeliveryRepository.js +148 -0
- package/dist/storage/repositories/DeliveryRepository.js.map +1 -0
- package/dist/storage/repositories/MetadataRepository.d.ts +27 -0
- package/dist/storage/repositories/MetadataRepository.d.ts.map +1 -0
- package/dist/storage/repositories/MetadataRepository.js +58 -0
- package/dist/storage/repositories/MetadataRepository.js.map +1 -0
- package/dist/storage/repositories/OutboxRepository.d.ts +70 -0
- package/dist/storage/repositories/OutboxRepository.d.ts.map +1 -0
- package/dist/storage/repositories/OutboxRepository.js +187 -0
- package/dist/storage/repositories/OutboxRepository.js.map +1 -0
- package/dist/storage/repositories/SlotRepository.d.ts +26 -1
- package/dist/storage/repositories/SlotRepository.d.ts.map +1 -1
- package/dist/storage/repositories/SlotRepository.js +76 -2
- package/dist/storage/repositories/SlotRepository.js.map +1 -1
- package/dist/webui/package.json +1 -1
- package/package.json +1 -1
- package/dist/delivery/DeliveryOutbox.d.ts +0 -43
- package/dist/delivery/DeliveryOutbox.d.ts.map +0 -1
- package/dist/delivery/DeliveryOutbox.js +0 -293
- package/dist/delivery/DeliveryOutbox.js.map +0 -1
|
@@ -11,9 +11,7 @@ const DownloadExecutor_1 = require("./exec/DownloadExecutor");
|
|
|
11
11
|
const ErrorRecovery_1 = require("./recovery/ErrorRecovery");
|
|
12
12
|
const DownloadPipeline_1 = require("./pipeline/DownloadPipeline");
|
|
13
13
|
const errors_1 = require("../utils/errors");
|
|
14
|
-
const
|
|
15
|
-
const DeliveryOutbox_1 = require("../delivery/DeliveryOutbox");
|
|
16
|
-
const node_path_1 = require("node:path");
|
|
14
|
+
const DeliveryService_1 = require("../delivery/DeliveryService");
|
|
17
15
|
const IllustrationTargetHandler_1 = require("./handlers/IllustrationTargetHandler");
|
|
18
16
|
const NovelTargetHandler_1 = require("./handlers/NovelTargetHandler");
|
|
19
17
|
const createTopicPipeline_1 = require("../topic/createTopicPipeline");
|
|
@@ -56,19 +54,19 @@ class DownloadManager {
|
|
|
56
54
|
// Cooperative cancellation state (see cancel())
|
|
57
55
|
cancelled = false;
|
|
58
56
|
cancelReason = '';
|
|
59
|
-
|
|
60
|
-
/** Per-target outcome hook (
|
|
57
|
+
deliveryService;
|
|
58
|
+
/** Per-target TYPED outcome hook (the Slot ledger maps it to cell transitions). */
|
|
61
59
|
onTargetOutcome = null;
|
|
62
|
-
/**
|
|
63
|
-
onWorkLocked = null;
|
|
64
|
-
/** Register a callback fired after each target with the error if it failed. */
|
|
60
|
+
/** Register a callback fired after each target with its explicit business outcome. */
|
|
65
61
|
setTargetOutcomeHook(fn) {
|
|
66
62
|
this.onTargetOutcome = fn;
|
|
67
63
|
}
|
|
68
|
-
/**
|
|
69
|
-
|
|
70
|
-
this.
|
|
64
|
+
/** Expose delivery service for handlers (preflight + intent creation). */
|
|
65
|
+
get deliveries() {
|
|
66
|
+
return this.deliveryService;
|
|
71
67
|
}
|
|
68
|
+
/** Slot context propagated to delivery templates/outcomes for scheduled runs. */
|
|
69
|
+
slotContext;
|
|
72
70
|
/**
|
|
73
71
|
* Request cooperative cancellation of the current run. In-flight item
|
|
74
72
|
* finishes; no further targets/items are started. runAllTargets() will
|
|
@@ -92,8 +90,10 @@ class DownloadManager {
|
|
|
92
90
|
const downloadConcurrency = config.download?.concurrency || 3;
|
|
93
91
|
const storagePath = config.storage?.illustrationDirectory ?? config.storage?.downloadDirectory ?? './downloads';
|
|
94
92
|
this.illustrationDownloader = new IllustrationDownloader_1.IllustrationDownloader(client, database, fileService, downloadConcurrency, storagePath);
|
|
95
|
-
this.novelDownloader = new NovelDownloader_1.NovelDownloader(client, database, fileService);
|
|
96
|
-
this.planner = new DownloadPlanner_1.DownloadPlanner(database
|
|
93
|
+
this.novelDownloader = new NovelDownloader_1.NovelDownloader(client, database, fileService, database);
|
|
94
|
+
this.planner = new DownloadPlanner_1.DownloadPlanner(database, {
|
|
95
|
+
deliveredIds: (target, type, ids) => this.deliveryService.deliveredIds(target, type, ids),
|
|
96
|
+
});
|
|
97
97
|
this.executor = new DownloadExecutor_1.DownloadExecutor();
|
|
98
98
|
const downloadConfig = config.download ?? {};
|
|
99
99
|
const maxRetries = downloadConfig.maxRetries ?? 3;
|
|
@@ -112,19 +112,16 @@ class DownloadManager {
|
|
|
112
112
|
recovery: this.errorRecovery,
|
|
113
113
|
isCancelled: () => this.cancelled,
|
|
114
114
|
});
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
retryMaxDelayMs: config.delivery?.outboxRetryMaxMs,
|
|
120
|
-
onDeliver: (artifact, target) => this.onWorkLocked?.(artifact, target),
|
|
121
|
-
});
|
|
115
|
+
// Delivery ledger + SQLite outbox. Requires the concrete Database (with the
|
|
116
|
+
// deliveries/outbox repositories). Unit tests pass plain mock databases; in
|
|
117
|
+
// that case delivery dedupe/enqueue is simply inactive.
|
|
118
|
+
this.deliveryService = new DeliveryService_1.DeliveryService(database);
|
|
122
119
|
// One lazily-created topic pipeline (shared resolver/cache) for this run.
|
|
123
120
|
const topicFactory = (0, createTopicPipeline_1.createTopicPipelineFactory)(client, typeof database.getDatabasePath === 'function'
|
|
124
121
|
? database
|
|
125
122
|
: config.storage?.databasePath, config.download?.requestDelay ?? 500);
|
|
126
|
-
this.illustrationHandler = new IllustrationTargetHandler_1.IllustrationTargetHandler(client, database, this.rankingService, this.illustrationDownloader, this.pipeline, this.
|
|
127
|
-
this.novelHandler = new NovelTargetHandler_1.NovelTargetHandler(client, database, this.rankingService, this.pipeline, this.novelDownloader, this.
|
|
123
|
+
this.illustrationHandler = new IllustrationTargetHandler_1.IllustrationTargetHandler(client, database, this.rankingService, this.illustrationDownloader, this.pipeline, topicFactory, this.deliveryService);
|
|
124
|
+
this.novelHandler = new NovelTargetHandler_1.NovelTargetHandler(client, database, this.rankingService, this.pipeline, this.novelDownloader, topicFactory, this.deliveryService);
|
|
128
125
|
}
|
|
129
126
|
setProgressCallback(callback) {
|
|
130
127
|
this.progressReporter.setCallback(callback);
|
|
@@ -133,13 +130,8 @@ class DownloadManager {
|
|
|
133
130
|
await this.fileService.initialise();
|
|
134
131
|
}
|
|
135
132
|
async runAllTargets() {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
logger_1.logger.info('Processed pending deliveries', { ...pending });
|
|
139
|
-
}
|
|
140
|
-
else if (pending.deferred > 0) {
|
|
141
|
-
logger_1.logger.debug('Pending deliveries remain in backoff', { ...pending });
|
|
142
|
-
}
|
|
133
|
+
// NOTE: pending deliveries are pumped independently by the OutboxWorker,
|
|
134
|
+
// not piggy-backed onto the next download run. No retryPending() here.
|
|
143
135
|
const totalTargets = this.config.targets.length;
|
|
144
136
|
if (totalTargets === 0) {
|
|
145
137
|
this.progressReporter.complete(0, '所有目标处理完成');
|
|
@@ -156,14 +148,18 @@ class DownloadManager {
|
|
|
156
148
|
const targetName = target.filterTag || target.tag || 'unknown';
|
|
157
149
|
this.updateProgress(currentTarget, totalTargets, `处理目标: ${targetName} (${target.type})`);
|
|
158
150
|
try {
|
|
159
|
-
await this.dispatchTarget(target);
|
|
160
|
-
this.onTargetOutcome?.(target);
|
|
151
|
+
const outcome = await this.dispatchTarget(target);
|
|
152
|
+
this.onTargetOutcome?.(target, outcome);
|
|
153
|
+
if (outcome.kind === 'failed' && !outcome.retryable) {
|
|
154
|
+
errors.push({ target: `${targetName} (${target.type})`, error: outcome.error });
|
|
155
|
+
}
|
|
161
156
|
}
|
|
162
157
|
catch (error) {
|
|
158
|
+
// A raw escape means an unexpected/retryable infrastructure failure.
|
|
163
159
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
164
160
|
errors.push({ target: `${targetName} (${target.type})`, error: errorMessage });
|
|
165
161
|
logger_1.logger.error(`Target ${targetName} (${target.type}) failed, continuing with next target`, { error: errorMessage });
|
|
166
|
-
this.onTargetOutcome?.(target, errorMessage);
|
|
162
|
+
this.onTargetOutcome?.(target, { kind: 'failed', retryable: true, error: errorMessage });
|
|
167
163
|
}
|
|
168
164
|
}
|
|
169
165
|
if (this.cancelled) {
|
|
@@ -184,35 +180,17 @@ class DownloadManager {
|
|
|
184
180
|
async dispatchTarget(target) {
|
|
185
181
|
switch (target.type) {
|
|
186
182
|
case 'illustration':
|
|
187
|
-
await this.illustrationHandler.handle(target);
|
|
188
|
-
break;
|
|
183
|
+
return await this.illustrationHandler.handle(target);
|
|
189
184
|
case 'novel':
|
|
190
|
-
await this.novelHandler.handle(target);
|
|
191
|
-
break;
|
|
185
|
+
return await this.novelHandler.handle(target);
|
|
192
186
|
default:
|
|
193
187
|
logger_1.logger.warn(`Unsupported target type ${target.type}`);
|
|
188
|
+
return { kind: 'failed', retryable: false, error: `unsupported target type ${target.type}` };
|
|
194
189
|
}
|
|
195
190
|
}
|
|
196
191
|
updateProgress(current, total, message) {
|
|
197
192
|
this.progressReporter.update(current, total, message);
|
|
198
193
|
}
|
|
199
|
-
buildProxyUrl(network) {
|
|
200
|
-
const proxy = network?.proxy;
|
|
201
|
-
if (!proxy?.enabled) {
|
|
202
|
-
return undefined;
|
|
203
|
-
}
|
|
204
|
-
const protocol = proxy.protocol ?? 'http';
|
|
205
|
-
if (protocol !== 'http' && protocol !== 'https') {
|
|
206
|
-
logger_1.logger.warn(`HTTP multipart delivery does not support ${protocol} proxy through undici; delivering directly`);
|
|
207
|
-
return undefined;
|
|
208
|
-
}
|
|
209
|
-
const url = new URL(`${protocol}://${proxy.host}:${proxy.port}`);
|
|
210
|
-
if (proxy.username)
|
|
211
|
-
url.username = proxy.username;
|
|
212
|
-
if (proxy.password)
|
|
213
|
-
url.password = proxy.password;
|
|
214
|
-
return url.toString();
|
|
215
|
-
}
|
|
216
194
|
}
|
|
217
195
|
exports.DownloadManager = DownloadManager;
|
|
218
196
|
//# sourceMappingURL=DownloadManager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DownloadManager.js","sourceRoot":"","sources":["../../src/download/DownloadManager.ts"],"names":[],"mappings":";;;AACA,sCAAmC;AAKnC,qDAAkD;AAClD,qEAAkE;AAClE,uDAAoD;AACpD,gEAA6D;AAC7D,4DAAyD;AACzD,8DAA2D;AAC3D,4DAAuF;AACvF,kEAA+D;AAC/D,4CAA0D;AAC1D,
|
|
1
|
+
{"version":3,"file":"DownloadManager.js","sourceRoot":"","sources":["../../src/download/DownloadManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,11 +4,13 @@ import { IDatabase } from '../interfaces/IDatabase';
|
|
|
4
4
|
import { IFileService } from '../interfaces/IFileService';
|
|
5
5
|
import { PixivNovel } from '../pixiv/PixivClient';
|
|
6
6
|
import { DownloadedArtifact } from '../delivery/types';
|
|
7
|
+
import type { Database } from '../storage/Database';
|
|
7
8
|
export declare class NovelDownloader {
|
|
8
9
|
private readonly client;
|
|
9
10
|
private readonly database;
|
|
10
11
|
private readonly fileService;
|
|
11
|
-
|
|
12
|
+
private readonly metadataDb?;
|
|
13
|
+
constructor(client: IPixivClient, database: IDatabase, fileService: IFileService, metadataDb?: Database | undefined);
|
|
12
14
|
download(novel: PixivNovel, tag: string, target: TargetConfig): Promise<DownloadedArtifact | undefined>;
|
|
13
15
|
}
|
|
14
16
|
//# sourceMappingURL=NovelDownloader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NovelDownloader.d.ts","sourceRoot":"","sources":["../../src/download/NovelDownloader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"NovelDownloader.d.ts","sourceRoot":"","sources":["../../src/download/NovelDownloader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;gBAHX,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,SAAS,EACnB,WAAW,EAAE,YAAY,EACzB,UAAU,CAAC,EAAE,QAAQ,YAAA;IAGlC,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;CAuM9G"}
|
|
@@ -36,16 +36,38 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.NovelDownloader = void 0;
|
|
37
37
|
const logger_1 = require("../logger");
|
|
38
38
|
const language_detection_1 = require("../utils/language-detection");
|
|
39
|
+
const LANGUAGE_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
39
40
|
class NovelDownloader {
|
|
40
41
|
client;
|
|
41
42
|
database;
|
|
42
43
|
fileService;
|
|
43
|
-
|
|
44
|
+
metadataDb;
|
|
45
|
+
constructor(client, database, fileService, metadataDb) {
|
|
44
46
|
this.client = client;
|
|
45
47
|
this.database = database;
|
|
46
48
|
this.fileService = fileService;
|
|
49
|
+
this.metadataDb = metadataDb;
|
|
47
50
|
}
|
|
48
51
|
async download(novel, tag, target) {
|
|
52
|
+
// Metadata cache: language filtering otherwise pulls FULL text per candidate
|
|
53
|
+
// on every fallback run. A cached classification short-circuits to the same
|
|
54
|
+
// skip decision without any Pixiv request (bounded detail/text fetch volume).
|
|
55
|
+
if (target.languageFilter && this.metadataDb) {
|
|
56
|
+
const cached = this.metadataDb?.metadata?.fresh?.(String(novel.id), 'novel', LANGUAGE_CACHE_TTL_MS);
|
|
57
|
+
if (cached?.language) {
|
|
58
|
+
const isChinese = cached.language.includes('Chinese') || cached.language.startsWith('zh');
|
|
59
|
+
const wanted = (target.languageFilter === 'chinese' && isChinese) ||
|
|
60
|
+
(target.languageFilter === 'non-chinese' && !isChinese);
|
|
61
|
+
if (!wanted) {
|
|
62
|
+
const reason = target.languageFilter === 'chinese' ? 'not Chinese' : 'is Chinese';
|
|
63
|
+
logger_1.logger.info(`Novel ${novel.id} language filter from cache (${cached.language}): ${reason}`, {
|
|
64
|
+
novelId: novel.id, filter: target.languageFilter, cached: true,
|
|
65
|
+
});
|
|
66
|
+
throw new Error(`Novel ${novel.id} skipped: language filter mismatch (cached: ${cached.language}, required: ${target.languageFilter})`);
|
|
67
|
+
}
|
|
68
|
+
// Cached match: still need full text to deliver; fall through to fetch.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
49
71
|
const { novel: detail, tags } = await this.client.getNovelDetailWithTags(novel.id);
|
|
50
72
|
// getNovelText resolves to the API envelope { novel_text }; unwrap it here,
|
|
51
73
|
// otherwise the object gets stringified to "[object Object]" and the
|
|
@@ -65,7 +87,7 @@ class NovelDownloader {
|
|
|
65
87
|
let detectedLang = null;
|
|
66
88
|
if (enableDetection) {
|
|
67
89
|
const fullContent = `${detail.title}\n${text}`;
|
|
68
|
-
detectedLang = (0, language_detection_1.detectLanguage)(fullContent);
|
|
90
|
+
detectedLang = await (0, language_detection_1.detectLanguage)(fullContent);
|
|
69
91
|
if (detectedLang) {
|
|
70
92
|
logger_1.logger.info(`Detected language for novel ${detail.id}: ${detectedLang.name} (${detectedLang.code})`, {
|
|
71
93
|
novelId: detail.id,
|
|
@@ -73,6 +95,12 @@ class NovelDownloader {
|
|
|
73
95
|
code: detectedLang.code,
|
|
74
96
|
isChinese: detectedLang.isChinese,
|
|
75
97
|
});
|
|
98
|
+
this.metadataDb?.metadata?.put?.(String(detail.id), 'novel', {
|
|
99
|
+
language: detectedLang.name,
|
|
100
|
+
xRestrict: detail.x_restrict ?? novel.x_restrict ?? null,
|
|
101
|
+
publishedAt: detail.create_date ?? novel.create_date ?? null,
|
|
102
|
+
title: detail.title,
|
|
103
|
+
});
|
|
76
104
|
}
|
|
77
105
|
else {
|
|
78
106
|
logger_1.logger.debug(`Language detection inconclusive for novel ${detail.id} (text may be too short)`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NovelDownloader.js","sourceRoot":"","sources":["../../src/download/NovelDownloader.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,sCAAmC;AAMnC,oEAA6D;
|
|
1
|
+
{"version":3,"file":"NovelDownloader.js","sourceRoot":"","sources":["../../src/download/NovelDownloader.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,8 @@ import { IDatabase } from '../../interfaces/IDatabase';
|
|
|
4
4
|
import { RankingService } from '../RankingService';
|
|
5
5
|
import { IllustrationDownloader } from '../IllustrationDownloader';
|
|
6
6
|
import { DownloadPipeline } from '../pipeline/DownloadPipeline';
|
|
7
|
-
import {
|
|
7
|
+
import { DeliveryService } from '../../delivery/DeliveryService';
|
|
8
|
+
import { TargetOutcome } from '../../scheduler/TargetOutcome';
|
|
8
9
|
import type { TopicPipelineFactory } from '../../topic/createTopicPipeline';
|
|
9
10
|
export declare class IllustrationTargetHandler {
|
|
10
11
|
private readonly client;
|
|
@@ -12,10 +13,15 @@ export declare class IllustrationTargetHandler {
|
|
|
12
13
|
private readonly rankingService;
|
|
13
14
|
private readonly illustrationDownloader;
|
|
14
15
|
private readonly pipeline;
|
|
15
|
-
private readonly deliveryOutbox?;
|
|
16
16
|
private readonly topicPipelineFactory?;
|
|
17
|
-
|
|
18
|
-
handle(
|
|
17
|
+
private readonly deliveryService?;
|
|
18
|
+
/** Outcomes produced during this handle() call (deliveries + terminal non-matches). */
|
|
19
|
+
private outcomes;
|
|
20
|
+
constructor(client: IPixivClient, database: IDatabase, rankingService: RankingService, illustrationDownloader: IllustrationDownloader, pipeline: DownloadPipeline, topicPipelineFactory?: TopicPipelineFactory | undefined, deliveryService?: DeliveryService | undefined);
|
|
21
|
+
handle(target: TargetConfig): Promise<TargetOutcome>;
|
|
22
|
+
/** Reduce the outcomes collected while processing one target to one cell result. */
|
|
23
|
+
private summarize;
|
|
24
|
+
private classifyError;
|
|
19
25
|
private fetchIllustrations;
|
|
20
26
|
private fetchTopicIllustrations;
|
|
21
27
|
private handleTopicWithLookback;
|
|
@@ -25,14 +31,18 @@ export declare class IllustrationTargetHandler {
|
|
|
25
31
|
private fetchSearchIllustrations;
|
|
26
32
|
private handleDownloadResult;
|
|
27
33
|
private handleZeroDownloads;
|
|
28
|
-
private handleError;
|
|
29
|
-
/** Notify the review group when a target's download hard-fails (not just a no-match). */
|
|
30
|
-
private notifyDownloadFailure;
|
|
31
|
-
private notifyNoMatch;
|
|
32
34
|
private handleSingleIllustration;
|
|
33
35
|
private handleUserIllustrations;
|
|
34
36
|
private sortByPopularityAndLog;
|
|
35
37
|
private logError;
|
|
36
38
|
private downloadAndDeliver;
|
|
39
|
+
/**
|
|
40
|
+
* Turn a downloaded artifact into the target's business outcome. In cache
|
|
41
|
+
* delivery mode the DeliveryService creates the durable intent atomically and
|
|
42
|
+
* the result is 'delivery_pending' (NOT submitted — the OutboxWorker confirms
|
|
43
|
+
* the ACK). Persistent/download-only runs are 'stored'.
|
|
44
|
+
*/
|
|
45
|
+
private recordArtifactOutcome;
|
|
46
|
+
private executionContextFields;
|
|
37
47
|
}
|
|
38
48
|
//# sourceMappingURL=IllustrationTargetHandler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IllustrationTargetHandler.d.ts","sourceRoot":"","sources":["../../../src/download/handlers/IllustrationTargetHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAKhE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"IllustrationTargetHandler.d.ts","sourceRoot":"","sources":["../../../src/download/handlers/IllustrationTargetHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAKhE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAG5E,qBAAa,yBAAyB;IAKlC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAC;IACtC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;IAVnC,uFAAuF;IACvF,OAAO,CAAC,QAAQ,CAAuB;gBAGpB,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,SAAS,EACnB,cAAc,EAAE,cAAc,EAC9B,sBAAsB,EAAE,sBAAsB,EAC9C,QAAQ,EAAE,gBAAgB,EAC1B,oBAAoB,CAAC,EAAE,oBAAoB,YAAA,EAC3C,eAAe,CAAC,EAAE,eAAe,YAAA;IAG9C,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;IAmC1D,oFAAoF;IACpF,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,aAAa;YAgBP,kBAAkB;YAWlB,uBAAuB;YA2BvB,uBAAuB;IAoCrC,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,QAAQ;YAMF,yBAAyB;YA4CzB,wBAAwB;IAwBtC,OAAO,CAAC,oBAAoB;IAuC5B,OAAO,CAAC,mBAAmB;YAmDb,wBAAwB;YAuBxB,uBAAuB;IAiCrC,OAAO,CAAC,sBAAsB;IAgC9B,OAAO,CAAC,QAAQ;YAmBF,kBAAkB;IAiBhC;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IA4B7B,OAAO,CAAC,sBAAsB;CAY/B"}
|
|
@@ -12,25 +12,28 @@ class IllustrationTargetHandler {
|
|
|
12
12
|
rankingService;
|
|
13
13
|
illustrationDownloader;
|
|
14
14
|
pipeline;
|
|
15
|
-
deliveryOutbox;
|
|
16
15
|
topicPipelineFactory;
|
|
17
|
-
|
|
16
|
+
deliveryService;
|
|
17
|
+
/** Outcomes produced during this handle() call (deliveries + terminal non-matches). */
|
|
18
|
+
outcomes = [];
|
|
19
|
+
constructor(client, database, rankingService, illustrationDownloader, pipeline, topicPipelineFactory, deliveryService) {
|
|
18
20
|
this.client = client;
|
|
19
21
|
this.database = database;
|
|
20
22
|
this.rankingService = rankingService;
|
|
21
23
|
this.illustrationDownloader = illustrationDownloader;
|
|
22
24
|
this.pipeline = pipeline;
|
|
23
|
-
this.deliveryOutbox = deliveryOutbox;
|
|
24
25
|
this.topicPipelineFactory = topicPipelineFactory;
|
|
26
|
+
this.deliveryService = deliveryService;
|
|
25
27
|
}
|
|
26
28
|
async handle(target) {
|
|
29
|
+
this.outcomes = [];
|
|
27
30
|
if (target.illustId) {
|
|
28
31
|
await this.handleSingleIllustration(target);
|
|
29
|
-
return;
|
|
32
|
+
return this.summarize(target);
|
|
30
33
|
}
|
|
31
34
|
if (target.userId) {
|
|
32
35
|
await this.handleUserIllustrations(target);
|
|
33
|
-
return;
|
|
36
|
+
return this.summarize(target);
|
|
34
37
|
}
|
|
35
38
|
const mode = target.mode || 'search';
|
|
36
39
|
const displayTag = (0, target_label_1.getTargetLabel)(target);
|
|
@@ -38,18 +41,50 @@ class IllustrationTargetHandler {
|
|
|
38
41
|
try {
|
|
39
42
|
if (mode === 'topic') {
|
|
40
43
|
await this.handleTopicWithLookback(target, displayTag);
|
|
41
|
-
return;
|
|
44
|
+
return this.summarize(target);
|
|
42
45
|
}
|
|
43
46
|
const illusts = await this.fetchIllustrations(target, mode);
|
|
44
47
|
const result = await this.pipeline.run(illusts, target, 'illustration', (illust, tag) => this.downloadAndDeliver(illust, tag, target));
|
|
45
48
|
this.handleDownloadResult(result, target, mode, illusts.length);
|
|
49
|
+
return this.summarize(target);
|
|
46
50
|
}
|
|
47
51
|
catch (error) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
return this.classifyError(error, displayTag, mode, target);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Reduce the outcomes collected while processing one target to one cell result. */
|
|
56
|
+
summarize(target) {
|
|
57
|
+
const submitted = this.outcomes.find((o) => o.kind === 'submitted');
|
|
58
|
+
if (submitted)
|
|
59
|
+
return submitted;
|
|
60
|
+
const stored = this.outcomes.find((o) => o.kind === 'stored');
|
|
61
|
+
if (stored)
|
|
62
|
+
return stored;
|
|
63
|
+
const pending = this.outcomes.find((o) => o.kind === 'delivery_pending');
|
|
64
|
+
if (pending)
|
|
65
|
+
return pending;
|
|
66
|
+
const duplicate = this.outcomes.find((o) => o.kind === 'duplicate');
|
|
67
|
+
if (duplicate)
|
|
68
|
+
return duplicate;
|
|
69
|
+
const failed = this.outcomes.find((o) => o.kind === 'failed');
|
|
70
|
+
if (failed)
|
|
71
|
+
return failed;
|
|
72
|
+
return { kind: 'no_candidate', reason: 'no matching illustration after filtering/dedupe' };
|
|
73
|
+
}
|
|
74
|
+
classifyError(error, displayTag, mode, target) {
|
|
75
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
76
|
+
this.database.logExecution(displayTag, 'illustration', 'failed', message);
|
|
77
|
+
logger_1.logger.error(`Illustration ${mode === 'ranking' ? 'ranking' : 'tag'} ${displayTag} failed`, {
|
|
78
|
+
error: message,
|
|
79
|
+
errorType: error instanceof Error ? error.constructor.name : typeof error,
|
|
80
|
+
});
|
|
81
|
+
// Explicit no-candidate signals are business outcomes, not failures.
|
|
82
|
+
if (/no matching|all .*filtered|no_candidate/i.test(message)) {
|
|
83
|
+
return { kind: 'no_candidate', reason: message };
|
|
52
84
|
}
|
|
85
|
+
// Network/transient => retryable so the SAME work resumes on next trigger.
|
|
86
|
+
const retryable = error instanceof errors_1.NetworkError || /timeout|econn|enotfound|etimed|429|5dd/i.test(message);
|
|
87
|
+
return { kind: 'failed', retryable, error: message };
|
|
53
88
|
}
|
|
54
89
|
async fetchIllustrations(target, mode) {
|
|
55
90
|
if (mode === 'topic') {
|
|
@@ -106,8 +141,7 @@ class IllustrationTargetHandler {
|
|
|
106
141
|
const message = `No matching illustrations found after checking ${checkedDays.length} day(s): ${checkedDays.join(', ')}`;
|
|
107
142
|
this.database.logExecution(displayTag, 'illustration', 'success', message);
|
|
108
143
|
logger_1.logger.warn(`Illustration topic ${displayTag} produced no matching result`, { checkedDays });
|
|
109
|
-
|
|
110
|
-
throw new Error(message);
|
|
144
|
+
this.outcomes.push({ kind: 'no_candidate', reason: message });
|
|
111
145
|
}
|
|
112
146
|
resolveTopicDay(target) {
|
|
113
147
|
return target.date === 'TODAY'
|
|
@@ -224,68 +258,17 @@ class IllustrationTargetHandler {
|
|
|
224
258
|
`多为网络/Pixiv 瞬时错误,下次计划会自动重试;同一作品持续失败请查日志(可能为已删除/私密/R-18 权限)。`
|
|
225
259
|
: `No new illustrations for ${tagForLog}: requested ${targetLimit}, but no matching illustrations were found.`;
|
|
226
260
|
this.database.logExecution(tagForLog, 'illustration', 'failed', errorMessage);
|
|
227
|
-
logger_1.logger.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const causeMsg = error.cause instanceof Error ? error.cause.message : String(error.cause);
|
|
235
|
-
errorMessage = `${errorMessage} (原因: ${causeMsg})`;
|
|
236
|
-
}
|
|
237
|
-
if (error instanceof errors_1.NetworkError && error.url) {
|
|
238
|
-
errorMessage = `${errorMessage} [URL: ${error.url}]`;
|
|
239
|
-
}
|
|
240
|
-
this.database.logExecution(displayTag, 'illustration', 'failed', errorMessage);
|
|
241
|
-
logger_1.logger.error(`Illustration ${mode === 'ranking' ? 'ranking' : 'tag'} ${displayTag} failed`, {
|
|
242
|
-
error: errorMessage,
|
|
243
|
-
errorType: error instanceof Error ? error.constructor.name : typeof error,
|
|
244
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
245
|
-
});
|
|
246
|
-
await this.notifyDownloadFailure(target, displayTag, errorMessage);
|
|
247
|
-
throw error;
|
|
248
|
-
}
|
|
249
|
-
/** Notify the review group when a target's download hard-fails (not just a no-match). */
|
|
250
|
-
async notifyDownloadFailure(target, label, errorMessage) {
|
|
251
|
-
if (!target?.delivery?.target?.trim())
|
|
252
|
-
return;
|
|
253
|
-
if (!this.deliveryOutbox) {
|
|
254
|
-
logger_1.logger.warn('Download-failure notification requested but delivery outbox is unavailable');
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
const truncated = errorMessage.length > 200 ? `${errorMessage.slice(0, 200)}…` : errorMessage;
|
|
258
|
-
const text = [
|
|
259
|
-
'❌ PixivFlow 本次下载失败',
|
|
260
|
-
`目标:${target.id || label}(${label} · 插画)`,
|
|
261
|
-
`错误:${truncated}`,
|
|
262
|
-
'处理结果:本次未投递;可点击「🔄 重抓/换一张」重试,或等待下次定时任务。',
|
|
263
|
-
].join('\n');
|
|
264
|
-
const key = `pixivflow:hard-fail:${target.id || label}:illustration:${(0, pixiv_date_utils_1.getTodayDate)()}`;
|
|
265
|
-
try {
|
|
266
|
-
await this.deliveryOutbox.notifyNoMatch(target, text, key);
|
|
267
|
-
}
|
|
268
|
-
catch (notifyError) {
|
|
269
|
-
logger_1.logger.warn('Failed to send download-failure notification', { label, errorMessage, notifyError });
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
async notifyNoMatch(target, label, checkedDays) {
|
|
273
|
-
if (target.noMatchPolicy?.notify !== true || !this.deliveryOutbox)
|
|
274
|
-
return;
|
|
275
|
-
const text = [
|
|
276
|
-
'⚠️ PixivFlow 本次没有可投稿内容',
|
|
277
|
-
`目标:${target.id || label}(${label} · 插画)`,
|
|
278
|
-
`检查日期:${checkedDays.join('、')}`,
|
|
279
|
-
'处理结果:未创建空投稿;下次定时任务会继续正常执行。',
|
|
280
|
-
].join('\n');
|
|
281
|
-
const key = `pixivflow:no-match:${target.id || label}:illustration:${checkedDays[0]}:${checkedDays.at(-1)}`;
|
|
282
|
-
try {
|
|
283
|
-
await this.deliveryOutbox.notifyNoMatch(target, text, key);
|
|
284
|
-
}
|
|
285
|
-
catch (error) {
|
|
286
|
-
logger_1.logger.warn('Failed to send no-match notification', { label, error });
|
|
261
|
+
logger_1.logger.warn(`Illustration ${mode === 'ranking' ? 'ranking' : 'tag'} ${tagForLog}: ${errorMessage}`);
|
|
262
|
+
// Skipped due to transient errors => no terminal submitted; caller retries.
|
|
263
|
+
this.outcomes.push({
|
|
264
|
+
kind: 'failed',
|
|
265
|
+
retryable: skipped > 0,
|
|
266
|
+
error: errorMessage,
|
|
267
|
+
});
|
|
287
268
|
}
|
|
288
269
|
}
|
|
270
|
+
// Notifications are produced centrally by NotificationPolicy (slot-scoped
|
|
271
|
+
// keys), not per-handler with date-based keys. See noteOutcome/sendSlotSummary.
|
|
289
272
|
async handleSingleIllustration(target) {
|
|
290
273
|
const illustId = Number(target.illustId);
|
|
291
274
|
if (!Number.isFinite(illustId)) {
|
|
@@ -376,9 +359,52 @@ class IllustrationTargetHandler {
|
|
|
376
359
|
aiMetadataCheck: target.aiMetadataCheck === true,
|
|
377
360
|
maxPageCount: target.maxPageCount,
|
|
378
361
|
});
|
|
379
|
-
if (artifact
|
|
380
|
-
|
|
362
|
+
if (!artifact)
|
|
363
|
+
return;
|
|
364
|
+
this.recordArtifactOutcome(artifact, target);
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Turn a downloaded artifact into the target's business outcome. In cache
|
|
368
|
+
* delivery mode the DeliveryService creates the durable intent atomically and
|
|
369
|
+
* the result is 'delivery_pending' (NOT submitted — the OutboxWorker confirms
|
|
370
|
+
* the ACK). Persistent/download-only runs are 'stored'.
|
|
371
|
+
*/
|
|
372
|
+
recordArtifactOutcome(artifact, target) {
|
|
373
|
+
const isDelivery = target.storageMode === 'cache' && target.delivery?.target?.trim();
|
|
374
|
+
if (!isDelivery || !this.deliveryService) {
|
|
375
|
+
this.outcomes.push({ kind: 'stored', workId: artifact.pixivId, workType: artifact.type });
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
// Pre-lock delivery dedupe (after selection): if the ledger already knows it,
|
|
379
|
+
// that is a confirmed fact, not a new submission.
|
|
380
|
+
const slotId = target.delivery?.executionContext?.slotId;
|
|
381
|
+
if (this.deliveryService.isAlreadyDelivered(target.delivery.target, artifact.type, artifact.pixivId)) {
|
|
382
|
+
this.outcomes.push({ kind: 'duplicate', workId: artifact.pixivId, reason: 'already delivered to target (ledger)' });
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const res = this.deliveryService.enqueue(artifact, target, {
|
|
386
|
+
slotId,
|
|
387
|
+
fields: target.delivery?.fields,
|
|
388
|
+
extraContext: this.executionContextFields(target),
|
|
389
|
+
});
|
|
390
|
+
if (res.duplicate) {
|
|
391
|
+
this.outcomes.push({ kind: 'duplicate', workId: artifact.pixivId, reason: 'already delivered to target (ledger)' });
|
|
381
392
|
}
|
|
393
|
+
else {
|
|
394
|
+
this.outcomes.push({ kind: 'delivery_pending', workId: artifact.pixivId, workType: artifact.type, deliveryId: res.deliveryId });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
executionContextFields(target) {
|
|
398
|
+
const ec = target.delivery;
|
|
399
|
+
return {
|
|
400
|
+
scheduleId: ec?.executionContext?.scheduleId,
|
|
401
|
+
executionId: ec?.executionContext?.slotId,
|
|
402
|
+
occurrenceAt: ec?.executionContext?.occurrenceAtIso,
|
|
403
|
+
triggerSource: ec?.executionContext?.triggerSource,
|
|
404
|
+
slotId: ec?.slotContext?.slotId ?? ec?.executionContext?.slotId,
|
|
405
|
+
slotName: ec?.slotContext?.slotName ?? ec?.executionContext?.slotName,
|
|
406
|
+
slotDate: ec?.slotContext?.slotDate ?? ec?.executionContext?.slotDate,
|
|
407
|
+
};
|
|
382
408
|
}
|
|
383
409
|
}
|
|
384
410
|
exports.IllustrationTargetHandler = IllustrationTargetHandler;
|