koishi-plugin-aka-ai-generator 0.5.0 → 0.5.1

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.d.ts CHANGED
@@ -25,8 +25,6 @@ export interface UserData {
25
25
  donationAmount: number;
26
26
  lastUsed: string;
27
27
  createdAt: string;
28
- safetyBlockCount: number;
29
- safetyBlockHistory: string[];
30
28
  }
31
29
  export interface UsersData {
32
30
  [userId: string]: UserData;
@@ -51,6 +49,8 @@ export interface Config {
51
49
  styles: StyleConfig[];
52
50
  styleGroups?: Record<string, StyleGroupConfig>;
53
51
  logLevel: 'info' | 'debug';
52
+ securityBlockWindow: number;
53
+ securityBlockWarningThreshold: number;
54
54
  }
55
55
  export interface RechargeRecord {
56
56
  id: string;
package/lib/index.js CHANGED
@@ -943,7 +943,10 @@ var Config = import_koishi.Schema.intersect([
943
943
  logLevel: import_koishi.Schema.union([
944
944
  import_koishi.Schema.const("info").description("普通信息"),
945
945
  import_koishi.Schema.const("debug").description("完整的debug信息")
946
- ]).default("info").description("日志输出详细程度")
946
+ ]).default("info").description("日志输出详细程度"),
947
+ // 安全策略拦截设置
948
+ securityBlockWindow: import_koishi.Schema.number().default(600).min(60).max(3600).description("安全策略拦截追踪时间窗口(秒),在此时间窗口内连续触发拦截会被记录"),
949
+ securityBlockWarningThreshold: import_koishi.Schema.number().default(3).min(1).max(10).description("安全策略拦截警示阈值,连续触发此次数拦截后将发送警示消息,再次触发将被扣除积分")
947
950
  }),
948
951
  // 自定义风格命令配置
949
952
  import_koishi.Schema.object({
@@ -968,6 +971,8 @@ function apply(ctx, config) {
968
971
  const logger = ctx.logger("aka-ai-generator");
969
972
  const activeTasks = /* @__PURE__ */ new Map();
970
973
  const rateLimitMap = /* @__PURE__ */ new Map();
974
+ const securityBlockMap = /* @__PURE__ */ new Map();
975
+ const securityWarningMap = /* @__PURE__ */ new Map();
971
976
  const providerCache = /* @__PURE__ */ new Map();
972
977
  function getProviderInstance(providerType, modelId) {
973
978
  const cacheKey = `${providerType}:${modelId || "default"}`;
@@ -1207,46 +1212,6 @@ function apply(ctx, config) {
1207
1212
  return { allowed: true, isAdmin: false };
1208
1213
  }
1209
1214
  __name(checkDailyLimit, "checkDailyLimit");
1210
- function isSafetyBlockError(error) {
1211
- if (!error) return false;
1212
- const errorMessage = typeof error === "string" ? error : error?.message || "";
1213
- if (!errorMessage) return false;
1214
- const lowerMessage = errorMessage.toLowerCase();
1215
- const safetyKeywords = [
1216
- "安全策略",
1217
- "被阻止",
1218
- "被拦截",
1219
- "blocked",
1220
- "safety",
1221
- "prohibited",
1222
- "recitation",
1223
- "内容被阻止",
1224
- "内容被拦截",
1225
- "安全策略阻止",
1226
- "安全策略拦截"
1227
- ];
1228
- return safetyKeywords.some((keyword) => lowerMessage.includes(keyword.toLowerCase()));
1229
- }
1230
- __name(isSafetyBlockError, "isSafetyBlockError");
1231
- async function checkSafetyBlockLimit(userId) {
1232
- const usersData = await loadUsersData();
1233
- const userData = usersData[userId];
1234
- if (!userData || !userData.safetyBlockHistory || userData.safetyBlockHistory.length === 0) {
1235
- return { allowed: true };
1236
- }
1237
- const windowSize = 10;
1238
- const recentBlocks = userData.safetyBlockHistory.slice(-windowSize);
1239
- const blockRate = recentBlocks.length / windowSize;
1240
- const threshold = 0.5;
1241
- if (blockRate > threshold) {
1242
- return {
1243
- allowed: false,
1244
- message: `检测到您最近多次发送被安全策略拦截的内容(拦截率:${(blockRate * 100).toFixed(0)}%),请检查您的内容是否符合使用规范。如继续发送违规内容,可能会被进一步限制。`
1245
- };
1246
- }
1247
- return { allowed: true };
1248
- }
1249
- __name(checkSafetyBlockLimit, "checkSafetyBlockLimit");
1250
1215
  async function getPromptInput(session, message) {
1251
1216
  await session.send(message);
1252
1217
  const input = await session.prompt(3e4);
@@ -1326,24 +1291,15 @@ function apply(ctx, config) {
1326
1291
  donationCount: 0,
1327
1292
  donationAmount: 0,
1328
1293
  lastUsed: (/* @__PURE__ */ new Date()).toISOString(),
1329
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1330
- safetyBlockCount: 0,
1331
- safetyBlockHistory: []
1294
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1332
1295
  };
1333
1296
  await saveUsersData(usersData);
1334
1297
  logger.info("创建新用户数据", { userId, userName });
1335
- } else {
1336
- if (usersData[userId].safetyBlockCount === void 0) {
1337
- usersData[userId].safetyBlockCount = 0;
1338
- }
1339
- if (usersData[userId].safetyBlockHistory === void 0) {
1340
- usersData[userId].safetyBlockHistory = [];
1341
- }
1342
1298
  }
1343
1299
  return usersData[userId];
1344
1300
  }
1345
1301
  __name(getUserData, "getUserData");
1346
- async function updateUserData(userId, userName, commandName, numImages = 1, isSafetyBlock = false) {
1302
+ async function updateUserData(userId, userName, commandName, numImages = 1) {
1347
1303
  const usersData = await loadUsersData();
1348
1304
  const now = (/* @__PURE__ */ new Date()).toISOString();
1349
1305
  const today = (/* @__PURE__ */ new Date()).toDateString();
@@ -1359,19 +1315,11 @@ function apply(ctx, config) {
1359
1315
  donationCount: 0,
1360
1316
  donationAmount: 0,
1361
1317
  lastUsed: now,
1362
- createdAt: now,
1363
- safetyBlockCount: isSafetyBlock ? numImages : 0,
1364
- safetyBlockHistory: isSafetyBlock ? [now] : []
1318
+ createdAt: now
1365
1319
  };
1366
1320
  await saveUsersData(usersData);
1367
1321
  return { userData: usersData[userId], consumptionType: "free", freeUsed: numImages, purchasedUsed: 0 };
1368
1322
  }
1369
- if (usersData[userId].safetyBlockCount === void 0) {
1370
- usersData[userId].safetyBlockCount = 0;
1371
- }
1372
- if (usersData[userId].safetyBlockHistory === void 0) {
1373
- usersData[userId].safetyBlockHistory = [];
1374
- }
1375
1323
  usersData[userId].totalUsageCount += numImages;
1376
1324
  usersData[userId].lastUsed = now;
1377
1325
  const lastReset = new Date(usersData[userId].lastDailyReset || usersData[userId].createdAt).toDateString();
@@ -1395,13 +1343,6 @@ function apply(ctx, config) {
1395
1343
  purchasedUsed = purchasedToUse;
1396
1344
  remainingToConsume -= purchasedToUse;
1397
1345
  }
1398
- if (isSafetyBlock) {
1399
- usersData[userId].safetyBlockCount += numImages;
1400
- usersData[userId].safetyBlockHistory.push(now);
1401
- if (usersData[userId].safetyBlockHistory.length > 20) {
1402
- usersData[userId].safetyBlockHistory = usersData[userId].safetyBlockHistory.slice(-20);
1403
- }
1404
- }
1405
1346
  await saveUsersData(usersData);
1406
1347
  let consumptionType;
1407
1348
  if (freeUsed > 0 && purchasedUsed > 0) {
@@ -1414,17 +1355,16 @@ function apply(ctx, config) {
1414
1355
  return { userData: usersData[userId], consumptionType, freeUsed, purchasedUsed };
1415
1356
  }
1416
1357
  __name(updateUserData, "updateUserData");
1417
- async function recordUserUsage(session, commandName, numImages = 1, isSafetyBlock = false) {
1358
+ async function recordUserUsage(session, commandName, numImages = 1) {
1418
1359
  const userId = session.userId;
1419
1360
  const userName = session.username || session.userId || "未知用户";
1420
1361
  if (!userId) return;
1421
- const { userData, consumptionType, freeUsed, purchasedUsed } = await updateUserData(userId, userName, commandName, numImages, isSafetyBlock);
1362
+ const { userData, consumptionType, freeUsed, purchasedUsed } = await updateUserData(userId, userName, commandName, numImages);
1422
1363
  if (isAdmin(userId)) {
1423
- const blockInfo = isSafetyBlock ? "\n⚠️ 本次请求被安全策略拦截,已扣除使用次数" : "";
1424
1364
  await session.send(`📊 使用统计 [管理员]
1425
1365
  用户:${userData.userName}
1426
1366
  总调用次数:${userData.totalUsageCount}次
1427
- 状态:无限制使用${blockInfo}`);
1367
+ 状态:无限制使用`);
1428
1368
  } else {
1429
1369
  const remainingToday = Math.max(0, config.dailyFreeLimit - userData.dailyUsageCount);
1430
1370
  let consumptionText = "";
@@ -1435,14 +1375,13 @@ function apply(ctx, config) {
1435
1375
  } else {
1436
1376
  consumptionText = `充值次数 -${purchasedUsed}`;
1437
1377
  }
1438
- const blockInfo = isSafetyBlock ? "\n⚠️ 本次请求被安全策略拦截,已扣除使用次数。请检查内容是否符合使用规范。" : "";
1439
1378
  await session.send(`📊 使用统计
1440
1379
  用户:${userData.userName}
1441
1380
  本次生成:${numImages}张图片
1442
1381
  本次消费:${consumptionText}
1443
1382
  总调用次数:${userData.totalUsageCount}次
1444
1383
  今日剩余免费:${remainingToday}次
1445
- 充值剩余:${userData.remainingPurchasedCount}次${blockInfo}`);
1384
+ 充值剩余:${userData.remainingPurchasedCount}次`);
1446
1385
  }
1447
1386
  logger.info("用户调用记录", {
1448
1387
  userId,
@@ -1455,12 +1394,44 @@ function apply(ctx, config) {
1455
1394
  totalUsageCount: userData.totalUsageCount,
1456
1395
  dailyUsageCount: userData.dailyUsageCount,
1457
1396
  remainingPurchasedCount: userData.remainingPurchasedCount,
1458
- isSafetyBlock,
1459
- safetyBlockCount: userData.safetyBlockCount,
1460
1397
  isAdmin: isAdmin(userId)
1461
1398
  });
1462
1399
  }
1463
1400
  __name(recordUserUsage, "recordUserUsage");
1401
+ async function recordSecurityBlock(session, numImages = 1) {
1402
+ const userId = session.userId;
1403
+ if (!userId) return;
1404
+ if (isAdmin(userId)) {
1405
+ return;
1406
+ }
1407
+ const now = Date.now();
1408
+ const windowMs = config.securityBlockWindow * 1e3;
1409
+ const windowStart = now - windowMs;
1410
+ let blockTimestamps = securityBlockMap.get(userId) || [];
1411
+ blockTimestamps = blockTimestamps.filter((timestamp) => timestamp > windowStart);
1412
+ blockTimestamps.push(now);
1413
+ securityBlockMap.set(userId, blockTimestamps);
1414
+ const blockCount = blockTimestamps.length;
1415
+ const hasWarning = securityWarningMap.get(userId) || false;
1416
+ logger.info("安全策略拦截记录", {
1417
+ userId,
1418
+ blockCount,
1419
+ threshold: config.securityBlockWarningThreshold,
1420
+ hasWarning,
1421
+ numImages
1422
+ });
1423
+ if (blockCount >= config.securityBlockWarningThreshold && !hasWarning) {
1424
+ securityWarningMap.set(userId, true);
1425
+ await session.send(`⚠️ 安全策略警示
1426
+ 您已连续${config.securityBlockWarningThreshold}次触发安全策略拦截,再次发送被拦截内容将被扣除积分`);
1427
+ logger.warn("用户收到安全策略警示", { userId, blockCount, threshold: config.securityBlockWarningThreshold });
1428
+ } else if (hasWarning) {
1429
+ const commandName = "安全策略拦截";
1430
+ await recordUserUsage(session, commandName, numImages);
1431
+ logger.warn("用户因安全策略拦截被扣除积分", { userId, numImages });
1432
+ }
1433
+ }
1434
+ __name(recordSecurityBlock, "recordSecurityBlock");
1464
1435
  async function getInputData(session, imgParam, mode) {
1465
1436
  const collectedImages = [];
1466
1437
  let collectedText = "";
@@ -1564,11 +1535,19 @@ function apply(ctx, config) {
1564
1535
  new Promise(
1565
1536
  (_, reject) => setTimeout(() => reject(new Error("命令执行超时")), config.commandTimeout * 1e3)
1566
1537
  )
1567
- ]).catch((error) => {
1538
+ ]).catch(async (error) => {
1568
1539
  const userId = session.userId;
1569
1540
  if (userId) activeTasks.delete(userId);
1570
1541
  const sanitizedError = sanitizeError(error);
1571
1542
  logger.error("图像处理超时或失败", { userId, error: sanitizedError });
1543
+ if (error?.message !== "命令执行超时") {
1544
+ const errorMessage = error?.message || "";
1545
+ const isSecurityBlock = errorMessage.includes("内容被安全策略拦截") || errorMessage.includes("内容被安全策略阻止") || errorMessage.includes("内容被阻止") || errorMessage.includes("被阻止") || errorMessage.includes("SAFETY") || errorMessage.includes("RECITATION");
1546
+ if (isSecurityBlock) {
1547
+ const imageCount = requestContext?.numImages || config.defaultNumImages;
1548
+ await recordSecurityBlock(session, imageCount);
1549
+ }
1550
+ }
1572
1551
  const safeMessage = typeof error?.message === "string" ? sanitizeString(error.message) : "未知错误";
1573
1552
  return error.message === "命令执行超时" ? "图像处理超时,请重试" : `图像处理失败:${safeMessage}`;
1574
1553
  });
@@ -1657,17 +1636,10 @@ ${infoParts.join("\n")}`;
1657
1636
  activeTasks.delete(userId);
1658
1637
  const sanitizedError = sanitizeError(error);
1659
1638
  logger.error("图像处理失败", { userId, error: sanitizedError });
1660
- const isSafetyBlock = isSafetyBlockError(error);
1661
- if (isSafetyBlock) {
1662
- try {
1663
- await recordUserUsage(session, styleName, imageCount, true);
1664
- const blockLimitCheck = await checkSafetyBlockLimit(userId);
1665
- if (!blockLimitCheck.allowed) {
1666
- return `图像处理失败:内容被安全策略拦截。${blockLimitCheck.message}`;
1667
- }
1668
- } catch (recordError) {
1669
- logger.error("记录安全拦截使用次数失败", { userId, error: recordError });
1670
- }
1639
+ const errorMessage = error?.message || "";
1640
+ const isSecurityBlock = errorMessage.includes("内容被安全策略拦截") || errorMessage.includes("内容被安全策略阻止") || errorMessage.includes("内容被阻止") || errorMessage.includes("被阻止") || errorMessage.includes("SAFETY") || errorMessage.includes("RECITATION");
1641
+ if (isSecurityBlock) {
1642
+ await recordSecurityBlock(session, imageCount);
1671
1643
  }
1672
1644
  if (error?.message) {
1673
1645
  const safeMessage = sanitizeString(error.message);
@@ -1835,17 +1807,10 @@ Prompt: ${prompt}`);
1835
1807
  activeTasks.delete(userId);
1836
1808
  const sanitizedError = sanitizeError(error);
1837
1809
  logger.error("图片合成失败", { userId, error: sanitizedError });
1838
- const isSafetyBlock = isSafetyBlockError(error);
1839
- if (isSafetyBlock) {
1840
- try {
1841
- await recordUserUsage(session, COMMANDS.COMPOSE_IMAGE, imageCount, true);
1842
- const blockLimitCheck = await checkSafetyBlockLimit(userId);
1843
- if (!blockLimitCheck.allowed) {
1844
- return `图片合成失败:内容被安全策略拦截。${blockLimitCheck.message}`;
1845
- }
1846
- } catch (recordError) {
1847
- logger.error("记录安全拦截使用次数失败", { userId, error: recordError });
1848
- }
1810
+ const errorMessage = error?.message || "";
1811
+ const isSecurityBlock = errorMessage.includes("内容被安全策略拦截") || errorMessage.includes("内容被安全策略阻止") || errorMessage.includes("内容被阻止") || errorMessage.includes("被阻止") || errorMessage.includes("SAFETY") || errorMessage.includes("RECITATION");
1812
+ if (isSecurityBlock) {
1813
+ await recordSecurityBlock(session, imageCount);
1849
1814
  }
1850
1815
  if (error?.message) {
1851
1816
  const safeMessage = sanitizeString(error.message);
@@ -1857,11 +1822,19 @@ Prompt: ${prompt}`);
1857
1822
  new Promise(
1858
1823
  (_, reject) => setTimeout(() => reject(new Error("命令执行超时")), config.commandTimeout * 1e3)
1859
1824
  )
1860
- ]).catch((error) => {
1825
+ ]).catch(async (error) => {
1861
1826
  const userId = session.userId;
1862
1827
  if (userId) activeTasks.delete(userId);
1863
1828
  const sanitizedError = sanitizeError(error);
1864
1829
  logger.error("图片合成超时或失败", { userId, error: sanitizedError });
1830
+ if (error?.message !== "命令执行超时") {
1831
+ const errorMessage = error?.message || "";
1832
+ const isSecurityBlock = errorMessage.includes("内容被安全策略拦截") || errorMessage.includes("内容被安全策略阻止") || errorMessage.includes("内容被阻止") || errorMessage.includes("被阻止") || errorMessage.includes("SAFETY") || errorMessage.includes("RECITATION");
1833
+ if (isSecurityBlock) {
1834
+ const imageCount = options?.num || config.defaultNumImages;
1835
+ await recordSecurityBlock(session, imageCount);
1836
+ }
1837
+ }
1865
1838
  const safeMessage = typeof error?.message === "string" ? sanitizeString(error.message) : "未知错误";
1866
1839
  return error.message === "命令执行超时" ? "图片合成超时,请重试" : `图片合成失败:${safeMessage}`;
1867
1840
  });
@@ -1917,9 +1890,7 @@ Prompt: ${prompt}`);
1917
1890
  donationCount: 0,
1918
1891
  donationAmount: 0,
1919
1892
  lastUsed: now,
1920
- createdAt: now,
1921
- safetyBlockCount: 0,
1922
- safetyBlockHistory: []
1893
+ createdAt: now
1923
1894
  };
1924
1895
  }
1925
1896
  const beforeBalance = usersData[userId].remainingPurchasedCount;
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.5.0",
4
+ "version": "0.5.1",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [