koishi-plugin-aka-ai-generator 0.7.4 → 0.7.6
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/lib/index.js +188 -30
- package/lib/providers/yunwu-video.d.ts +5 -1
- package/lib/services/UserManager.d.ts +27 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -954,12 +954,12 @@ var YunwuVideoProvider = class {
|
|
|
954
954
|
logger?.error("API 返回错误状态", { status: response.status, error: errorMsg, response: response.data });
|
|
955
955
|
throw new Error(sanitizeString(errorMsg));
|
|
956
956
|
}
|
|
957
|
-
const taskId = response.id
|
|
957
|
+
const taskId = response.id;
|
|
958
958
|
if (!taskId) {
|
|
959
959
|
logger?.error("未能获取任务ID", { response });
|
|
960
960
|
throw new Error("未能获取任务ID,请检查 API 响应格式");
|
|
961
961
|
}
|
|
962
|
-
logger?.info("视频任务已创建", { taskId });
|
|
962
|
+
logger?.info("视频任务已创建", { taskId, status: response.status });
|
|
963
963
|
return taskId;
|
|
964
964
|
} catch (error) {
|
|
965
965
|
logger?.error("创建视频任务失败", { error: sanitizeError(error) });
|
|
@@ -979,12 +979,12 @@ var YunwuVideoProvider = class {
|
|
|
979
979
|
}
|
|
980
980
|
/**
|
|
981
981
|
* 查询任务状态
|
|
982
|
-
* API: GET /v1/video/{taskId}
|
|
982
|
+
* API: GET /v1/video/query?id={taskId}
|
|
983
983
|
*/
|
|
984
984
|
async queryTaskStatus(taskId) {
|
|
985
985
|
const { logger, ctx } = this.config;
|
|
986
986
|
try {
|
|
987
|
-
const endpoint = `/v1/video
|
|
987
|
+
const endpoint = `/v1/video/query?id=${encodeURIComponent(taskId)}`;
|
|
988
988
|
logger?.debug("查询任务状态", { taskId, endpoint });
|
|
989
989
|
const response = await ctx.http.get(
|
|
990
990
|
`${this.config.apiBase}${endpoint}`,
|
|
@@ -996,14 +996,14 @@ var YunwuVideoProvider = class {
|
|
|
996
996
|
timeout: this.config.apiTimeout * 1e3
|
|
997
997
|
}
|
|
998
998
|
);
|
|
999
|
-
const status = response.status ||
|
|
1000
|
-
const videoUrl = response.video_url ||
|
|
999
|
+
const status = response.status || "pending";
|
|
1000
|
+
const videoUrl = response.video_url || null;
|
|
1001
1001
|
return {
|
|
1002
1002
|
status,
|
|
1003
|
-
taskId,
|
|
1004
|
-
videoUrl,
|
|
1005
|
-
error: response.error
|
|
1006
|
-
progress: response.progress
|
|
1003
|
+
taskId: response.id || taskId,
|
|
1004
|
+
videoUrl: videoUrl || void 0,
|
|
1005
|
+
error: response.error,
|
|
1006
|
+
progress: response.progress
|
|
1007
1007
|
};
|
|
1008
1008
|
} catch (error) {
|
|
1009
1009
|
logger?.error("查询任务状态失败", { taskId, error: sanitizeError(error) });
|
|
@@ -1020,6 +1020,8 @@ var YunwuVideoProvider = class {
|
|
|
1020
1020
|
async pollTaskCompletion(taskId, maxWaitTime = 300, pollInterval = 3, onProgress) {
|
|
1021
1021
|
const { logger } = this.config;
|
|
1022
1022
|
const startTime = Date.now();
|
|
1023
|
+
let consecutiveFailures = 0;
|
|
1024
|
+
const maxConsecutiveFailures = 5;
|
|
1023
1025
|
while (true) {
|
|
1024
1026
|
const elapsed = (Date.now() - startTime) / 1e3;
|
|
1025
1027
|
if (elapsed > maxWaitTime) {
|
|
@@ -1027,21 +1029,50 @@ var YunwuVideoProvider = class {
|
|
|
1027
1029
|
throw new Error(`视频生成超时(已等待${Math.floor(elapsed)}秒),任务ID: ${taskId}
|
|
1028
1030
|
请使用"查询视频 ${taskId}"命令稍后查询结果`);
|
|
1029
1031
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1032
|
+
try {
|
|
1033
|
+
const status = await this.queryTaskStatus(taskId);
|
|
1034
|
+
consecutiveFailures = 0;
|
|
1035
|
+
logger?.debug("任务状态", { taskId, status: status.status, elapsed: Math.floor(elapsed) });
|
|
1036
|
+
if (onProgress) {
|
|
1037
|
+
await onProgress(status);
|
|
1038
|
+
}
|
|
1039
|
+
if (status.status === "completed" && status.videoUrl) {
|
|
1040
|
+
logger?.info("视频生成完成", { taskId, elapsed: Math.floor(elapsed) });
|
|
1041
|
+
return status.videoUrl;
|
|
1042
|
+
}
|
|
1043
|
+
if (status.status === "failed") {
|
|
1044
|
+
throw new Error(status.error || "视频生成失败");
|
|
1045
|
+
}
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
consecutiveFailures++;
|
|
1048
|
+
logger?.warn("查询任务状态失败,继续重试", {
|
|
1049
|
+
taskId,
|
|
1050
|
+
consecutiveFailures,
|
|
1051
|
+
maxConsecutiveFailures,
|
|
1052
|
+
error: sanitizeError(error),
|
|
1053
|
+
elapsed: Math.floor(elapsed)
|
|
1054
|
+
});
|
|
1055
|
+
if (consecutiveFailures >= maxConsecutiveFailures) {
|
|
1056
|
+
logger?.error("连续查询失败次数过多,终止轮询", {
|
|
1057
|
+
taskId,
|
|
1058
|
+
consecutiveFailures,
|
|
1059
|
+
elapsed: Math.floor(elapsed)
|
|
1060
|
+
});
|
|
1061
|
+
throw new Error(`查询任务状态连续失败 ${consecutiveFailures} 次,任务ID: ${taskId}
|
|
1062
|
+
请稍后使用"查询视频 ${taskId}"命令手动查询结果`);
|
|
1063
|
+
}
|
|
1064
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval * 1e3 * 2));
|
|
1065
|
+
continue;
|
|
1041
1066
|
}
|
|
1042
1067
|
await new Promise((resolve) => setTimeout(resolve, pollInterval * 1e3));
|
|
1043
1068
|
}
|
|
1044
1069
|
}
|
|
1070
|
+
/**
|
|
1071
|
+
* 等待指定任务完成并返回视频 URL(供外部在拿到 taskId 后自行控制扣费/提示)
|
|
1072
|
+
*/
|
|
1073
|
+
async waitForVideo(taskId, maxWaitTime = 300) {
|
|
1074
|
+
return await this.pollTaskCompletion(taskId, maxWaitTime);
|
|
1075
|
+
}
|
|
1045
1076
|
/**
|
|
1046
1077
|
* 生成视频(主入口)
|
|
1047
1078
|
*/
|
|
@@ -1052,7 +1083,7 @@ var YunwuVideoProvider = class {
|
|
|
1052
1083
|
const taskId = await this.createVideoTask(prompt, imageUrl, options);
|
|
1053
1084
|
logger?.debug("任务已创建,等待 3 秒后开始查询", { taskId });
|
|
1054
1085
|
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
1055
|
-
const videoUrl = await this.
|
|
1086
|
+
const videoUrl = await this.waitForVideo(taskId, maxWaitTime);
|
|
1056
1087
|
logger?.info("视频生成完成", { taskId, videoUrl });
|
|
1057
1088
|
return videoUrl;
|
|
1058
1089
|
} catch (error) {
|
|
@@ -1132,11 +1163,14 @@ var UserManager = class {
|
|
|
1132
1163
|
dataFile;
|
|
1133
1164
|
backupFile;
|
|
1134
1165
|
rechargeHistoryFile;
|
|
1166
|
+
pendingVideoTasksFile;
|
|
1135
1167
|
logger;
|
|
1136
1168
|
dataLock = new AsyncLock();
|
|
1137
1169
|
historyLock = new AsyncLock();
|
|
1170
|
+
pendingLock = new AsyncLock();
|
|
1138
1171
|
// 内存缓存
|
|
1139
1172
|
usersCache = null;
|
|
1173
|
+
pendingVideoCache = null;
|
|
1140
1174
|
activeTasks = /* @__PURE__ */ new Map();
|
|
1141
1175
|
// userId -> requestId
|
|
1142
1176
|
rateLimitMap = /* @__PURE__ */ new Map();
|
|
@@ -1151,6 +1185,7 @@ var UserManager = class {
|
|
|
1151
1185
|
this.dataFile = (0, import_path.join)(this.dataDir, "users_data.json");
|
|
1152
1186
|
this.backupFile = (0, import_path.join)(this.dataDir, "users_data.json.backup");
|
|
1153
1187
|
this.rechargeHistoryFile = (0, import_path.join)(this.dataDir, "recharge_history.json");
|
|
1188
|
+
this.pendingVideoTasksFile = (0, import_path.join)(this.dataDir, "pending_video_tasks.json");
|
|
1154
1189
|
if (!(0, import_fs.existsSync)(this.dataDir)) {
|
|
1155
1190
|
(0, import_fs.mkdirSync)(this.dataDir, { recursive: true });
|
|
1156
1191
|
}
|
|
@@ -1212,6 +1247,83 @@ var UserManager = class {
|
|
|
1212
1247
|
throw error;
|
|
1213
1248
|
}
|
|
1214
1249
|
}
|
|
1250
|
+
// --- 待结算视频任务(防止超时套利) ---
|
|
1251
|
+
async loadPendingVideoTasks() {
|
|
1252
|
+
if (this.pendingVideoCache) return this.pendingVideoCache;
|
|
1253
|
+
return await this.pendingLock.acquire(async () => {
|
|
1254
|
+
if (this.pendingVideoCache) return this.pendingVideoCache;
|
|
1255
|
+
try {
|
|
1256
|
+
if ((0, import_fs.existsSync)(this.pendingVideoTasksFile)) {
|
|
1257
|
+
const data = await import_fs.promises.readFile(this.pendingVideoTasksFile, "utf-8");
|
|
1258
|
+
const parsed = JSON.parse(data);
|
|
1259
|
+
this.pendingVideoCache = {
|
|
1260
|
+
version: parsed?.version || "1.0.0",
|
|
1261
|
+
lastUpdate: parsed?.lastUpdate || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1262
|
+
tasks: parsed?.tasks || {}
|
|
1263
|
+
};
|
|
1264
|
+
return this.pendingVideoCache;
|
|
1265
|
+
}
|
|
1266
|
+
} catch (error) {
|
|
1267
|
+
this.logger.error("读取待结算视频任务失败", error);
|
|
1268
|
+
}
|
|
1269
|
+
this.pendingVideoCache = {
|
|
1270
|
+
version: "1.0.0",
|
|
1271
|
+
lastUpdate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1272
|
+
tasks: {}
|
|
1273
|
+
};
|
|
1274
|
+
return this.pendingVideoCache;
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
async savePendingVideoTasksInternal() {
|
|
1278
|
+
if (!this.pendingVideoCache) return;
|
|
1279
|
+
try {
|
|
1280
|
+
this.pendingVideoCache.lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
1281
|
+
await import_fs.promises.writeFile(this.pendingVideoTasksFile, JSON.stringify(this.pendingVideoCache, null, 2), "utf-8");
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
this.logger.error("保存待结算视频任务失败", error);
|
|
1284
|
+
throw error;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* 记录一个待结算的视频任务(任务创建成功后立即写入,避免“超时但最终成功”不扣费)
|
|
1289
|
+
*/
|
|
1290
|
+
async addPendingVideoTask(task) {
|
|
1291
|
+
await this.loadPendingVideoTasks();
|
|
1292
|
+
await this.pendingLock.acquire(async () => {
|
|
1293
|
+
if (!this.pendingVideoCache) {
|
|
1294
|
+
this.pendingVideoCache = { version: "1.0.0", lastUpdate: (/* @__PURE__ */ new Date()).toISOString(), tasks: {} };
|
|
1295
|
+
}
|
|
1296
|
+
if (this.pendingVideoCache.tasks[task.taskId]) return;
|
|
1297
|
+
this.pendingVideoCache.tasks[task.taskId] = task;
|
|
1298
|
+
await this.savePendingVideoTasksInternal();
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
async getPendingVideoTask(taskId) {
|
|
1302
|
+
const data = await this.loadPendingVideoTasks();
|
|
1303
|
+
return data.tasks[taskId] || null;
|
|
1304
|
+
}
|
|
1305
|
+
async markPendingVideoTaskCharged(taskId) {
|
|
1306
|
+
await this.loadPendingVideoTasks();
|
|
1307
|
+
return await this.pendingLock.acquire(async () => {
|
|
1308
|
+
if (!this.pendingVideoCache) return null;
|
|
1309
|
+
const task = this.pendingVideoCache.tasks[taskId];
|
|
1310
|
+
if (!task) return null;
|
|
1311
|
+
if (task.charged) return task;
|
|
1312
|
+
task.charged = true;
|
|
1313
|
+
task.chargedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1314
|
+
await this.savePendingVideoTasksInternal();
|
|
1315
|
+
return task;
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
async deletePendingVideoTask(taskId) {
|
|
1319
|
+
await this.loadPendingVideoTasks();
|
|
1320
|
+
await this.pendingLock.acquire(async () => {
|
|
1321
|
+
if (!this.pendingVideoCache) return;
|
|
1322
|
+
if (!this.pendingVideoCache.tasks[taskId]) return;
|
|
1323
|
+
delete this.pendingVideoCache.tasks[taskId];
|
|
1324
|
+
await this.savePendingVideoTasksInternal();
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1215
1327
|
// 获取特定用户数据
|
|
1216
1328
|
async getUserData(userId, userName) {
|
|
1217
1329
|
await this.loadUsersData();
|
|
@@ -2205,6 +2317,7 @@ ${infoParts.join("\n")}`;
|
|
|
2205
2317
|
if (!userManager.startTask(userId)) {
|
|
2206
2318
|
return "您有一个任务正在进行中,请等待完成";
|
|
2207
2319
|
}
|
|
2320
|
+
let createdTaskId = null;
|
|
2208
2321
|
try {
|
|
2209
2322
|
const inputResult = await getInputData(session, img, "single");
|
|
2210
2323
|
if ("error" in inputResult) {
|
|
@@ -2246,17 +2359,30 @@ ${infoParts.join("\n")}`;
|
|
|
2246
2359
|
💡 提示:生成过程中可继续使用其他功能`
|
|
2247
2360
|
);
|
|
2248
2361
|
const startTime = Date.now();
|
|
2249
|
-
const
|
|
2362
|
+
const taskId = await videoProvider.createVideoTask(
|
|
2250
2363
|
prompt,
|
|
2251
2364
|
imageUrls[0],
|
|
2252
2365
|
{
|
|
2253
2366
|
duration,
|
|
2254
2367
|
aspectRatio: ratio
|
|
2255
|
-
}
|
|
2256
|
-
config.videoMaxWaitTime
|
|
2368
|
+
}
|
|
2257
2369
|
);
|
|
2258
|
-
|
|
2370
|
+
createdTaskId = taskId;
|
|
2371
|
+
await userManager.addPendingVideoTask({
|
|
2372
|
+
taskId,
|
|
2373
|
+
userId,
|
|
2374
|
+
userName,
|
|
2375
|
+
commandName: "图生视频",
|
|
2376
|
+
credits: videoCredits,
|
|
2377
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2378
|
+
charged: false
|
|
2379
|
+
});
|
|
2380
|
+
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
2381
|
+
const videoUrl = await videoProvider.waitForVideo(taskId, config.videoMaxWaitTime);
|
|
2259
2382
|
await session.send(import_koishi2.h.video(videoUrl));
|
|
2383
|
+
await recordUserUsage(session, "图生视频", videoCredits, false);
|
|
2384
|
+
await userManager.markPendingVideoTaskCharged(taskId);
|
|
2385
|
+
await userManager.deletePendingVideoTask(taskId);
|
|
2260
2386
|
const totalTime = Math.floor((Date.now() - startTime) / 1e3);
|
|
2261
2387
|
await session.send(`✅ 视频生成完成!(耗时 ${totalTime} 秒)`);
|
|
2262
2388
|
} catch (error) {
|
|
@@ -2265,6 +2391,10 @@ ${infoParts.join("\n")}`;
|
|
|
2265
2391
|
if (errorMsg.includes("任务ID:")) {
|
|
2266
2392
|
return errorMsg;
|
|
2267
2393
|
}
|
|
2394
|
+
try {
|
|
2395
|
+
if (createdTaskId) await userManager.deletePendingVideoTask(createdTaskId);
|
|
2396
|
+
} catch {
|
|
2397
|
+
}
|
|
2268
2398
|
return `视频生成失败:${sanitizeString(errorMsg)}`;
|
|
2269
2399
|
} finally {
|
|
2270
2400
|
userManager.endTask(userId);
|
|
@@ -2281,7 +2411,17 @@ ${infoParts.join("\n")}`;
|
|
|
2281
2411
|
await session.send("🔍 正在查询视频生成状态...");
|
|
2282
2412
|
const status = await videoProvider.queryTaskStatus(taskId.trim());
|
|
2283
2413
|
if (status.status === "completed" && status.videoUrl) {
|
|
2414
|
+
const trimmedTaskId = taskId.trim();
|
|
2415
|
+
const pending = await userManager.getPendingVideoTask(trimmedTaskId);
|
|
2416
|
+
if (pending && pending.userId && pending.userId !== session.userId) {
|
|
2417
|
+
return "该任务ID不属于当前用户,无法查询";
|
|
2418
|
+
}
|
|
2284
2419
|
await session.send(import_koishi2.h.video(status.videoUrl));
|
|
2420
|
+
if (pending && !pending.charged) {
|
|
2421
|
+
await recordUserUsage(session, pending.commandName, pending.credits, false);
|
|
2422
|
+
await userManager.markPendingVideoTaskCharged(trimmedTaskId);
|
|
2423
|
+
await userManager.deletePendingVideoTask(trimmedTaskId);
|
|
2424
|
+
}
|
|
2285
2425
|
return "✅ 视频已生成完成!";
|
|
2286
2426
|
} else if (status.status === "processing" || status.status === "pending") {
|
|
2287
2427
|
const progressText = status.progress ? `(进度:${status.progress}%)` : "";
|
|
@@ -2318,6 +2458,7 @@ ${infoParts.join("\n")}`;
|
|
|
2318
2458
|
if (!userManager.startTask(userId)) {
|
|
2319
2459
|
return "您有一个任务正在进行中,请等待完成";
|
|
2320
2460
|
}
|
|
2461
|
+
let createdTaskId = null;
|
|
2321
2462
|
try {
|
|
2322
2463
|
const inputResult = await getInputData(session, img, "single");
|
|
2323
2464
|
if ("error" in inputResult) {
|
|
@@ -2336,17 +2477,30 @@ ${infoParts.join("\n")}`;
|
|
|
2336
2477
|
📝 描述:${finalPrompt}
|
|
2337
2478
|
⏱️ 预计需要 1-3 分钟`
|
|
2338
2479
|
);
|
|
2339
|
-
const
|
|
2480
|
+
const taskId = await videoProvider.createVideoTask(
|
|
2340
2481
|
finalPrompt,
|
|
2341
2482
|
imageUrls[0],
|
|
2342
2483
|
{
|
|
2343
2484
|
duration: style.duration || 15,
|
|
2344
2485
|
aspectRatio: style.aspectRatio || "16:9"
|
|
2345
|
-
}
|
|
2346
|
-
config.videoMaxWaitTime
|
|
2486
|
+
}
|
|
2347
2487
|
);
|
|
2348
|
-
|
|
2488
|
+
createdTaskId = taskId;
|
|
2489
|
+
await userManager.addPendingVideoTask({
|
|
2490
|
+
taskId,
|
|
2491
|
+
userId,
|
|
2492
|
+
userName,
|
|
2493
|
+
commandName: style.commandName,
|
|
2494
|
+
credits: videoCredits,
|
|
2495
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2496
|
+
charged: false
|
|
2497
|
+
});
|
|
2498
|
+
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
2499
|
+
const videoUrl = await videoProvider.waitForVideo(taskId, config.videoMaxWaitTime);
|
|
2349
2500
|
await session.send(import_koishi2.h.video(videoUrl));
|
|
2501
|
+
await recordUserUsage(session, style.commandName, videoCredits, false);
|
|
2502
|
+
await userManager.markPendingVideoTaskCharged(taskId);
|
|
2503
|
+
await userManager.deletePendingVideoTask(taskId);
|
|
2350
2504
|
await session.send(`✅ 视频生成完成!`);
|
|
2351
2505
|
} catch (error) {
|
|
2352
2506
|
logger.error("视频风格转换失败", { userId, style: style.commandName, error: sanitizeError(error) });
|
|
@@ -2354,6 +2508,10 @@ ${infoParts.join("\n")}`;
|
|
|
2354
2508
|
if (errorMsg.includes("任务ID:")) {
|
|
2355
2509
|
return errorMsg;
|
|
2356
2510
|
}
|
|
2511
|
+
try {
|
|
2512
|
+
if (createdTaskId) await userManager.deletePendingVideoTask(createdTaskId);
|
|
2513
|
+
} catch {
|
|
2514
|
+
}
|
|
2357
2515
|
return `视频生成失败:${sanitizeString(errorMsg)}`;
|
|
2358
2516
|
} finally {
|
|
2359
2517
|
userManager.endTask(userId);
|
|
@@ -14,13 +14,17 @@ export declare class YunwuVideoProvider implements VideoProvider {
|
|
|
14
14
|
createVideoTask(prompt: string, imageUrl: string, options?: VideoGenerationOptions): Promise<string>;
|
|
15
15
|
/**
|
|
16
16
|
* 查询任务状态
|
|
17
|
-
* API: GET /v1/video/{taskId}
|
|
17
|
+
* API: GET /v1/video/query?id={taskId}
|
|
18
18
|
*/
|
|
19
19
|
queryTaskStatus(taskId: string): Promise<VideoTaskStatus>;
|
|
20
20
|
/**
|
|
21
21
|
* 轮询等待任务完成
|
|
22
22
|
*/
|
|
23
23
|
private pollTaskCompletion;
|
|
24
|
+
/**
|
|
25
|
+
* 等待指定任务完成并返回视频 URL(供外部在拿到 taskId 后自行控制扣费/提示)
|
|
26
|
+
*/
|
|
27
|
+
waitForVideo(taskId: string, maxWaitTime?: number): Promise<string>;
|
|
24
28
|
/**
|
|
25
29
|
* 生成视频(主入口)
|
|
26
30
|
*/
|
|
@@ -39,15 +39,33 @@ export interface RechargeHistory {
|
|
|
39
39
|
lastUpdate: string;
|
|
40
40
|
records: RechargeRecord[];
|
|
41
41
|
}
|
|
42
|
+
export interface PendingVideoTask {
|
|
43
|
+
taskId: string;
|
|
44
|
+
userId: string;
|
|
45
|
+
userName: string;
|
|
46
|
+
commandName: string;
|
|
47
|
+
credits: number;
|
|
48
|
+
createdAt: string;
|
|
49
|
+
charged: boolean;
|
|
50
|
+
chargedAt?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface PendingVideoTasksData {
|
|
53
|
+
version: string;
|
|
54
|
+
lastUpdate: string;
|
|
55
|
+
tasks: Record<string, PendingVideoTask>;
|
|
56
|
+
}
|
|
42
57
|
export declare class UserManager {
|
|
43
58
|
private dataDir;
|
|
44
59
|
private dataFile;
|
|
45
60
|
private backupFile;
|
|
46
61
|
private rechargeHistoryFile;
|
|
62
|
+
private pendingVideoTasksFile;
|
|
47
63
|
private logger;
|
|
48
64
|
private dataLock;
|
|
49
65
|
private historyLock;
|
|
66
|
+
private pendingLock;
|
|
50
67
|
private usersCache;
|
|
68
|
+
private pendingVideoCache;
|
|
51
69
|
private activeTasks;
|
|
52
70
|
private rateLimitMap;
|
|
53
71
|
private securityBlockMap;
|
|
@@ -59,6 +77,15 @@ export declare class UserManager {
|
|
|
59
77
|
isAdmin(userId: string, config: Config): boolean;
|
|
60
78
|
private loadUsersData;
|
|
61
79
|
private saveUsersDataInternal;
|
|
80
|
+
private loadPendingVideoTasks;
|
|
81
|
+
private savePendingVideoTasksInternal;
|
|
82
|
+
/**
|
|
83
|
+
* 记录一个待结算的视频任务(任务创建成功后立即写入,避免“超时但最终成功”不扣费)
|
|
84
|
+
*/
|
|
85
|
+
addPendingVideoTask(task: PendingVideoTask): Promise<void>;
|
|
86
|
+
getPendingVideoTask(taskId: string): Promise<PendingVideoTask | null>;
|
|
87
|
+
markPendingVideoTaskCharged(taskId: string): Promise<PendingVideoTask | null>;
|
|
88
|
+
deletePendingVideoTask(taskId: string): Promise<void>;
|
|
62
89
|
getUserData(userId: string, userName: string): Promise<UserData>;
|
|
63
90
|
getAllUsers(): Promise<UsersData>;
|
|
64
91
|
updateUsersBatch(updates: (data: UsersData) => void): Promise<void>;
|