koishi-plugin-aka-ai-generator 0.4.1 → 0.4.4
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 +103 -59
- package/lib/providers/types.d.ts +8 -0
- package/package.json +9 -1
package/lib/index.js
CHANGED
|
@@ -29,6 +29,36 @@ var import_koishi = require("koishi");
|
|
|
29
29
|
var import_fs = require("fs");
|
|
30
30
|
var import_path = require("path");
|
|
31
31
|
|
|
32
|
+
// src/providers/types.ts
|
|
33
|
+
function sanitizeError(error) {
|
|
34
|
+
if (!error) return error;
|
|
35
|
+
if (typeof error === "string") {
|
|
36
|
+
return sanitizeString(error);
|
|
37
|
+
}
|
|
38
|
+
if (Array.isArray(error)) {
|
|
39
|
+
return error.map((item) => sanitizeError(item));
|
|
40
|
+
}
|
|
41
|
+
if (typeof error === "object") {
|
|
42
|
+
const sanitized = {};
|
|
43
|
+
for (const key in error) {
|
|
44
|
+
const lowerKey = key.toLowerCase();
|
|
45
|
+
if (lowerKey.includes("apikey") || lowerKey.includes("api_key") || lowerKey.includes("apikey") || lowerKey === "key" || lowerKey === "authorization" || lowerKey === "token" || lowerKey === "secret" || lowerKey === "password") {
|
|
46
|
+
sanitized[key] = "[REDACTED]";
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
sanitized[key] = sanitizeError(error[key]);
|
|
50
|
+
}
|
|
51
|
+
return sanitized;
|
|
52
|
+
}
|
|
53
|
+
return error;
|
|
54
|
+
}
|
|
55
|
+
__name(sanitizeError, "sanitizeError");
|
|
56
|
+
function sanitizeString(str) {
|
|
57
|
+
if (typeof str !== "string") return str;
|
|
58
|
+
return str.replace(/key["\s:=]+([a-zA-Z0-9_-]{20,})/gi, 'key="[REDACTED]"').replace(/apikey["\s:=]+([a-zA-Z0-9_-]{20,})/gi, 'apikey="[REDACTED]"').replace(/api_key["\s:=]+([a-zA-Z0-9_-]{20,})/gi, 'api_key="[REDACTED]"').replace(/authorization["\s:=]+(Bearer\s+)?([a-zA-Z0-9_-]{20,})/gi, 'authorization="[REDACTED]"').replace(/Bearer\s+([a-zA-Z0-9_-]{20,})/gi, "Bearer [REDACTED]");
|
|
59
|
+
}
|
|
60
|
+
__name(sanitizeString, "sanitizeString");
|
|
61
|
+
|
|
32
62
|
// src/providers/yunwu.ts
|
|
33
63
|
async function downloadImageAsBase64(ctx, url, timeout, logger) {
|
|
34
64
|
try {
|
|
@@ -148,8 +178,9 @@ var YunwuProvider = class {
|
|
|
148
178
|
allImages.push(...images);
|
|
149
179
|
logger.success("云雾图像编辑 API 调用成功", { current: i + 1, total: numImages });
|
|
150
180
|
} catch (error) {
|
|
181
|
+
const safeMessage = typeof error?.message === "string" ? sanitizeString(error.message) : "未知错误";
|
|
151
182
|
logger.error("云雾图像编辑 API 调用失败", {
|
|
152
|
-
message:
|
|
183
|
+
message: safeMessage,
|
|
153
184
|
code: error?.code,
|
|
154
185
|
status: error?.response?.status,
|
|
155
186
|
current: i + 1,
|
|
@@ -516,23 +547,25 @@ var GptGodProvider = class {
|
|
|
516
547
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
517
548
|
continue;
|
|
518
549
|
}
|
|
550
|
+
const sanitizedError = sanitizeError(error);
|
|
551
|
+
const safeMessage = typeof error?.message === "string" ? sanitizeString(error.message) : "未知错误";
|
|
519
552
|
logger.error("GPTGod 图像编辑 API 调用失败", {
|
|
520
|
-
message:
|
|
553
|
+
message: safeMessage,
|
|
521
554
|
name: error?.name,
|
|
522
555
|
code: error?.code,
|
|
523
556
|
status: error?.response?.status,
|
|
524
557
|
statusText: error?.response?.statusText,
|
|
525
|
-
data: error?.response?.data,
|
|
526
|
-
stack: error?.stack,
|
|
527
|
-
cause: error?.cause,
|
|
558
|
+
data: sanitizeError(error?.response?.data),
|
|
559
|
+
stack: sanitizeString(error?.stack || ""),
|
|
560
|
+
cause: sanitizeError(error?.cause),
|
|
528
561
|
attempt,
|
|
529
562
|
maxRetries,
|
|
530
563
|
current: i + 1,
|
|
531
564
|
total: numImages,
|
|
532
|
-
// 如果是 axios 错误,通常会有 config 和 request
|
|
565
|
+
// 如果是 axios 错误,通常会有 config 和 request 信息(清理敏感信息)
|
|
533
566
|
url: error?.config?.url,
|
|
534
567
|
method: error?.config?.method,
|
|
535
|
-
headers: error?.config?.headers
|
|
568
|
+
headers: sanitizeError(error?.config?.headers)
|
|
536
569
|
});
|
|
537
570
|
if (error?.cause?.code === "UND_ERR_SOCKET" || error?.message?.includes("other side closed")) {
|
|
538
571
|
throw new Error("图像处理失败:服务器连接中断,可能是服务器负载过高或网络不稳定,请稍后重试");
|
|
@@ -595,8 +628,11 @@ function parseGeminiResponse(response, logger) {
|
|
|
595
628
|
return [];
|
|
596
629
|
}
|
|
597
630
|
if (response.error) {
|
|
598
|
-
|
|
599
|
-
|
|
631
|
+
const sanitizedError = sanitizeError(response.error);
|
|
632
|
+
logger?.error("Gemini API 返回错误", { error: sanitizedError });
|
|
633
|
+
const errorMessage = response.error.message || JSON.stringify(sanitizedError);
|
|
634
|
+
const safeMessage = sanitizeString(errorMessage);
|
|
635
|
+
throw new Error(`Gemini API 错误: ${safeMessage}`);
|
|
600
636
|
}
|
|
601
637
|
if (response.promptFeedback) {
|
|
602
638
|
const blockReason = response.promptFeedback.blockReason;
|
|
@@ -698,8 +734,12 @@ function parseGeminiResponse(response, logger) {
|
|
|
698
734
|
}
|
|
699
735
|
return images;
|
|
700
736
|
} catch (error) {
|
|
701
|
-
|
|
702
|
-
|
|
737
|
+
const safeMessage = sanitizeString(error?.message || "未知错误");
|
|
738
|
+
const safeStack = sanitizeString(error?.stack || "");
|
|
739
|
+
logger?.error("解析 Gemini 响应时出错", { error: safeMessage, stack: safeStack });
|
|
740
|
+
const sanitizedError = new Error(safeMessage);
|
|
741
|
+
sanitizedError.name = error?.name || "Error";
|
|
742
|
+
throw sanitizedError;
|
|
703
743
|
}
|
|
704
744
|
}
|
|
705
745
|
__name(parseGeminiResponse, "parseGeminiResponse");
|
|
@@ -783,11 +823,13 @@ var GeminiProvider = class {
|
|
|
783
823
|
}
|
|
784
824
|
allImages.push(...images);
|
|
785
825
|
} catch (error) {
|
|
826
|
+
const sanitizedError = sanitizeError(error);
|
|
827
|
+
const safeMessage = typeof error?.message === "string" ? sanitizeString(error.message) : "未知错误";
|
|
786
828
|
logger.error("Gemini API 调用失败", {
|
|
787
|
-
message:
|
|
829
|
+
message: safeMessage,
|
|
788
830
|
code: error?.code,
|
|
789
831
|
status: error?.response?.status,
|
|
790
|
-
responseData: error?.response?.data ? JSON.stringify(error.response.data).substring(0, 500) : void 0,
|
|
832
|
+
responseData: error?.response?.data ? sanitizeString(JSON.stringify(error.response.data).substring(0, 500)) : void 0,
|
|
791
833
|
current: i + 1,
|
|
792
834
|
total: numImages
|
|
793
835
|
});
|
|
@@ -795,7 +837,7 @@ var GeminiProvider = class {
|
|
|
795
837
|
logger.warn("部分图片生成失败,返回已生成的图片", { generated: allImages.length, requested: numImages });
|
|
796
838
|
break;
|
|
797
839
|
}
|
|
798
|
-
throw new Error(`图像处理API调用失败: ${
|
|
840
|
+
throw new Error(`图像处理API调用失败: ${safeMessage}`);
|
|
799
841
|
}
|
|
800
842
|
}
|
|
801
843
|
if (allImages.length === 0) {
|
|
@@ -1122,7 +1164,7 @@ function apply(ctx, config) {
|
|
|
1122
1164
|
rateLimitMap.set(userId, userTimestamps);
|
|
1123
1165
|
}
|
|
1124
1166
|
__name(updateRateLimit, "updateRateLimit");
|
|
1125
|
-
async function checkDailyLimit(userId, numImages = 1) {
|
|
1167
|
+
async function checkDailyLimit(userId, numImages = 1, updateRateLimitImmediately = true) {
|
|
1126
1168
|
if (isAdmin(userId)) {
|
|
1127
1169
|
return { allowed: true, isAdmin: true };
|
|
1128
1170
|
}
|
|
@@ -1130,6 +1172,9 @@ function apply(ctx, config) {
|
|
|
1130
1172
|
if (!rateLimitCheck.allowed) {
|
|
1131
1173
|
return { ...rateLimitCheck, isAdmin: false };
|
|
1132
1174
|
}
|
|
1175
|
+
if (updateRateLimitImmediately) {
|
|
1176
|
+
updateRateLimit(userId);
|
|
1177
|
+
}
|
|
1133
1178
|
const usersData = await loadUsersData();
|
|
1134
1179
|
const userData = usersData[userId];
|
|
1135
1180
|
if (!userData) {
|
|
@@ -1309,7 +1354,6 @@ function apply(ctx, config) {
|
|
|
1309
1354
|
const userId = session.userId;
|
|
1310
1355
|
const userName = session.username || session.userId || "未知用户";
|
|
1311
1356
|
if (!userId) return;
|
|
1312
|
-
updateRateLimit(userId);
|
|
1313
1357
|
const { userData, consumptionType, freeUsed, purchasedUsed } = await updateUserData(userId, userName, commandName, numImages);
|
|
1314
1358
|
if (isAdmin(userId)) {
|
|
1315
1359
|
await session.send(`📊 使用统计 [管理员]
|
|
@@ -1357,22 +1401,18 @@ function apply(ctx, config) {
|
|
|
1357
1401
|
return { images: [], text: imgParam.trim() };
|
|
1358
1402
|
}
|
|
1359
1403
|
await session.send("请输入画面描述");
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
await session.send("检测到图片,请发送文字描述");
|
|
1367
|
-
continue;
|
|
1368
|
-
}
|
|
1369
|
-
const text = import_koishi.h.select(elements, "text").map((e) => e.attrs.content).join(" ").trim();
|
|
1370
|
-
if (!text) {
|
|
1371
|
-
await session.send("未检测到描述,请重新发送");
|
|
1372
|
-
continue;
|
|
1373
|
-
}
|
|
1374
|
-
return { images: [], text };
|
|
1404
|
+
const msg = await session.prompt(3e4);
|
|
1405
|
+
if (!msg) return { error: "等待超时" };
|
|
1406
|
+
const elements = import_koishi.h.parse(msg);
|
|
1407
|
+
const images = import_koishi.h.select(elements, "img");
|
|
1408
|
+
if (images.length > 0) {
|
|
1409
|
+
return { error: "检测到图片,本功能仅支持文字输入" };
|
|
1375
1410
|
}
|
|
1411
|
+
const text = import_koishi.h.select(elements, "text").map((e) => e.attrs.content).join(" ").trim();
|
|
1412
|
+
if (!text) {
|
|
1413
|
+
return { error: "未检测到描述,操作已取消" };
|
|
1414
|
+
}
|
|
1415
|
+
return { images: [], text };
|
|
1376
1416
|
}
|
|
1377
1417
|
if (imgParam) {
|
|
1378
1418
|
if (typeof imgParam === "object" && imgParam.attrs?.src) {
|
|
@@ -1427,8 +1467,7 @@ function apply(ctx, config) {
|
|
|
1427
1467
|
}
|
|
1428
1468
|
if (text) {
|
|
1429
1469
|
if (collectedImages.length === 0) {
|
|
1430
|
-
|
|
1431
|
-
continue;
|
|
1470
|
+
return { error: "未检测到图片,请重新发起指令并发送图片" };
|
|
1432
1471
|
}
|
|
1433
1472
|
collectedText = text;
|
|
1434
1473
|
break;
|
|
@@ -1460,8 +1499,10 @@ function apply(ctx, config) {
|
|
|
1460
1499
|
]).catch((error) => {
|
|
1461
1500
|
const userId = session.userId;
|
|
1462
1501
|
if (userId) activeTasks.delete(userId);
|
|
1463
|
-
|
|
1464
|
-
|
|
1502
|
+
const sanitizedError = sanitizeError(error);
|
|
1503
|
+
logger.error("图像处理超时或失败", { userId, error: sanitizedError });
|
|
1504
|
+
const safeMessage = typeof error?.message === "string" ? sanitizeString(error.message) : "未知错误";
|
|
1505
|
+
return error.message === "命令执行超时" ? "图像处理超时,请重试" : `图像处理失败:${safeMessage}`;
|
|
1465
1506
|
});
|
|
1466
1507
|
}
|
|
1467
1508
|
__name(processImageWithTimeout, "processImageWithTimeout");
|
|
@@ -1486,23 +1527,20 @@ function apply(ctx, config) {
|
|
|
1486
1527
|
finalPrompt = finalPrompt.trim();
|
|
1487
1528
|
if (!finalPrompt) {
|
|
1488
1529
|
await session.send("请发送画面描述");
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
break;
|
|
1504
|
-
}
|
|
1505
|
-
await session.send("未检测到有效文字描述,请重新发送");
|
|
1530
|
+
const promptMsg = await session.prompt(3e4);
|
|
1531
|
+
if (!promptMsg) {
|
|
1532
|
+
return "未检测到描述,操作已取消";
|
|
1533
|
+
}
|
|
1534
|
+
const elements = import_koishi.h.parse(promptMsg);
|
|
1535
|
+
const images = import_koishi.h.select(elements, "img");
|
|
1536
|
+
if (images.length > 0) {
|
|
1537
|
+
return "检测到图片,本功能仅支持文字输入";
|
|
1538
|
+
}
|
|
1539
|
+
const text = import_koishi.h.select(elements, "text").map((e) => e.attrs.content).join(" ").trim();
|
|
1540
|
+
if (text) {
|
|
1541
|
+
finalPrompt = text;
|
|
1542
|
+
} else {
|
|
1543
|
+
return "未检测到有效文字描述,操作已取消";
|
|
1506
1544
|
}
|
|
1507
1545
|
}
|
|
1508
1546
|
const providerType = requestContext?.provider || config.provider;
|
|
@@ -1549,9 +1587,11 @@ ${infoParts.join("\n")}`;
|
|
|
1549
1587
|
activeTasks.delete(userId);
|
|
1550
1588
|
} catch (error) {
|
|
1551
1589
|
activeTasks.delete(userId);
|
|
1552
|
-
|
|
1590
|
+
const sanitizedError = sanitizeError(error);
|
|
1591
|
+
logger.error("图像处理失败", { userId, error: sanitizedError });
|
|
1553
1592
|
if (error?.message) {
|
|
1554
|
-
|
|
1593
|
+
const safeMessage = sanitizeString(error.message);
|
|
1594
|
+
return `图像处理失败:${safeMessage}`;
|
|
1555
1595
|
}
|
|
1556
1596
|
return "图像处理失败,请稍后重试";
|
|
1557
1597
|
}
|
|
@@ -1670,7 +1710,7 @@ ${infoParts.join("\n")}`;
|
|
|
1670
1710
|
prompt = text;
|
|
1671
1711
|
break;
|
|
1672
1712
|
}
|
|
1673
|
-
return "
|
|
1713
|
+
return "未检测到有效内容,操作已取消";
|
|
1674
1714
|
}
|
|
1675
1715
|
if (collectedImages.length < 2) {
|
|
1676
1716
|
return "需要至少两张图片进行合成,请重新发送";
|
|
@@ -1713,9 +1753,11 @@ Prompt: ${prompt}`);
|
|
|
1713
1753
|
activeTasks.delete(userId);
|
|
1714
1754
|
} catch (error) {
|
|
1715
1755
|
activeTasks.delete(userId);
|
|
1716
|
-
|
|
1756
|
+
const sanitizedError = sanitizeError(error);
|
|
1757
|
+
logger.error("图片合成失败", { userId, error: sanitizedError });
|
|
1717
1758
|
if (error?.message) {
|
|
1718
|
-
|
|
1759
|
+
const safeMessage = sanitizeString(error.message);
|
|
1760
|
+
return `图片合成失败:${safeMessage}`;
|
|
1719
1761
|
}
|
|
1720
1762
|
return "图片合成失败,请稍后重试";
|
|
1721
1763
|
}
|
|
@@ -1726,8 +1768,10 @@ Prompt: ${prompt}`);
|
|
|
1726
1768
|
]).catch((error) => {
|
|
1727
1769
|
const userId = session.userId;
|
|
1728
1770
|
if (userId) activeTasks.delete(userId);
|
|
1729
|
-
|
|
1730
|
-
|
|
1771
|
+
const sanitizedError = sanitizeError(error);
|
|
1772
|
+
logger.error("图片合成超时或失败", { userId, error: sanitizedError });
|
|
1773
|
+
const safeMessage = typeof error?.message === "string" ? sanitizeString(error.message) : "未知错误";
|
|
1774
|
+
return error.message === "命令执行超时" ? "图片合成超时,请重试" : `图片合成失败:${safeMessage}`;
|
|
1731
1775
|
});
|
|
1732
1776
|
});
|
|
1733
1777
|
ctx.command(`${COMMANDS.RECHARGE} [content:text]`, "为用户充值次数(仅管理员)").action(async ({ session }, content) => {
|
package/lib/providers/types.d.ts
CHANGED
|
@@ -14,3 +14,11 @@ export interface ProviderConfig {
|
|
|
14
14
|
logger: any;
|
|
15
15
|
ctx: any;
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* 清理对象中的敏感信息(API KEY、密钥等)
|
|
19
|
+
*/
|
|
20
|
+
export declare function sanitizeError(error: any): any;
|
|
21
|
+
/**
|
|
22
|
+
* 清理字符串中的 API KEY 模式
|
|
23
|
+
*/
|
|
24
|
+
export declare function sanitizeString(str: string): string;
|
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.4.
|
|
4
|
+
"version": "0.4.4",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -9,11 +9,19 @@
|
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
11
|
"license": "MIT",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"dev": "tsc --watch"
|
|
15
|
+
},
|
|
12
16
|
"keywords": [
|
|
13
17
|
"chatbot",
|
|
14
18
|
"koishi",
|
|
15
19
|
"plugin"
|
|
16
20
|
],
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^20.0.0",
|
|
23
|
+
"typescript": "^5.0.0"
|
|
24
|
+
},
|
|
17
25
|
"peerDependencies": {
|
|
18
26
|
"koishi": "^4.18.9"
|
|
19
27
|
}
|