koishi-plugin-aka-ai-generator 0.6.8 → 0.6.9
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 +63 -27
- package/lib/services/UserManager.d.ts +5 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -983,6 +983,36 @@ var UserManager = class {
|
|
|
983
983
|
}
|
|
984
984
|
return { allowed: true, isAdmin: false };
|
|
985
985
|
}
|
|
986
|
+
// 原子性地检查并预留额度(防止并发绕过)
|
|
987
|
+
async checkAndReserveQuota(userId, userName, numImages, config) {
|
|
988
|
+
if (this.isAdmin(userId, config)) {
|
|
989
|
+
return { allowed: true, reservationId: "admin" };
|
|
990
|
+
}
|
|
991
|
+
const rateLimitCheck = this.checkRateLimit(userId, config);
|
|
992
|
+
if (!rateLimitCheck.allowed) {
|
|
993
|
+
return { ...rateLimitCheck };
|
|
994
|
+
}
|
|
995
|
+
this.updateRateLimit(userId);
|
|
996
|
+
return await this.dataLock.acquire(async () => {
|
|
997
|
+
const userData = await this.getUserData(userId, userName);
|
|
998
|
+
const today = (/* @__PURE__ */ new Date()).toDateString();
|
|
999
|
+
const lastReset = new Date(userData.lastDailyReset || userData.createdAt).toDateString();
|
|
1000
|
+
let dailyCount = userData.dailyUsageCount;
|
|
1001
|
+
if (today !== lastReset) {
|
|
1002
|
+
dailyCount = 0;
|
|
1003
|
+
}
|
|
1004
|
+
const remainingToday = Math.max(0, config.dailyFreeLimit - dailyCount);
|
|
1005
|
+
const totalAvailable = remainingToday + userData.remainingPurchasedCount;
|
|
1006
|
+
if (totalAvailable < numImages) {
|
|
1007
|
+
return {
|
|
1008
|
+
allowed: false,
|
|
1009
|
+
message: `生成 ${numImages} 张图片需要 ${numImages} 次可用次数,但您的可用次数不足(今日免费剩余:${remainingToday}次,充值剩余:${userData.remainingPurchasedCount}次,共${totalAvailable}次)`
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
const reservationId = `${userId}_${Date.now()}_${Math.random()}`;
|
|
1013
|
+
return { allowed: true, reservationId };
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
986
1016
|
// 扣减额度并记录使用
|
|
987
1017
|
async consumeQuota(userId, userName, commandName, numImages, config) {
|
|
988
1018
|
return await this.dataLock.acquire(async () => {
|
|
@@ -1059,7 +1089,7 @@ var UserManager = class {
|
|
|
1059
1089
|
if (blockCount >= config.securityBlockWarningThreshold && !hasWarning) {
|
|
1060
1090
|
this.securityWarningMap.set(userId, true);
|
|
1061
1091
|
shouldWarn = true;
|
|
1062
|
-
} else if (
|
|
1092
|
+
} else if (blockCount > config.securityBlockWarningThreshold) {
|
|
1063
1093
|
shouldDeduct = true;
|
|
1064
1094
|
}
|
|
1065
1095
|
return { shouldWarn, shouldDeduct, blockCount };
|
|
@@ -1318,34 +1348,36 @@ function apply(ctx, config) {
|
|
|
1318
1348
|
return input || null;
|
|
1319
1349
|
}
|
|
1320
1350
|
__name(getPromptInput, "getPromptInput");
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
if (!userId) return;
|
|
1325
|
-
const { userData, consumptionType, freeUsed, purchasedUsed } = await userManager.consumeQuota(userId, userName, commandName, numImages, config);
|
|
1326
|
-
if (userManager.isAdmin(userId, config)) {
|
|
1327
|
-
await session.send(`📊 使用统计 [管理员]
|
|
1351
|
+
function buildStatsMessage(userData, numImages, consumptionType, freeUsed, purchasedUsed, config2) {
|
|
1352
|
+
if (userManager.isAdmin(userData.userId, config2)) {
|
|
1353
|
+
return `📊 使用统计 [管理员]
|
|
1328
1354
|
用户:${userData.userName}
|
|
1329
1355
|
总调用次数:${userData.totalUsageCount}次
|
|
1330
|
-
|
|
1356
|
+
状态:无限制使用`;
|
|
1357
|
+
}
|
|
1358
|
+
const remainingToday = Math.max(0, config2.dailyFreeLimit - userData.dailyUsageCount);
|
|
1359
|
+
let consumptionText = "";
|
|
1360
|
+
if (consumptionType === "mixed") {
|
|
1361
|
+
consumptionText = `每日免费次数 -${freeUsed},充值次数 -${purchasedUsed}`;
|
|
1362
|
+
} else if (consumptionType === "free") {
|
|
1363
|
+
consumptionText = `每日免费次数 -${freeUsed}`;
|
|
1331
1364
|
} else {
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
consumptionText = `每日免费次数 -${freeUsed},充值次数 -${purchasedUsed}`;
|
|
1336
|
-
} else if (consumptionType === "free") {
|
|
1337
|
-
consumptionText = `每日免费次数 -${freeUsed}`;
|
|
1338
|
-
} else {
|
|
1339
|
-
consumptionText = `充值次数 -${purchasedUsed}`;
|
|
1340
|
-
}
|
|
1341
|
-
await session.send(`📊 使用统计
|
|
1365
|
+
consumptionText = `充值次数 -${purchasedUsed}`;
|
|
1366
|
+
}
|
|
1367
|
+
return `📊 使用统计
|
|
1342
1368
|
用户:${userData.userName}
|
|
1343
1369
|
本次生成:${numImages}张图片
|
|
1344
1370
|
本次消费:${consumptionText}
|
|
1345
1371
|
总调用次数:${userData.totalUsageCount}次
|
|
1346
1372
|
今日剩余免费:${remainingToday}次
|
|
1347
|
-
充值剩余:${userData.remainingPurchasedCount}
|
|
1348
|
-
|
|
1373
|
+
充值剩余:${userData.remainingPurchasedCount}次`;
|
|
1374
|
+
}
|
|
1375
|
+
__name(buildStatsMessage, "buildStatsMessage");
|
|
1376
|
+
async function recordUserUsage(session, commandName, numImages = 1) {
|
|
1377
|
+
const userId = session.userId;
|
|
1378
|
+
const userName = session.username || session.userId || "未知用户";
|
|
1379
|
+
if (!userId) return;
|
|
1380
|
+
const { userData, consumptionType, freeUsed, purchasedUsed } = await userManager.consumeQuota(userId, userName, commandName, numImages, config);
|
|
1349
1381
|
logger.info("用户调用记录", {
|
|
1350
1382
|
userId,
|
|
1351
1383
|
userName: userData.userName,
|
|
@@ -1359,6 +1391,12 @@ function apply(ctx, config) {
|
|
|
1359
1391
|
remainingPurchasedCount: userData.remainingPurchasedCount,
|
|
1360
1392
|
isAdmin: userManager.isAdmin(userId, config)
|
|
1361
1393
|
});
|
|
1394
|
+
try {
|
|
1395
|
+
const statsMessage = buildStatsMessage(userData, numImages, consumptionType, freeUsed, purchasedUsed, config);
|
|
1396
|
+
await session.send(statsMessage);
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
logger.warn("发送统计信息失败", { userId, error: sanitizeError(error) });
|
|
1399
|
+
}
|
|
1362
1400
|
}
|
|
1363
1401
|
__name(recordUserUsage, "recordUserUsage");
|
|
1364
1402
|
async function recordSecurityBlock(session, numImages = 1) {
|
|
@@ -1493,7 +1531,6 @@ function apply(ctx, config) {
|
|
|
1493
1531
|
}, config.commandTimeout * 1e3)
|
|
1494
1532
|
)
|
|
1495
1533
|
]).catch(async (error) => {
|
|
1496
|
-
if (userId) userManager.endTask(userId);
|
|
1497
1534
|
const sanitizedError = sanitizeError(error);
|
|
1498
1535
|
logger.error("图像处理超时或失败", { userId, error: sanitizedError });
|
|
1499
1536
|
if (error?.message !== "命令执行超时") {
|
|
@@ -1580,6 +1617,8 @@ ${infoParts.join("\n")}`;
|
|
|
1580
1617
|
if (images.length === 0) {
|
|
1581
1618
|
return "图像处理失败:未能生成图片";
|
|
1582
1619
|
}
|
|
1620
|
+
await recordUserUsage(session, styleName, images.length);
|
|
1621
|
+
if (checkTimeout && checkTimeout()) throw new Error("命令执行超时");
|
|
1583
1622
|
await session.send("图像处理完成!");
|
|
1584
1623
|
for (let i = 0; i < images.length; i++) {
|
|
1585
1624
|
if (checkTimeout && checkTimeout()) break;
|
|
@@ -1588,7 +1627,6 @@ ${infoParts.join("\n")}`;
|
|
|
1588
1627
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1589
1628
|
}
|
|
1590
1629
|
}
|
|
1591
|
-
await recordUserUsage(session, styleName, images.length);
|
|
1592
1630
|
} finally {
|
|
1593
1631
|
userManager.endTask(userId);
|
|
1594
1632
|
}
|
|
@@ -1674,11 +1712,9 @@ ${infoParts.join("\n")}`;
|
|
|
1674
1712
|
if (!userManager.startTask(userId)) {
|
|
1675
1713
|
return "您有一个图像处理任务正在进行中,请等待完成";
|
|
1676
1714
|
}
|
|
1677
|
-
userManager.endTask(userId);
|
|
1678
1715
|
let isTimeout = false;
|
|
1679
1716
|
return Promise.race([
|
|
1680
1717
|
(async () => {
|
|
1681
|
-
if (!userManager.startTask(userId)) return "您有一个图像处理任务正在进行中";
|
|
1682
1718
|
try {
|
|
1683
1719
|
await session.send("多张图片+描述");
|
|
1684
1720
|
const collectedImages = [];
|
|
@@ -1742,6 +1778,8 @@ Prompt: ${prompt}`);
|
|
|
1742
1778
|
if (resultImages.length === 0) {
|
|
1743
1779
|
return "图片合成失败:未能生成图片";
|
|
1744
1780
|
}
|
|
1781
|
+
await recordUserUsage(session, COMMANDS.COMPOSE_IMAGE, resultImages.length);
|
|
1782
|
+
if (isTimeout) throw new Error("命令执行超时");
|
|
1745
1783
|
await session.send("图片合成完成!");
|
|
1746
1784
|
for (let i = 0; i < resultImages.length; i++) {
|
|
1747
1785
|
if (isTimeout) break;
|
|
@@ -1750,7 +1788,6 @@ Prompt: ${prompt}`);
|
|
|
1750
1788
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1751
1789
|
}
|
|
1752
1790
|
}
|
|
1753
|
-
await recordUserUsage(session, COMMANDS.COMPOSE_IMAGE, resultImages.length);
|
|
1754
1791
|
} finally {
|
|
1755
1792
|
userManager.endTask(userId);
|
|
1756
1793
|
}
|
|
@@ -1762,7 +1799,6 @@ Prompt: ${prompt}`);
|
|
|
1762
1799
|
}, config.commandTimeout * 1e3)
|
|
1763
1800
|
)
|
|
1764
1801
|
]).catch(async (error) => {
|
|
1765
|
-
if (userId) userManager.endTask(userId);
|
|
1766
1802
|
const sanitizedError = sanitizeError(error);
|
|
1767
1803
|
logger.error("图片合成超时或失败", { userId, error: sanitizedError });
|
|
1768
1804
|
if (error?.message !== "命令执行超时") {
|
|
@@ -74,6 +74,11 @@ export declare class UserManager {
|
|
|
74
74
|
message?: string;
|
|
75
75
|
isAdmin?: boolean;
|
|
76
76
|
}>;
|
|
77
|
+
checkAndReserveQuota(userId: string, userName: string, numImages: number, config: Config): Promise<{
|
|
78
|
+
allowed: boolean;
|
|
79
|
+
message?: string;
|
|
80
|
+
reservationId?: string;
|
|
81
|
+
}>;
|
|
77
82
|
consumeQuota(userId: string, userName: string, commandName: string, numImages: number, config: Config): Promise<{
|
|
78
83
|
userData: UserData;
|
|
79
84
|
consumptionType: 'free' | 'purchased' | 'mixed';
|