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

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
@@ -771,8 +771,8 @@ var GeminiProvider = class {
771
771
  logger
772
772
  );
773
773
  imageParts.push({
774
- inline_data: {
775
- mime_type: mimeType,
774
+ inlineData: {
775
+ mimeType,
776
776
  data
777
777
  }
778
778
  });
@@ -792,7 +792,11 @@ var GeminiProvider = class {
792
792
  }
793
793
  ],
794
794
  generationConfig: {
795
- responseModalities: ["IMAGE"]
795
+ responseModalities: ["IMAGE"],
796
+ imageConfig: {
797
+ aspectRatio: "16:9",
798
+ imageSize: "4K"
799
+ }
796
800
  }
797
801
  };
798
802
  logger.debug("调用 Gemini API", { prompt, imageCount: urls.length, numImages, current: i + 1, endpoint });
@@ -802,10 +806,8 @@ var GeminiProvider = class {
802
806
  requestData,
803
807
  {
804
808
  headers: {
805
- "Content-Type": "application/json"
806
- },
807
- params: {
808
- key: this.config.apiKey
809
+ "Content-Type": "application/json",
810
+ "x-goog-api-key": this.config.apiKey
809
811
  },
810
812
  timeout: this.config.apiTimeout * 1e3
811
813
  }
@@ -943,7 +945,10 @@ var Config = import_koishi.Schema.intersect([
943
945
  logLevel: import_koishi.Schema.union([
944
946
  import_koishi.Schema.const("info").description("普通信息"),
945
947
  import_koishi.Schema.const("debug").description("完整的debug信息")
946
- ]).default("info").description("日志输出详细程度")
948
+ ]).default("info").description("日志输出详细程度"),
949
+ // 安全策略拦截设置
950
+ securityBlockWindow: import_koishi.Schema.number().default(600).min(60).max(3600).description("安全策略拦截追踪时间窗口(秒),在此时间窗口内连续触发拦截会被记录"),
951
+ securityBlockWarningThreshold: import_koishi.Schema.number().default(3).min(1).max(10).description("安全策略拦截警示阈值,连续触发此次数拦截后将发送警示消息,再次触发将被扣除积分")
947
952
  }),
948
953
  // 自定义风格命令配置
949
954
  import_koishi.Schema.object({
@@ -968,6 +973,8 @@ function apply(ctx, config) {
968
973
  const logger = ctx.logger("aka-ai-generator");
969
974
  const activeTasks = /* @__PURE__ */ new Map();
970
975
  const rateLimitMap = /* @__PURE__ */ new Map();
976
+ const securityBlockMap = /* @__PURE__ */ new Map();
977
+ const securityWarningMap = /* @__PURE__ */ new Map();
971
978
  const providerCache = /* @__PURE__ */ new Map();
972
979
  function getProviderInstance(providerType, modelId) {
973
980
  const cacheKey = `${providerType}:${modelId || "default"}`;
@@ -1207,46 +1214,6 @@ function apply(ctx, config) {
1207
1214
  return { allowed: true, isAdmin: false };
1208
1215
  }
1209
1216
  __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
1217
  async function getPromptInput(session, message) {
1251
1218
  await session.send(message);
1252
1219
  const input = await session.prompt(3e4);
@@ -1326,24 +1293,15 @@ function apply(ctx, config) {
1326
1293
  donationCount: 0,
1327
1294
  donationAmount: 0,
1328
1295
  lastUsed: (/* @__PURE__ */ new Date()).toISOString(),
1329
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1330
- safetyBlockCount: 0,
1331
- safetyBlockHistory: []
1296
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1332
1297
  };
1333
1298
  await saveUsersData(usersData);
1334
1299
  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
1300
  }
1343
1301
  return usersData[userId];
1344
1302
  }
1345
1303
  __name(getUserData, "getUserData");
1346
- async function updateUserData(userId, userName, commandName, numImages = 1, isSafetyBlock = false) {
1304
+ async function updateUserData(userId, userName, commandName, numImages = 1) {
1347
1305
  const usersData = await loadUsersData();
1348
1306
  const now = (/* @__PURE__ */ new Date()).toISOString();
1349
1307
  const today = (/* @__PURE__ */ new Date()).toDateString();
@@ -1359,19 +1317,11 @@ function apply(ctx, config) {
1359
1317
  donationCount: 0,
1360
1318
  donationAmount: 0,
1361
1319
  lastUsed: now,
1362
- createdAt: now,
1363
- safetyBlockCount: isSafetyBlock ? numImages : 0,
1364
- safetyBlockHistory: isSafetyBlock ? [now] : []
1320
+ createdAt: now
1365
1321
  };
1366
1322
  await saveUsersData(usersData);
1367
1323
  return { userData: usersData[userId], consumptionType: "free", freeUsed: numImages, purchasedUsed: 0 };
1368
1324
  }
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
1325
  usersData[userId].totalUsageCount += numImages;
1376
1326
  usersData[userId].lastUsed = now;
1377
1327
  const lastReset = new Date(usersData[userId].lastDailyReset || usersData[userId].createdAt).toDateString();
@@ -1395,13 +1345,6 @@ function apply(ctx, config) {
1395
1345
  purchasedUsed = purchasedToUse;
1396
1346
  remainingToConsume -= purchasedToUse;
1397
1347
  }
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
1348
  await saveUsersData(usersData);
1406
1349
  let consumptionType;
1407
1350
  if (freeUsed > 0 && purchasedUsed > 0) {
@@ -1414,17 +1357,16 @@ function apply(ctx, config) {
1414
1357
  return { userData: usersData[userId], consumptionType, freeUsed, purchasedUsed };
1415
1358
  }
1416
1359
  __name(updateUserData, "updateUserData");
1417
- async function recordUserUsage(session, commandName, numImages = 1, isSafetyBlock = false) {
1360
+ async function recordUserUsage(session, commandName, numImages = 1) {
1418
1361
  const userId = session.userId;
1419
1362
  const userName = session.username || session.userId || "未知用户";
1420
1363
  if (!userId) return;
1421
- const { userData, consumptionType, freeUsed, purchasedUsed } = await updateUserData(userId, userName, commandName, numImages, isSafetyBlock);
1364
+ const { userData, consumptionType, freeUsed, purchasedUsed } = await updateUserData(userId, userName, commandName, numImages);
1422
1365
  if (isAdmin(userId)) {
1423
- const blockInfo = isSafetyBlock ? "\n⚠️ 本次请求被安全策略拦截,已扣除使用次数" : "";
1424
1366
  await session.send(`📊 使用统计 [管理员]
1425
1367
  用户:${userData.userName}
1426
1368
  总调用次数:${userData.totalUsageCount}次
1427
- 状态:无限制使用${blockInfo}`);
1369
+ 状态:无限制使用`);
1428
1370
  } else {
1429
1371
  const remainingToday = Math.max(0, config.dailyFreeLimit - userData.dailyUsageCount);
1430
1372
  let consumptionText = "";
@@ -1435,14 +1377,13 @@ function apply(ctx, config) {
1435
1377
  } else {
1436
1378
  consumptionText = `充值次数 -${purchasedUsed}`;
1437
1379
  }
1438
- const blockInfo = isSafetyBlock ? "\n⚠️ 本次请求被安全策略拦截,已扣除使用次数。请检查内容是否符合使用规范。" : "";
1439
1380
  await session.send(`📊 使用统计
1440
1381
  用户:${userData.userName}
1441
1382
  本次生成:${numImages}张图片
1442
1383
  本次消费:${consumptionText}
1443
1384
  总调用次数:${userData.totalUsageCount}次
1444
1385
  今日剩余免费:${remainingToday}次
1445
- 充值剩余:${userData.remainingPurchasedCount}次${blockInfo}`);
1386
+ 充值剩余:${userData.remainingPurchasedCount}次`);
1446
1387
  }
1447
1388
  logger.info("用户调用记录", {
1448
1389
  userId,
@@ -1455,12 +1396,44 @@ function apply(ctx, config) {
1455
1396
  totalUsageCount: userData.totalUsageCount,
1456
1397
  dailyUsageCount: userData.dailyUsageCount,
1457
1398
  remainingPurchasedCount: userData.remainingPurchasedCount,
1458
- isSafetyBlock,
1459
- safetyBlockCount: userData.safetyBlockCount,
1460
1399
  isAdmin: isAdmin(userId)
1461
1400
  });
1462
1401
  }
1463
1402
  __name(recordUserUsage, "recordUserUsage");
1403
+ async function recordSecurityBlock(session, numImages = 1) {
1404
+ const userId = session.userId;
1405
+ if (!userId) return;
1406
+ if (isAdmin(userId)) {
1407
+ return;
1408
+ }
1409
+ const now = Date.now();
1410
+ const windowMs = config.securityBlockWindow * 1e3;
1411
+ const windowStart = now - windowMs;
1412
+ let blockTimestamps = securityBlockMap.get(userId) || [];
1413
+ blockTimestamps = blockTimestamps.filter((timestamp) => timestamp > windowStart);
1414
+ blockTimestamps.push(now);
1415
+ securityBlockMap.set(userId, blockTimestamps);
1416
+ const blockCount = blockTimestamps.length;
1417
+ const hasWarning = securityWarningMap.get(userId) || false;
1418
+ logger.info("安全策略拦截记录", {
1419
+ userId,
1420
+ blockCount,
1421
+ threshold: config.securityBlockWarningThreshold,
1422
+ hasWarning,
1423
+ numImages
1424
+ });
1425
+ if (blockCount >= config.securityBlockWarningThreshold && !hasWarning) {
1426
+ securityWarningMap.set(userId, true);
1427
+ await session.send(`⚠️ 安全策略警示
1428
+ 您已连续${config.securityBlockWarningThreshold}次触发安全策略拦截,再次发送被拦截内容将被扣除积分`);
1429
+ logger.warn("用户收到安全策略警示", { userId, blockCount, threshold: config.securityBlockWarningThreshold });
1430
+ } else if (hasWarning) {
1431
+ const commandName = "安全策略拦截";
1432
+ await recordUserUsage(session, commandName, numImages);
1433
+ logger.warn("用户因安全策略拦截被扣除积分", { userId, numImages });
1434
+ }
1435
+ }
1436
+ __name(recordSecurityBlock, "recordSecurityBlock");
1464
1437
  async function getInputData(session, imgParam, mode) {
1465
1438
  const collectedImages = [];
1466
1439
  let collectedText = "";
@@ -1564,11 +1537,19 @@ function apply(ctx, config) {
1564
1537
  new Promise(
1565
1538
  (_, reject) => setTimeout(() => reject(new Error("命令执行超时")), config.commandTimeout * 1e3)
1566
1539
  )
1567
- ]).catch((error) => {
1540
+ ]).catch(async (error) => {
1568
1541
  const userId = session.userId;
1569
1542
  if (userId) activeTasks.delete(userId);
1570
1543
  const sanitizedError = sanitizeError(error);
1571
1544
  logger.error("图像处理超时或失败", { userId, error: sanitizedError });
1545
+ if (error?.message !== "命令执行超时") {
1546
+ const errorMessage = error?.message || "";
1547
+ const isSecurityBlock = errorMessage.includes("内容被安全策略拦截") || errorMessage.includes("内容被安全策略阻止") || errorMessage.includes("内容被阻止") || errorMessage.includes("被阻止") || errorMessage.includes("SAFETY") || errorMessage.includes("RECITATION");
1548
+ if (isSecurityBlock) {
1549
+ const imageCount = requestContext?.numImages || config.defaultNumImages;
1550
+ await recordSecurityBlock(session, imageCount);
1551
+ }
1552
+ }
1572
1553
  const safeMessage = typeof error?.message === "string" ? sanitizeString(error.message) : "未知错误";
1573
1554
  return error.message === "命令执行超时" ? "图像处理超时,请重试" : `图像处理失败:${safeMessage}`;
1574
1555
  });
@@ -1657,17 +1638,10 @@ ${infoParts.join("\n")}`;
1657
1638
  activeTasks.delete(userId);
1658
1639
  const sanitizedError = sanitizeError(error);
1659
1640
  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
- }
1641
+ const errorMessage = error?.message || "";
1642
+ const isSecurityBlock = errorMessage.includes("内容被安全策略拦截") || errorMessage.includes("内容被安全策略阻止") || errorMessage.includes("内容被阻止") || errorMessage.includes("被阻止") || errorMessage.includes("SAFETY") || errorMessage.includes("RECITATION");
1643
+ if (isSecurityBlock) {
1644
+ await recordSecurityBlock(session, imageCount);
1671
1645
  }
1672
1646
  if (error?.message) {
1673
1647
  const safeMessage = sanitizeString(error.message);
@@ -1835,17 +1809,10 @@ Prompt: ${prompt}`);
1835
1809
  activeTasks.delete(userId);
1836
1810
  const sanitizedError = sanitizeError(error);
1837
1811
  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
- }
1812
+ const errorMessage = error?.message || "";
1813
+ const isSecurityBlock = errorMessage.includes("内容被安全策略拦截") || errorMessage.includes("内容被安全策略阻止") || errorMessage.includes("内容被阻止") || errorMessage.includes("被阻止") || errorMessage.includes("SAFETY") || errorMessage.includes("RECITATION");
1814
+ if (isSecurityBlock) {
1815
+ await recordSecurityBlock(session, imageCount);
1849
1816
  }
1850
1817
  if (error?.message) {
1851
1818
  const safeMessage = sanitizeString(error.message);
@@ -1857,11 +1824,19 @@ Prompt: ${prompt}`);
1857
1824
  new Promise(
1858
1825
  (_, reject) => setTimeout(() => reject(new Error("命令执行超时")), config.commandTimeout * 1e3)
1859
1826
  )
1860
- ]).catch((error) => {
1827
+ ]).catch(async (error) => {
1861
1828
  const userId = session.userId;
1862
1829
  if (userId) activeTasks.delete(userId);
1863
1830
  const sanitizedError = sanitizeError(error);
1864
1831
  logger.error("图片合成超时或失败", { userId, error: sanitizedError });
1832
+ if (error?.message !== "命令执行超时") {
1833
+ const errorMessage = error?.message || "";
1834
+ const isSecurityBlock = errorMessage.includes("内容被安全策略拦截") || errorMessage.includes("内容被安全策略阻止") || errorMessage.includes("内容被阻止") || errorMessage.includes("被阻止") || errorMessage.includes("SAFETY") || errorMessage.includes("RECITATION");
1835
+ if (isSecurityBlock) {
1836
+ const imageCount = options?.num || config.defaultNumImages;
1837
+ await recordSecurityBlock(session, imageCount);
1838
+ }
1839
+ }
1865
1840
  const safeMessage = typeof error?.message === "string" ? sanitizeString(error.message) : "未知错误";
1866
1841
  return error.message === "命令执行超时" ? "图片合成超时,请重试" : `图片合成失败:${safeMessage}`;
1867
1842
  });
@@ -1917,9 +1892,7 @@ Prompt: ${prompt}`);
1917
1892
  donationCount: 0,
1918
1893
  donationAmount: 0,
1919
1894
  lastUsed: now,
1920
- createdAt: now,
1921
- safetyBlockCount: 0,
1922
- safetyBlockHistory: []
1895
+ createdAt: now
1923
1896
  };
1924
1897
  }
1925
1898
  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.2",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [