koishi-plugin-aka-ai-generator 0.7.5 → 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 +142 -9
- package/lib/providers/yunwu-video.d.ts +4 -0
- package/lib/services/UserManager.d.ts +27 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1067,6 +1067,12 @@ var YunwuVideoProvider = class {
|
|
|
1067
1067
|
await new Promise((resolve) => setTimeout(resolve, pollInterval * 1e3));
|
|
1068
1068
|
}
|
|
1069
1069
|
}
|
|
1070
|
+
/**
|
|
1071
|
+
* 等待指定任务完成并返回视频 URL(供外部在拿到 taskId 后自行控制扣费/提示)
|
|
1072
|
+
*/
|
|
1073
|
+
async waitForVideo(taskId, maxWaitTime = 300) {
|
|
1074
|
+
return await this.pollTaskCompletion(taskId, maxWaitTime);
|
|
1075
|
+
}
|
|
1070
1076
|
/**
|
|
1071
1077
|
* 生成视频(主入口)
|
|
1072
1078
|
*/
|
|
@@ -1077,7 +1083,7 @@ var YunwuVideoProvider = class {
|
|
|
1077
1083
|
const taskId = await this.createVideoTask(prompt, imageUrl, options);
|
|
1078
1084
|
logger?.debug("任务已创建,等待 3 秒后开始查询", { taskId });
|
|
1079
1085
|
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
1080
|
-
const videoUrl = await this.
|
|
1086
|
+
const videoUrl = await this.waitForVideo(taskId, maxWaitTime);
|
|
1081
1087
|
logger?.info("视频生成完成", { taskId, videoUrl });
|
|
1082
1088
|
return videoUrl;
|
|
1083
1089
|
} catch (error) {
|
|
@@ -1157,11 +1163,14 @@ var UserManager = class {
|
|
|
1157
1163
|
dataFile;
|
|
1158
1164
|
backupFile;
|
|
1159
1165
|
rechargeHistoryFile;
|
|
1166
|
+
pendingVideoTasksFile;
|
|
1160
1167
|
logger;
|
|
1161
1168
|
dataLock = new AsyncLock();
|
|
1162
1169
|
historyLock = new AsyncLock();
|
|
1170
|
+
pendingLock = new AsyncLock();
|
|
1163
1171
|
// 内存缓存
|
|
1164
1172
|
usersCache = null;
|
|
1173
|
+
pendingVideoCache = null;
|
|
1165
1174
|
activeTasks = /* @__PURE__ */ new Map();
|
|
1166
1175
|
// userId -> requestId
|
|
1167
1176
|
rateLimitMap = /* @__PURE__ */ new Map();
|
|
@@ -1176,6 +1185,7 @@ var UserManager = class {
|
|
|
1176
1185
|
this.dataFile = (0, import_path.join)(this.dataDir, "users_data.json");
|
|
1177
1186
|
this.backupFile = (0, import_path.join)(this.dataDir, "users_data.json.backup");
|
|
1178
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");
|
|
1179
1189
|
if (!(0, import_fs.existsSync)(this.dataDir)) {
|
|
1180
1190
|
(0, import_fs.mkdirSync)(this.dataDir, { recursive: true });
|
|
1181
1191
|
}
|
|
@@ -1237,6 +1247,83 @@ var UserManager = class {
|
|
|
1237
1247
|
throw error;
|
|
1238
1248
|
}
|
|
1239
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
|
+
}
|
|
1240
1327
|
// 获取特定用户数据
|
|
1241
1328
|
async getUserData(userId, userName) {
|
|
1242
1329
|
await this.loadUsersData();
|
|
@@ -2230,6 +2317,7 @@ ${infoParts.join("\n")}`;
|
|
|
2230
2317
|
if (!userManager.startTask(userId)) {
|
|
2231
2318
|
return "您有一个任务正在进行中,请等待完成";
|
|
2232
2319
|
}
|
|
2320
|
+
let createdTaskId = null;
|
|
2233
2321
|
try {
|
|
2234
2322
|
const inputResult = await getInputData(session, img, "single");
|
|
2235
2323
|
if ("error" in inputResult) {
|
|
@@ -2271,17 +2359,30 @@ ${infoParts.join("\n")}`;
|
|
|
2271
2359
|
💡 提示:生成过程中可继续使用其他功能`
|
|
2272
2360
|
);
|
|
2273
2361
|
const startTime = Date.now();
|
|
2274
|
-
const
|
|
2362
|
+
const taskId = await videoProvider.createVideoTask(
|
|
2275
2363
|
prompt,
|
|
2276
2364
|
imageUrls[0],
|
|
2277
2365
|
{
|
|
2278
2366
|
duration,
|
|
2279
2367
|
aspectRatio: ratio
|
|
2280
|
-
}
|
|
2281
|
-
config.videoMaxWaitTime
|
|
2368
|
+
}
|
|
2282
2369
|
);
|
|
2283
|
-
|
|
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);
|
|
2284
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);
|
|
2285
2386
|
const totalTime = Math.floor((Date.now() - startTime) / 1e3);
|
|
2286
2387
|
await session.send(`✅ 视频生成完成!(耗时 ${totalTime} 秒)`);
|
|
2287
2388
|
} catch (error) {
|
|
@@ -2290,6 +2391,10 @@ ${infoParts.join("\n")}`;
|
|
|
2290
2391
|
if (errorMsg.includes("任务ID:")) {
|
|
2291
2392
|
return errorMsg;
|
|
2292
2393
|
}
|
|
2394
|
+
try {
|
|
2395
|
+
if (createdTaskId) await userManager.deletePendingVideoTask(createdTaskId);
|
|
2396
|
+
} catch {
|
|
2397
|
+
}
|
|
2293
2398
|
return `视频生成失败:${sanitizeString(errorMsg)}`;
|
|
2294
2399
|
} finally {
|
|
2295
2400
|
userManager.endTask(userId);
|
|
@@ -2306,7 +2411,17 @@ ${infoParts.join("\n")}`;
|
|
|
2306
2411
|
await session.send("🔍 正在查询视频生成状态...");
|
|
2307
2412
|
const status = await videoProvider.queryTaskStatus(taskId.trim());
|
|
2308
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
|
+
}
|
|
2309
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
|
+
}
|
|
2310
2425
|
return "✅ 视频已生成完成!";
|
|
2311
2426
|
} else if (status.status === "processing" || status.status === "pending") {
|
|
2312
2427
|
const progressText = status.progress ? `(进度:${status.progress}%)` : "";
|
|
@@ -2343,6 +2458,7 @@ ${infoParts.join("\n")}`;
|
|
|
2343
2458
|
if (!userManager.startTask(userId)) {
|
|
2344
2459
|
return "您有一个任务正在进行中,请等待完成";
|
|
2345
2460
|
}
|
|
2461
|
+
let createdTaskId = null;
|
|
2346
2462
|
try {
|
|
2347
2463
|
const inputResult = await getInputData(session, img, "single");
|
|
2348
2464
|
if ("error" in inputResult) {
|
|
@@ -2361,17 +2477,30 @@ ${infoParts.join("\n")}`;
|
|
|
2361
2477
|
📝 描述:${finalPrompt}
|
|
2362
2478
|
⏱️ 预计需要 1-3 分钟`
|
|
2363
2479
|
);
|
|
2364
|
-
const
|
|
2480
|
+
const taskId = await videoProvider.createVideoTask(
|
|
2365
2481
|
finalPrompt,
|
|
2366
2482
|
imageUrls[0],
|
|
2367
2483
|
{
|
|
2368
2484
|
duration: style.duration || 15,
|
|
2369
2485
|
aspectRatio: style.aspectRatio || "16:9"
|
|
2370
|
-
}
|
|
2371
|
-
config.videoMaxWaitTime
|
|
2486
|
+
}
|
|
2372
2487
|
);
|
|
2373
|
-
|
|
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);
|
|
2374
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);
|
|
2375
2504
|
await session.send(`✅ 视频生成完成!`);
|
|
2376
2505
|
} catch (error) {
|
|
2377
2506
|
logger.error("视频风格转换失败", { userId, style: style.commandName, error: sanitizeError(error) });
|
|
@@ -2379,6 +2508,10 @@ ${infoParts.join("\n")}`;
|
|
|
2379
2508
|
if (errorMsg.includes("任务ID:")) {
|
|
2380
2509
|
return errorMsg;
|
|
2381
2510
|
}
|
|
2511
|
+
try {
|
|
2512
|
+
if (createdTaskId) await userManager.deletePendingVideoTask(createdTaskId);
|
|
2513
|
+
} catch {
|
|
2514
|
+
}
|
|
2382
2515
|
return `视频生成失败:${sanitizeString(errorMsg)}`;
|
|
2383
2516
|
} finally {
|
|
2384
2517
|
userManager.endTask(userId);
|
|
@@ -21,6 +21,10 @@ export declare class YunwuVideoProvider implements VideoProvider {
|
|
|
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>;
|