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 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 (hasWarning) {
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
- async function recordUserUsage(session, commandName, numImages = 1) {
1322
- const userId = session.userId;
1323
- const userName = session.username || session.userId || "未知用户";
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
- const remainingToday = Math.max(0, config.dailyFreeLimit - userData.dailyUsageCount);
1333
- let consumptionText = "";
1334
- if (consumptionType === "mixed") {
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';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-aka-ai-generator",
3
3
  "description": "自用AI生成插件(GPTGod & Yunwu)",
4
- "version": "0.6.8",
4
+ "version": "0.6.9",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [