koishi-plugin-aka-ai-generator 0.5.4 → 0.6.0
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 +0 -40
- package/lib/index.js +778 -904
- package/lib/providers/index.d.ts +0 -1
- package/lib/providers/types.d.ts +1 -12
- package/lib/providers/utils.d.ts +17 -0
- package/lib/services/UserManager.d.ts +88 -0
- package/lib/utils/parser.d.ts +20 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -25,11 +25,9 @@ __export(src_exports, {
|
|
|
25
25
|
name: () => name
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(src_exports);
|
|
28
|
-
var
|
|
29
|
-
var import_fs = require("fs");
|
|
30
|
-
var import_path = require("path");
|
|
28
|
+
var import_koishi2 = require("koishi");
|
|
31
29
|
|
|
32
|
-
// src/providers/
|
|
30
|
+
// src/providers/utils.ts
|
|
33
31
|
function sanitizeError(error) {
|
|
34
32
|
if (!error) return error;
|
|
35
33
|
if (typeof error === "string") {
|
|
@@ -42,14 +40,10 @@ function sanitizeError(error) {
|
|
|
42
40
|
const sanitized = {};
|
|
43
41
|
for (const key in error) {
|
|
44
42
|
const lowerKey = key.toLowerCase();
|
|
45
|
-
if (lowerKey.includes("apikey") || lowerKey.includes("api_key") || lowerKey
|
|
43
|
+
if (lowerKey.includes("apikey") || lowerKey.includes("api_key") || lowerKey.includes("apikey") || lowerKey === "key" || lowerKey === "authorization" || lowerKey === "token" || lowerKey === "secret" || lowerKey === "password") {
|
|
46
44
|
sanitized[key] = "[REDACTED]";
|
|
47
45
|
continue;
|
|
48
46
|
}
|
|
49
|
-
if (lowerKey === "url" && typeof error[key] === "string") {
|
|
50
|
-
sanitized[key] = sanitizeUrl(error[key]);
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
47
|
sanitized[key] = sanitizeError(error[key]);
|
|
54
48
|
}
|
|
55
49
|
return sanitized;
|
|
@@ -59,196 +53,61 @@ function sanitizeError(error) {
|
|
|
59
53
|
__name(sanitizeError, "sanitizeError");
|
|
60
54
|
function sanitizeString(str) {
|
|
61
55
|
if (typeof str !== "string") return str;
|
|
62
|
-
|
|
63
|
-
sanitized = sanitized.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]").replace(/x-goog-api-key["\s:]+([a-zA-Z0-9_-]{20,})/gi, "x-goog-api-key: [REDACTED]").replace(/X-Goog-Api-Key["\s:]+([a-zA-Z0-9_-]{20,})/gi, "X-Goog-Api-Key: [REDACTED]");
|
|
64
|
-
return sanitized;
|
|
56
|
+
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]");
|
|
65
57
|
}
|
|
66
58
|
__name(sanitizeString, "sanitizeString");
|
|
67
|
-
function
|
|
68
|
-
if (typeof url !== "string") return url;
|
|
69
|
-
try {
|
|
70
|
-
const urlObj = new URL(url);
|
|
71
|
-
urlObj.searchParams.delete("key");
|
|
72
|
-
urlObj.searchParams.delete("apikey");
|
|
73
|
-
urlObj.searchParams.delete("api_key");
|
|
74
|
-
urlObj.searchParams.delete("token");
|
|
75
|
-
urlObj.searchParams.delete("secret");
|
|
76
|
-
return urlObj.toString();
|
|
77
|
-
} catch (e) {
|
|
78
|
-
return sanitizeString(url);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
__name(sanitizeUrl, "sanitizeUrl");
|
|
82
|
-
|
|
83
|
-
// src/providers/yunwu.ts
|
|
84
|
-
async function downloadImageAsBase64(ctx, url, timeout, logger) {
|
|
59
|
+
async function downloadImageAsBase64(ctx, url, timeout, logger, maxSize = 10 * 1024 * 1024) {
|
|
85
60
|
try {
|
|
86
61
|
const response = await ctx.http.get(url, {
|
|
87
62
|
responseType: "arraybuffer",
|
|
88
|
-
timeout: timeout * 1e3
|
|
63
|
+
timeout: timeout * 1e3,
|
|
64
|
+
headers: {
|
|
65
|
+
"Accept": "image/*"
|
|
66
|
+
}
|
|
89
67
|
});
|
|
90
68
|
const buffer = Buffer.from(response);
|
|
69
|
+
if (buffer.length > maxSize) {
|
|
70
|
+
throw new Error(`图片大小超过限制 (${(maxSize / 1024 / 1024).toFixed(1)}MB)`);
|
|
71
|
+
}
|
|
91
72
|
const base64 = buffer.toString("base64");
|
|
92
73
|
let mimeType = "image/jpeg";
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
74
|
+
if (buffer.length > 4) {
|
|
75
|
+
if (buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71) {
|
|
76
|
+
mimeType = "image/png";
|
|
77
|
+
} else if (buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) {
|
|
78
|
+
mimeType = "image/jpeg";
|
|
79
|
+
} else if (buffer[0] === 71 && buffer[1] === 73 && buffer[2] === 70) {
|
|
80
|
+
mimeType = "image/gif";
|
|
81
|
+
} else if (buffer[0] === 82 && buffer[1] === 73 && buffer[2] === 70 && buffer[3] === 70 && buffer[8] === 87 && buffer[9] === 69 && buffer[10] === 66 && buffer[11] === 80) {
|
|
82
|
+
mimeType = "image/webp";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (mimeType === "image/jpeg") {
|
|
86
|
+
const urlLower = url.toLowerCase();
|
|
87
|
+
if (urlLower.endsWith(".png")) {
|
|
88
|
+
mimeType = "image/png";
|
|
89
|
+
} else if (urlLower.endsWith(".webp")) {
|
|
90
|
+
mimeType = "image/webp";
|
|
91
|
+
} else if (urlLower.endsWith(".gif")) {
|
|
92
|
+
mimeType = "image/gif";
|
|
93
|
+
}
|
|
100
94
|
}
|
|
101
95
|
logger.debug("图片下载并转换为Base64", { url, mimeType, size: base64.length });
|
|
102
96
|
return { data: base64, mimeType };
|
|
103
97
|
} catch (error) {
|
|
104
|
-
logger.error("下载图片失败", { url, error });
|
|
98
|
+
logger.error("下载图片失败", { url, error: sanitizeError(error) });
|
|
99
|
+
if (error?.message?.includes("图片大小超过限制")) {
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
105
102
|
throw new Error("下载图片失败,请检查图片链接是否有效");
|
|
106
103
|
}
|
|
107
104
|
}
|
|
108
105
|
__name(downloadImageAsBase64, "downloadImageAsBase64");
|
|
109
|
-
function parseYunwuResponse(response) {
|
|
110
|
-
try {
|
|
111
|
-
const images = [];
|
|
112
|
-
if (response.candidates && response.candidates.length > 0) {
|
|
113
|
-
for (const candidate of response.candidates) {
|
|
114
|
-
if (candidate.content && candidate.content.parts) {
|
|
115
|
-
for (const part of candidate.content.parts) {
|
|
116
|
-
if (part.inlineData && part.inlineData.data) {
|
|
117
|
-
const base64Data = part.inlineData.data;
|
|
118
|
-
const mimeType = part.inlineData.mimeType || "image/jpeg";
|
|
119
|
-
const dataUrl = `data:${mimeType};base64,${base64Data}`;
|
|
120
|
-
images.push(dataUrl);
|
|
121
|
-
} else if (part.inline_data && part.inline_data.data) {
|
|
122
|
-
const base64Data = part.inline_data.data;
|
|
123
|
-
const mimeType = part.inline_data.mime_type || "image/jpeg";
|
|
124
|
-
const dataUrl = `data:${mimeType};base64,${base64Data}`;
|
|
125
|
-
images.push(dataUrl);
|
|
126
|
-
} else if (part.fileData && part.fileData.fileUri) {
|
|
127
|
-
images.push(part.fileData.fileUri);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return images;
|
|
134
|
-
} catch (error) {
|
|
135
|
-
return [];
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
__name(parseYunwuResponse, "parseYunwuResponse");
|
|
139
|
-
var YunwuProvider = class {
|
|
140
|
-
static {
|
|
141
|
-
__name(this, "YunwuProvider");
|
|
142
|
-
}
|
|
143
|
-
config;
|
|
144
|
-
constructor(config) {
|
|
145
|
-
this.config = config;
|
|
146
|
-
}
|
|
147
|
-
async generateImages(prompt, imageUrls, numImages) {
|
|
148
|
-
const urls = Array.isArray(imageUrls) ? imageUrls : [imageUrls];
|
|
149
|
-
const logger = this.config.logger;
|
|
150
|
-
const ctx = this.config.ctx;
|
|
151
|
-
logger.debug("开始下载图片并转换为Base64", { urls });
|
|
152
|
-
const imageParts = [];
|
|
153
|
-
for (const url of urls) {
|
|
154
|
-
const { data, mimeType } = await downloadImageAsBase64(
|
|
155
|
-
ctx,
|
|
156
|
-
url,
|
|
157
|
-
this.config.apiTimeout,
|
|
158
|
-
logger
|
|
159
|
-
);
|
|
160
|
-
imageParts.push({
|
|
161
|
-
inline_data: {
|
|
162
|
-
mime_type: mimeType,
|
|
163
|
-
data
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
const allImages = [];
|
|
168
|
-
for (let i = 0; i < numImages; i++) {
|
|
169
|
-
const requestData = {
|
|
170
|
-
contents: [
|
|
171
|
-
{
|
|
172
|
-
role: "user",
|
|
173
|
-
parts: [
|
|
174
|
-
{ text: prompt },
|
|
175
|
-
...imageParts
|
|
176
|
-
]
|
|
177
|
-
}
|
|
178
|
-
],
|
|
179
|
-
generationConfig: {
|
|
180
|
-
responseModalities: ["IMAGE"]
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
logger.debug("调用云雾图像编辑 API", { prompt, imageCount: urls.length, numImages, current: i + 1 });
|
|
184
|
-
try {
|
|
185
|
-
const response = await ctx.http.post(
|
|
186
|
-
`https://yunwu.ai/v1beta/models/${this.config.modelId}:generateContent`,
|
|
187
|
-
requestData,
|
|
188
|
-
{
|
|
189
|
-
headers: {
|
|
190
|
-
"Content-Type": "application/json"
|
|
191
|
-
},
|
|
192
|
-
params: {
|
|
193
|
-
key: this.config.apiKey
|
|
194
|
-
},
|
|
195
|
-
timeout: this.config.apiTimeout * 1e3
|
|
196
|
-
}
|
|
197
|
-
);
|
|
198
|
-
const images = parseYunwuResponse(response);
|
|
199
|
-
allImages.push(...images);
|
|
200
|
-
logger.success("云雾图像编辑 API 调用成功", { current: i + 1, total: numImages });
|
|
201
|
-
} catch (error) {
|
|
202
|
-
const safeMessage = typeof error?.message === "string" ? sanitizeString(error.message) : "未知错误";
|
|
203
|
-
logger.error("云雾图像编辑 API 调用失败", {
|
|
204
|
-
message: safeMessage,
|
|
205
|
-
code: error?.code,
|
|
206
|
-
status: error?.response?.status,
|
|
207
|
-
current: i + 1,
|
|
208
|
-
total: numImages
|
|
209
|
-
});
|
|
210
|
-
if (allImages.length > 0) {
|
|
211
|
-
logger.warn("部分图片生成失败,返回已生成的图片", { generated: allImages.length, requested: numImages });
|
|
212
|
-
break;
|
|
213
|
-
}
|
|
214
|
-
throw new Error("图像处理API调用失败");
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return allImages;
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
106
|
|
|
221
107
|
// src/providers/gptgod.ts
|
|
222
108
|
var GPTGOD_DEFAULT_API_URL = "https://api.gptgod.online/v1/chat/completions";
|
|
223
109
|
var HTTP_URL_REGEX = /^https?:\/\//i;
|
|
224
110
|
var DATA_URL_REGEX = /^data:image\//i;
|
|
225
|
-
async function downloadImageAsBase642(ctx, url, timeout, logger) {
|
|
226
|
-
try {
|
|
227
|
-
const response = await ctx.http.get(url, {
|
|
228
|
-
responseType: "arraybuffer",
|
|
229
|
-
timeout: timeout * 1e3
|
|
230
|
-
});
|
|
231
|
-
const buffer = Buffer.from(response);
|
|
232
|
-
const base64 = buffer.toString("base64");
|
|
233
|
-
let mimeType = "image/jpeg";
|
|
234
|
-
const urlLower = url.toLowerCase();
|
|
235
|
-
if (urlLower.endsWith(".png")) {
|
|
236
|
-
mimeType = "image/png";
|
|
237
|
-
} else if (urlLower.endsWith(".webp")) {
|
|
238
|
-
mimeType = "image/webp";
|
|
239
|
-
} else if (urlLower.endsWith(".gif")) {
|
|
240
|
-
mimeType = "image/gif";
|
|
241
|
-
}
|
|
242
|
-
if (logger) {
|
|
243
|
-
logger.debug("图片下载并转换为Base64", { url, mimeType, size: base64.length });
|
|
244
|
-
}
|
|
245
|
-
return { data: base64, mimeType };
|
|
246
|
-
} catch (error) {
|
|
247
|
-
logger.error("下载图片失败", { url, error });
|
|
248
|
-
throw new Error("下载图片失败,请检查图片链接是否有效");
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
__name(downloadImageAsBase642, "downloadImageAsBase64");
|
|
252
111
|
function isHttpImage(url) {
|
|
253
112
|
return HTTP_URL_REGEX.test(url);
|
|
254
113
|
}
|
|
@@ -273,7 +132,7 @@ async function buildImageContentPart(ctx, url, timeout, logger) {
|
|
|
273
132
|
image_url: { url }
|
|
274
133
|
};
|
|
275
134
|
}
|
|
276
|
-
const { data, mimeType } = await
|
|
135
|
+
const { data, mimeType } = await downloadImageAsBase64(ctx, url, timeout, logger);
|
|
277
136
|
return {
|
|
278
137
|
type: "image_url",
|
|
279
138
|
image_url: {
|
|
@@ -423,7 +282,7 @@ function parseGptGodResponse(response, logger) {
|
|
|
423
282
|
}
|
|
424
283
|
return images;
|
|
425
284
|
} catch (error) {
|
|
426
|
-
logger?.error("解析响应时出错", { error });
|
|
285
|
+
logger?.error("解析响应时出错", { error: sanitizeError(error) });
|
|
427
286
|
return [];
|
|
428
287
|
}
|
|
429
288
|
}
|
|
@@ -448,13 +307,17 @@ var GptGodProvider = class {
|
|
|
448
307
|
}
|
|
449
308
|
const imageParts = [];
|
|
450
309
|
for (const url of urls) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
310
|
+
try {
|
|
311
|
+
const imagePart = await buildImageContentPart(
|
|
312
|
+
ctx,
|
|
313
|
+
url,
|
|
314
|
+
this.config.apiTimeout,
|
|
315
|
+
logger
|
|
316
|
+
);
|
|
317
|
+
imageParts.push(imagePart);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
logger.error("构建图片部分失败,跳过该图片", { url, error: sanitizeError(error) });
|
|
320
|
+
}
|
|
458
321
|
}
|
|
459
322
|
const allImages = [];
|
|
460
323
|
for (let i = 0; i < numImages; i++) {
|
|
@@ -568,7 +431,6 @@ var GptGodProvider = class {
|
|
|
568
431
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
569
432
|
continue;
|
|
570
433
|
}
|
|
571
|
-
const sanitizedError = sanitizeError(error);
|
|
572
434
|
const safeMessage = typeof error?.message === "string" ? sanitizeString(error.message) : "未知错误";
|
|
573
435
|
logger.error("GPTGod 图像编辑 API 调用失败", {
|
|
574
436
|
message: safeMessage,
|
|
@@ -616,31 +478,6 @@ var GptGodProvider = class {
|
|
|
616
478
|
};
|
|
617
479
|
|
|
618
480
|
// src/providers/gemini.ts
|
|
619
|
-
async function downloadImageAsBase643(ctx, url, timeout, logger) {
|
|
620
|
-
try {
|
|
621
|
-
const response = await ctx.http.get(url, {
|
|
622
|
-
responseType: "arraybuffer",
|
|
623
|
-
timeout: timeout * 1e3
|
|
624
|
-
});
|
|
625
|
-
const buffer = Buffer.from(response);
|
|
626
|
-
const base64 = buffer.toString("base64");
|
|
627
|
-
let mimeType = "image/jpeg";
|
|
628
|
-
const urlLower = url.toLowerCase();
|
|
629
|
-
if (urlLower.endsWith(".png")) {
|
|
630
|
-
mimeType = "image/png";
|
|
631
|
-
} else if (urlLower.endsWith(".webp")) {
|
|
632
|
-
mimeType = "image/webp";
|
|
633
|
-
} else if (urlLower.endsWith(".gif")) {
|
|
634
|
-
mimeType = "image/gif";
|
|
635
|
-
}
|
|
636
|
-
logger.debug("图片下载并转换为Base64", { url, mimeType, size: base64.length });
|
|
637
|
-
return { data: base64, mimeType };
|
|
638
|
-
} catch (error) {
|
|
639
|
-
logger.error("下载图片失败", { url, error });
|
|
640
|
-
throw new Error("下载图片失败,请检查图片链接是否有效");
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
__name(downloadImageAsBase643, "downloadImageAsBase64");
|
|
644
481
|
function parseGeminiResponse(response, logger) {
|
|
645
482
|
try {
|
|
646
483
|
const images = [];
|
|
@@ -773,9 +610,6 @@ var GeminiProvider = class {
|
|
|
773
610
|
this.config = config;
|
|
774
611
|
}
|
|
775
612
|
async generateImages(prompt, imageUrls, numImages) {
|
|
776
|
-
if (!this.config.apiKey || !this.config.apiKey.trim()) {
|
|
777
|
-
throw new Error("Gemini API key 未配置或为空");
|
|
778
|
-
}
|
|
779
613
|
let urls = [];
|
|
780
614
|
if (Array.isArray(imageUrls)) {
|
|
781
615
|
urls = imageUrls.filter((url) => url && typeof url === "string" && url.trim());
|
|
@@ -788,18 +622,22 @@ var GeminiProvider = class {
|
|
|
788
622
|
const imageParts = [];
|
|
789
623
|
for (const url of urls) {
|
|
790
624
|
if (!url || !url.trim()) continue;
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
625
|
+
try {
|
|
626
|
+
const { data, mimeType } = await downloadImageAsBase64(
|
|
627
|
+
ctx,
|
|
628
|
+
url,
|
|
629
|
+
this.config.apiTimeout,
|
|
630
|
+
logger
|
|
631
|
+
);
|
|
632
|
+
imageParts.push({
|
|
633
|
+
inline_data: {
|
|
634
|
+
mime_type: mimeType,
|
|
635
|
+
data
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
} catch (error) {
|
|
639
|
+
logger.error("处理输入图片失败,跳过该图片", { url, error: sanitizeError(error) });
|
|
640
|
+
}
|
|
803
641
|
}
|
|
804
642
|
const apiBase = this.config.apiBase?.replace(/\/$/, "") || "https://generativelanguage.googleapis.com";
|
|
805
643
|
const endpoint = `${apiBase}/v1beta/models/${this.config.modelId}:generateContent`;
|
|
@@ -808,6 +646,7 @@ var GeminiProvider = class {
|
|
|
808
646
|
const requestData = {
|
|
809
647
|
contents: [
|
|
810
648
|
{
|
|
649
|
+
role: "user",
|
|
811
650
|
parts: [
|
|
812
651
|
{ text: prompt },
|
|
813
652
|
...imageParts
|
|
@@ -815,22 +654,10 @@ var GeminiProvider = class {
|
|
|
815
654
|
}
|
|
816
655
|
],
|
|
817
656
|
generationConfig: {
|
|
818
|
-
responseModalities: ["IMAGE"]
|
|
819
|
-
imageConfig: {
|
|
820
|
-
aspectRatio: "16:9"
|
|
821
|
-
}
|
|
657
|
+
responseModalities: ["IMAGE"]
|
|
822
658
|
}
|
|
823
659
|
};
|
|
824
|
-
logger.debug("调用 Gemini API", {
|
|
825
|
-
prompt: prompt.substring(0, 100),
|
|
826
|
-
imageCount: urls.length,
|
|
827
|
-
numImages,
|
|
828
|
-
current: i + 1,
|
|
829
|
-
endpoint,
|
|
830
|
-
hasApiKey: !!this.config.apiKey,
|
|
831
|
-
apiKeyLength: this.config.apiKey?.length || 0,
|
|
832
|
-
modelId: this.config.modelId
|
|
833
|
-
});
|
|
660
|
+
logger.debug("调用 Gemini API", { prompt, imageCount: urls.length, numImages, current: i + 1, endpoint });
|
|
834
661
|
try {
|
|
835
662
|
const response = await ctx.http.post(
|
|
836
663
|
endpoint,
|
|
@@ -860,33 +687,14 @@ var GeminiProvider = class {
|
|
|
860
687
|
} catch (error) {
|
|
861
688
|
const sanitizedError = sanitizeError(error);
|
|
862
689
|
const safeMessage = typeof error?.message === "string" ? sanitizeString(error.message) : "未知错误";
|
|
863
|
-
|
|
690
|
+
logger.error("Gemini API 调用失败", {
|
|
864
691
|
message: safeMessage,
|
|
865
692
|
code: error?.code,
|
|
866
693
|
status: error?.response?.status,
|
|
867
|
-
|
|
694
|
+
responseData: error?.response?.data ? sanitizeString(JSON.stringify(error.response.data).substring(0, 500)) : void 0,
|
|
868
695
|
current: i + 1,
|
|
869
|
-
total: numImages
|
|
870
|
-
|
|
871
|
-
};
|
|
872
|
-
if (error?.response?.data) {
|
|
873
|
-
try {
|
|
874
|
-
const responseStr = JSON.stringify(error.response.data);
|
|
875
|
-
errorDetails.responseData = sanitizeString(responseStr.substring(0, 1e3));
|
|
876
|
-
} catch (e) {
|
|
877
|
-
errorDetails.responseData = "无法序列化响应数据";
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
if (error?.config) {
|
|
881
|
-
errorDetails.url = error.config.url ? sanitizeUrl(error.config.url) : void 0;
|
|
882
|
-
errorDetails.method = error.config.method;
|
|
883
|
-
errorDetails.hasData = !!error.config.data;
|
|
884
|
-
}
|
|
885
|
-
if (error?.code === "ECONNREFUSED" || error?.code === "ETIMEDOUT" || error?.code === "ENOTFOUND") {
|
|
886
|
-
errorDetails.networkError = true;
|
|
887
|
-
errorDetails.networkErrorCode = error.code;
|
|
888
|
-
}
|
|
889
|
-
logger.error("Gemini API 调用失败", errorDetails);
|
|
696
|
+
total: numImages
|
|
697
|
+
});
|
|
890
698
|
if (allImages.length > 0) {
|
|
891
699
|
logger.warn("部分图片生成失败,返回已生成的图片", { generated: allImages.length, requested: numImages });
|
|
892
700
|
break;
|
|
@@ -906,9 +714,11 @@ var GeminiProvider = class {
|
|
|
906
714
|
function createImageProvider(config) {
|
|
907
715
|
switch (config.provider) {
|
|
908
716
|
case "yunwu":
|
|
909
|
-
return new
|
|
717
|
+
return new GeminiProvider({
|
|
910
718
|
apiKey: config.yunwuApiKey,
|
|
911
719
|
modelId: config.yunwuModelId,
|
|
720
|
+
apiBase: "https://yunwu.ai",
|
|
721
|
+
// 指定云雾 API 地址
|
|
912
722
|
apiTimeout: config.apiTimeout,
|
|
913
723
|
logLevel: config.logLevel,
|
|
914
724
|
logger: config.logger,
|
|
@@ -939,6 +749,412 @@ function createImageProvider(config) {
|
|
|
939
749
|
}
|
|
940
750
|
__name(createImageProvider, "createImageProvider");
|
|
941
751
|
|
|
752
|
+
// src/services/UserManager.ts
|
|
753
|
+
var import_fs = require("fs");
|
|
754
|
+
var import_path = require("path");
|
|
755
|
+
var AsyncLock = class {
|
|
756
|
+
static {
|
|
757
|
+
__name(this, "AsyncLock");
|
|
758
|
+
}
|
|
759
|
+
promise = Promise.resolve();
|
|
760
|
+
async acquire(fn) {
|
|
761
|
+
const previousPromise = this.promise;
|
|
762
|
+
let release;
|
|
763
|
+
const nextPromise = new Promise((resolve) => {
|
|
764
|
+
release = resolve;
|
|
765
|
+
});
|
|
766
|
+
this.promise = nextPromise;
|
|
767
|
+
await previousPromise;
|
|
768
|
+
try {
|
|
769
|
+
return await fn();
|
|
770
|
+
} finally {
|
|
771
|
+
release();
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
var UserManager = class {
|
|
776
|
+
static {
|
|
777
|
+
__name(this, "UserManager");
|
|
778
|
+
}
|
|
779
|
+
dataDir;
|
|
780
|
+
dataFile;
|
|
781
|
+
backupFile;
|
|
782
|
+
rechargeHistoryFile;
|
|
783
|
+
logger;
|
|
784
|
+
dataLock = new AsyncLock();
|
|
785
|
+
historyLock = new AsyncLock();
|
|
786
|
+
// 内存缓存
|
|
787
|
+
usersCache = null;
|
|
788
|
+
activeTasks = /* @__PURE__ */ new Map();
|
|
789
|
+
// userId -> requestId
|
|
790
|
+
rateLimitMap = /* @__PURE__ */ new Map();
|
|
791
|
+
// userId -> timestamps
|
|
792
|
+
securityBlockMap = /* @__PURE__ */ new Map();
|
|
793
|
+
// userId -> 拦截时间戳数组
|
|
794
|
+
securityWarningMap = /* @__PURE__ */ new Map();
|
|
795
|
+
// userId -> 是否已收到警示
|
|
796
|
+
constructor(baseDir, logger) {
|
|
797
|
+
this.logger = logger;
|
|
798
|
+
this.dataDir = (0, import_path.join)(baseDir, "data/aka-ai-generator");
|
|
799
|
+
this.dataFile = (0, import_path.join)(this.dataDir, "users_data.json");
|
|
800
|
+
this.backupFile = (0, import_path.join)(this.dataDir, "users_data.json.backup");
|
|
801
|
+
this.rechargeHistoryFile = (0, import_path.join)(this.dataDir, "recharge_history.json");
|
|
802
|
+
if (!(0, import_fs.existsSync)(this.dataDir)) {
|
|
803
|
+
(0, import_fs.mkdirSync)(this.dataDir, { recursive: true });
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
// --- 任务管理 ---
|
|
807
|
+
startTask(userId) {
|
|
808
|
+
if (this.activeTasks.has(userId)) return false;
|
|
809
|
+
this.activeTasks.set(userId, "processing");
|
|
810
|
+
return true;
|
|
811
|
+
}
|
|
812
|
+
endTask(userId) {
|
|
813
|
+
this.activeTasks.delete(userId);
|
|
814
|
+
}
|
|
815
|
+
isTaskActive(userId) {
|
|
816
|
+
return this.activeTasks.has(userId);
|
|
817
|
+
}
|
|
818
|
+
// --- 权限管理 ---
|
|
819
|
+
isAdmin(userId, config) {
|
|
820
|
+
return config.adminUsers && config.adminUsers.includes(userId);
|
|
821
|
+
}
|
|
822
|
+
// --- 数据持久化 ---
|
|
823
|
+
async loadUsersData() {
|
|
824
|
+
if (this.usersCache) return this.usersCache;
|
|
825
|
+
return await this.dataLock.acquire(async () => {
|
|
826
|
+
if (this.usersCache) return this.usersCache;
|
|
827
|
+
try {
|
|
828
|
+
if ((0, import_fs.existsSync)(this.dataFile)) {
|
|
829
|
+
const data = await import_fs.promises.readFile(this.dataFile, "utf-8");
|
|
830
|
+
this.usersCache = JSON.parse(data);
|
|
831
|
+
return this.usersCache;
|
|
832
|
+
}
|
|
833
|
+
} catch (error) {
|
|
834
|
+
this.logger.error("读取用户数据失败", error);
|
|
835
|
+
if ((0, import_fs.existsSync)(this.backupFile)) {
|
|
836
|
+
try {
|
|
837
|
+
const backupData = await import_fs.promises.readFile(this.backupFile, "utf-8");
|
|
838
|
+
this.logger.warn("从备份文件恢复用户数据");
|
|
839
|
+
this.usersCache = JSON.parse(backupData);
|
|
840
|
+
return this.usersCache;
|
|
841
|
+
} catch (backupError) {
|
|
842
|
+
this.logger.error("备份文件也损坏,使用空数据", backupError);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
this.usersCache = {};
|
|
847
|
+
return this.usersCache;
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
// 保存所有用户数据(内部使用)
|
|
851
|
+
async saveUsersDataInternal() {
|
|
852
|
+
if (!this.usersCache) return;
|
|
853
|
+
try {
|
|
854
|
+
if ((0, import_fs.existsSync)(this.dataFile)) {
|
|
855
|
+
await import_fs.promises.copyFile(this.dataFile, this.backupFile);
|
|
856
|
+
}
|
|
857
|
+
await import_fs.promises.writeFile(this.dataFile, JSON.stringify(this.usersCache, null, 2), "utf-8");
|
|
858
|
+
} catch (error) {
|
|
859
|
+
this.logger.error("保存用户数据失败", error);
|
|
860
|
+
throw error;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
// 获取特定用户数据
|
|
864
|
+
async getUserData(userId, userName) {
|
|
865
|
+
await this.loadUsersData();
|
|
866
|
+
if (!this.usersCache[userId]) {
|
|
867
|
+
await this.dataLock.acquire(async () => {
|
|
868
|
+
if (this.usersCache[userId]) return;
|
|
869
|
+
this.usersCache[userId] = {
|
|
870
|
+
userId,
|
|
871
|
+
userName,
|
|
872
|
+
totalUsageCount: 0,
|
|
873
|
+
dailyUsageCount: 0,
|
|
874
|
+
lastDailyReset: (/* @__PURE__ */ new Date()).toISOString(),
|
|
875
|
+
purchasedCount: 0,
|
|
876
|
+
remainingPurchasedCount: 0,
|
|
877
|
+
donationCount: 0,
|
|
878
|
+
donationAmount: 0,
|
|
879
|
+
lastUsed: (/* @__PURE__ */ new Date()).toISOString(),
|
|
880
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
881
|
+
};
|
|
882
|
+
await this.saveUsersDataInternal();
|
|
883
|
+
this.logger.info("创建新用户数据", { userId, userName });
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
return this.usersCache[userId];
|
|
887
|
+
}
|
|
888
|
+
// 获取所有用户数据 (用于充值等批量操作)
|
|
889
|
+
async getAllUsers() {
|
|
890
|
+
return await this.loadUsersData();
|
|
891
|
+
}
|
|
892
|
+
// 批量更新用户数据 (用于充值)
|
|
893
|
+
async updateUsersBatch(updates) {
|
|
894
|
+
await this.dataLock.acquire(async () => {
|
|
895
|
+
await this.loadUsersData();
|
|
896
|
+
updates(this.usersCache);
|
|
897
|
+
await this.saveUsersDataInternal();
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
// --- 充值历史 ---
|
|
901
|
+
async loadRechargeHistory() {
|
|
902
|
+
return await this.historyLock.acquire(async () => {
|
|
903
|
+
try {
|
|
904
|
+
if ((0, import_fs.existsSync)(this.rechargeHistoryFile)) {
|
|
905
|
+
const data = await import_fs.promises.readFile(this.rechargeHistoryFile, "utf-8");
|
|
906
|
+
return JSON.parse(data);
|
|
907
|
+
}
|
|
908
|
+
} catch (error) {
|
|
909
|
+
this.logger.error("读取充值历史失败", error);
|
|
910
|
+
}
|
|
911
|
+
return {
|
|
912
|
+
version: "1.0.0",
|
|
913
|
+
lastUpdate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
914
|
+
records: []
|
|
915
|
+
};
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
async addRechargeRecord(record) {
|
|
919
|
+
await this.historyLock.acquire(async () => {
|
|
920
|
+
let history;
|
|
921
|
+
try {
|
|
922
|
+
if ((0, import_fs.existsSync)(this.rechargeHistoryFile)) {
|
|
923
|
+
history = JSON.parse(await import_fs.promises.readFile(this.rechargeHistoryFile, "utf-8"));
|
|
924
|
+
} else {
|
|
925
|
+
history = { version: "1.0.0", lastUpdate: "", records: [] };
|
|
926
|
+
}
|
|
927
|
+
} catch (e) {
|
|
928
|
+
history = { version: "1.0.0", lastUpdate: "", records: [] };
|
|
929
|
+
}
|
|
930
|
+
history.records.push(record);
|
|
931
|
+
history.lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
932
|
+
await import_fs.promises.writeFile(this.rechargeHistoryFile, JSON.stringify(history, null, 2), "utf-8");
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
// --- 限流逻辑 ---
|
|
936
|
+
checkRateLimit(userId, config) {
|
|
937
|
+
const now = Date.now();
|
|
938
|
+
const userTimestamps = this.rateLimitMap.get(userId) || [];
|
|
939
|
+
const windowStart = now - config.rateLimitWindow * 1e3;
|
|
940
|
+
const validTimestamps = userTimestamps.filter((timestamp) => timestamp > windowStart);
|
|
941
|
+
this.rateLimitMap.set(userId, validTimestamps);
|
|
942
|
+
if (validTimestamps.length >= config.rateLimitMax) {
|
|
943
|
+
return {
|
|
944
|
+
allowed: false,
|
|
945
|
+
message: `操作过于频繁,请${Math.ceil((validTimestamps[0] + config.rateLimitWindow * 1e3 - now) / 1e3)}秒后再试`
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
return { allowed: true };
|
|
949
|
+
}
|
|
950
|
+
updateRateLimit(userId) {
|
|
951
|
+
const now = Date.now();
|
|
952
|
+
const userTimestamps = this.rateLimitMap.get(userId) || [];
|
|
953
|
+
userTimestamps.push(now);
|
|
954
|
+
this.rateLimitMap.set(userId, userTimestamps);
|
|
955
|
+
}
|
|
956
|
+
// --- 核心业务逻辑 ---
|
|
957
|
+
async checkDailyLimit(userId, config, numImages = 1) {
|
|
958
|
+
if (this.isAdmin(userId, config)) {
|
|
959
|
+
return { allowed: true, isAdmin: true };
|
|
960
|
+
}
|
|
961
|
+
const rateLimitCheck = this.checkRateLimit(userId, config);
|
|
962
|
+
if (!rateLimitCheck.allowed) {
|
|
963
|
+
return { ...rateLimitCheck, isAdmin: false };
|
|
964
|
+
}
|
|
965
|
+
this.updateRateLimit(userId);
|
|
966
|
+
const userData = await this.getUserData(userId, userId);
|
|
967
|
+
const today = (/* @__PURE__ */ new Date()).toDateString();
|
|
968
|
+
const lastReset = new Date(userData.lastDailyReset || userData.createdAt).toDateString();
|
|
969
|
+
let dailyCount = userData.dailyUsageCount;
|
|
970
|
+
if (today !== lastReset) {
|
|
971
|
+
dailyCount = 0;
|
|
972
|
+
}
|
|
973
|
+
if (numImages > config.dailyFreeLimit && userData.totalUsageCount === 0 && userData.purchasedCount === 0) {
|
|
974
|
+
}
|
|
975
|
+
const remainingToday = Math.max(0, config.dailyFreeLimit - dailyCount);
|
|
976
|
+
const totalAvailable = remainingToday + userData.remainingPurchasedCount;
|
|
977
|
+
if (totalAvailable < numImages) {
|
|
978
|
+
return {
|
|
979
|
+
allowed: false,
|
|
980
|
+
message: `生成 ${numImages} 张图片需要 ${numImages} 次可用次数,但您的可用次数不足(今日免费剩余:${remainingToday}次,充值剩余:${userData.remainingPurchasedCount}次,共${totalAvailable}次)`,
|
|
981
|
+
isAdmin: false
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
return { allowed: true, isAdmin: false };
|
|
985
|
+
}
|
|
986
|
+
// 扣减额度并记录使用
|
|
987
|
+
async consumeQuota(userId, userName, commandName, numImages, config) {
|
|
988
|
+
return await this.dataLock.acquire(async () => {
|
|
989
|
+
await this.loadUsersData();
|
|
990
|
+
let userData = this.usersCache[userId];
|
|
991
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
992
|
+
const today = (/* @__PURE__ */ new Date()).toDateString();
|
|
993
|
+
if (!userData) {
|
|
994
|
+
userData = {
|
|
995
|
+
userId,
|
|
996
|
+
userName: userName || userId,
|
|
997
|
+
totalUsageCount: 0,
|
|
998
|
+
dailyUsageCount: 0,
|
|
999
|
+
lastDailyReset: now,
|
|
1000
|
+
purchasedCount: 0,
|
|
1001
|
+
remainingPurchasedCount: 0,
|
|
1002
|
+
donationCount: 0,
|
|
1003
|
+
donationAmount: 0,
|
|
1004
|
+
lastUsed: now,
|
|
1005
|
+
createdAt: now
|
|
1006
|
+
};
|
|
1007
|
+
this.usersCache[userId] = userData;
|
|
1008
|
+
}
|
|
1009
|
+
userData.totalUsageCount += numImages;
|
|
1010
|
+
userData.lastUsed = now;
|
|
1011
|
+
const lastReset = new Date(userData.lastDailyReset || userData.createdAt).toDateString();
|
|
1012
|
+
if (today !== lastReset) {
|
|
1013
|
+
userData.dailyUsageCount = 0;
|
|
1014
|
+
userData.lastDailyReset = now;
|
|
1015
|
+
}
|
|
1016
|
+
let remainingToConsume = numImages;
|
|
1017
|
+
let freeUsed = 0;
|
|
1018
|
+
let purchasedUsed = 0;
|
|
1019
|
+
const availableFree = Math.max(0, config.dailyFreeLimit - userData.dailyUsageCount);
|
|
1020
|
+
if (availableFree > 0) {
|
|
1021
|
+
const freeToUse = Math.min(availableFree, remainingToConsume);
|
|
1022
|
+
userData.dailyUsageCount += freeToUse;
|
|
1023
|
+
freeUsed = freeToUse;
|
|
1024
|
+
remainingToConsume -= freeToUse;
|
|
1025
|
+
}
|
|
1026
|
+
if (remainingToConsume > 0) {
|
|
1027
|
+
const purchasedToUse = Math.min(userData.remainingPurchasedCount, remainingToConsume);
|
|
1028
|
+
userData.remainingPurchasedCount -= purchasedToUse;
|
|
1029
|
+
purchasedUsed = purchasedToUse;
|
|
1030
|
+
remainingToConsume -= purchasedToUse;
|
|
1031
|
+
}
|
|
1032
|
+
await this.saveUsersDataInternal();
|
|
1033
|
+
let consumptionType;
|
|
1034
|
+
if (freeUsed > 0 && purchasedUsed > 0) {
|
|
1035
|
+
consumptionType = "mixed";
|
|
1036
|
+
} else if (freeUsed > 0) {
|
|
1037
|
+
consumptionType = "free";
|
|
1038
|
+
} else {
|
|
1039
|
+
consumptionType = "purchased";
|
|
1040
|
+
}
|
|
1041
|
+
return { userData, consumptionType, freeUsed, purchasedUsed };
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
// 记录安全拦截
|
|
1045
|
+
async recordSecurityBlock(userId, config) {
|
|
1046
|
+
if (!userId) return { shouldWarn: false, shouldDeduct: false, blockCount: 0 };
|
|
1047
|
+
if (this.isAdmin(userId, config)) return { shouldWarn: false, shouldDeduct: false, blockCount: 0 };
|
|
1048
|
+
const now = Date.now();
|
|
1049
|
+
const windowMs = config.securityBlockWindow * 1e3;
|
|
1050
|
+
const windowStart = now - windowMs;
|
|
1051
|
+
let blockTimestamps = this.securityBlockMap.get(userId) || [];
|
|
1052
|
+
blockTimestamps = blockTimestamps.filter((timestamp) => timestamp > windowStart);
|
|
1053
|
+
blockTimestamps.push(now);
|
|
1054
|
+
this.securityBlockMap.set(userId, blockTimestamps);
|
|
1055
|
+
const blockCount = blockTimestamps.length;
|
|
1056
|
+
const hasWarning = this.securityWarningMap.get(userId) || false;
|
|
1057
|
+
let shouldWarn = false;
|
|
1058
|
+
let shouldDeduct = false;
|
|
1059
|
+
if (blockCount >= config.securityBlockWarningThreshold && !hasWarning) {
|
|
1060
|
+
this.securityWarningMap.set(userId, true);
|
|
1061
|
+
shouldWarn = true;
|
|
1062
|
+
} else if (hasWarning) {
|
|
1063
|
+
shouldDeduct = true;
|
|
1064
|
+
}
|
|
1065
|
+
return { shouldWarn, shouldDeduct, blockCount };
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
// src/utils/parser.ts
|
|
1070
|
+
var import_koishi = require("koishi");
|
|
1071
|
+
function normalizeSuffix(value) {
|
|
1072
|
+
return value?.replace(/^\-+/, "").trim().toLowerCase();
|
|
1073
|
+
}
|
|
1074
|
+
__name(normalizeSuffix, "normalizeSuffix");
|
|
1075
|
+
function buildModelMappingIndex(mappings) {
|
|
1076
|
+
const map = /* @__PURE__ */ new Map();
|
|
1077
|
+
if (!Array.isArray(mappings)) return map;
|
|
1078
|
+
for (const mapping of mappings) {
|
|
1079
|
+
const key = normalizeSuffix(mapping?.suffix);
|
|
1080
|
+
if (!key || !mapping?.modelId) continue;
|
|
1081
|
+
map.set(key, mapping);
|
|
1082
|
+
}
|
|
1083
|
+
return map;
|
|
1084
|
+
}
|
|
1085
|
+
__name(buildModelMappingIndex, "buildModelMappingIndex");
|
|
1086
|
+
function parseStyleCommandModifiers(argv, imgParam, modelMappingIndex) {
|
|
1087
|
+
const session = argv.session;
|
|
1088
|
+
let rawText = "";
|
|
1089
|
+
if (session?.content) {
|
|
1090
|
+
const elements = import_koishi.h.parse(session.content);
|
|
1091
|
+
rawText = import_koishi.h.select(elements, "text").map((e) => e.attrs.content).join(" ");
|
|
1092
|
+
}
|
|
1093
|
+
const argsList = rawText ? rawText.split(/\s+/).filter(Boolean) : [...argv.args || []].map((arg) => typeof arg === "string" ? arg.trim() : "").filter(Boolean);
|
|
1094
|
+
if (!rawText) {
|
|
1095
|
+
const restStr = typeof argv.rest === "string" ? argv.rest.trim() : "";
|
|
1096
|
+
if (restStr) {
|
|
1097
|
+
const restParts = restStr.split(/\s+/).filter(Boolean);
|
|
1098
|
+
argsList.push(...restParts);
|
|
1099
|
+
}
|
|
1100
|
+
if (imgParam && typeof imgParam === "string" && !imgParam.startsWith("http") && !imgParam.startsWith("data:")) {
|
|
1101
|
+
const imgParts = imgParam.split(/\s+/).filter(Boolean);
|
|
1102
|
+
argsList.push(...imgParts);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
if (!argsList.length) return {};
|
|
1106
|
+
const modifiers = { customAdditions: [] };
|
|
1107
|
+
const flagCandidates = [];
|
|
1108
|
+
let index = 0;
|
|
1109
|
+
while (index < argsList.length) {
|
|
1110
|
+
const token = argsList[index];
|
|
1111
|
+
if (!token) {
|
|
1112
|
+
index++;
|
|
1113
|
+
continue;
|
|
1114
|
+
}
|
|
1115
|
+
const lower = token.toLowerCase();
|
|
1116
|
+
if (lower.startsWith("-prompt:")) {
|
|
1117
|
+
const promptHead = token.substring(token.indexOf(":") + 1);
|
|
1118
|
+
const restTokens = argsList.slice(index + 1);
|
|
1119
|
+
modifiers.customPromptSuffix = [promptHead, ...restTokens].join(" ").trim();
|
|
1120
|
+
break;
|
|
1121
|
+
}
|
|
1122
|
+
if (lower === "-add") {
|
|
1123
|
+
index++;
|
|
1124
|
+
const additionTokens = [];
|
|
1125
|
+
while (index < argsList.length) {
|
|
1126
|
+
const nextToken = argsList[index];
|
|
1127
|
+
if (nextToken.startsWith("-")) {
|
|
1128
|
+
const key = normalizeSuffix(nextToken);
|
|
1129
|
+
if (key && modelMappingIndex.has(key)) break;
|
|
1130
|
+
if (nextToken.toLowerCase().startsWith("-prompt:")) break;
|
|
1131
|
+
if (nextToken.toLowerCase() === "-add") break;
|
|
1132
|
+
}
|
|
1133
|
+
additionTokens.push(nextToken);
|
|
1134
|
+
index++;
|
|
1135
|
+
}
|
|
1136
|
+
if (additionTokens.length) {
|
|
1137
|
+
modifiers.customAdditions.push(additionTokens.join(" "));
|
|
1138
|
+
}
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
flagCandidates.push(token);
|
|
1142
|
+
index++;
|
|
1143
|
+
}
|
|
1144
|
+
for (const arg of flagCandidates) {
|
|
1145
|
+
if (!arg.startsWith("-")) continue;
|
|
1146
|
+
const key = normalizeSuffix(arg);
|
|
1147
|
+
if (!key) continue;
|
|
1148
|
+
const mapping = modelMappingIndex.get(key);
|
|
1149
|
+
if (mapping) {
|
|
1150
|
+
modifiers.modelMapping = mapping;
|
|
1151
|
+
break;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return modifiers;
|
|
1155
|
+
}
|
|
1156
|
+
__name(parseStyleCommandModifiers, "parseStyleCommandModifiers");
|
|
1157
|
+
|
|
942
1158
|
// src/index.ts
|
|
943
1159
|
var name = "aka-ai-generator";
|
|
944
1160
|
var COMMANDS = {
|
|
@@ -955,56 +1171,56 @@ var COMMANDS = {
|
|
|
955
1171
|
FUNCTION_LIST: "图像功能",
|
|
956
1172
|
IMAGE_COMMANDS: "图像指令"
|
|
957
1173
|
};
|
|
958
|
-
var StyleItemSchema =
|
|
959
|
-
commandName:
|
|
960
|
-
prompt:
|
|
1174
|
+
var StyleItemSchema = import_koishi2.Schema.object({
|
|
1175
|
+
commandName: import_koishi2.Schema.string().required().description("命令名称").role("table-cell", { width: 100 }),
|
|
1176
|
+
prompt: import_koishi2.Schema.string().role("textarea", { rows: 4 }).required().description("生成 prompt")
|
|
961
1177
|
});
|
|
962
|
-
var Config =
|
|
963
|
-
|
|
964
|
-
provider:
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1178
|
+
var Config = import_koishi2.Schema.intersect([
|
|
1179
|
+
import_koishi2.Schema.object({
|
|
1180
|
+
provider: import_koishi2.Schema.union([
|
|
1181
|
+
import_koishi2.Schema.const("yunwu").description("云雾 Gemini 服务"),
|
|
1182
|
+
import_koishi2.Schema.const("gptgod").description("GPTGod 服务"),
|
|
1183
|
+
import_koishi2.Schema.const("gemini").description("Google Gemini 原生")
|
|
968
1184
|
]).default("yunwu").description("图像生成供应商"),
|
|
969
|
-
yunwuApiKey:
|
|
970
|
-
yunwuModelId:
|
|
971
|
-
gptgodApiKey:
|
|
972
|
-
gptgodModelId:
|
|
973
|
-
geminiApiKey:
|
|
974
|
-
geminiModelId:
|
|
975
|
-
geminiApiBase:
|
|
976
|
-
modelMappings:
|
|
977
|
-
suffix:
|
|
978
|
-
provider:
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1185
|
+
yunwuApiKey: import_koishi2.Schema.string().description("云雾API密钥").role("secret").required(),
|
|
1186
|
+
yunwuModelId: import_koishi2.Schema.string().default("gemini-2.5-flash-image").description("云雾图像生成模型ID"),
|
|
1187
|
+
gptgodApiKey: import_koishi2.Schema.string().description("GPTGod API 密钥").role("secret").default(""),
|
|
1188
|
+
gptgodModelId: import_koishi2.Schema.string().default("nano-banana").description("GPTGod 模型ID"),
|
|
1189
|
+
geminiApiKey: import_koishi2.Schema.string().description("Gemini API 密钥").role("secret").default(""),
|
|
1190
|
+
geminiModelId: import_koishi2.Schema.string().default("gemini-2.5-flash").description("Gemini 模型ID"),
|
|
1191
|
+
geminiApiBase: import_koishi2.Schema.string().default("https://generativelanguage.googleapis.com").description("Gemini API 基础地址"),
|
|
1192
|
+
modelMappings: import_koishi2.Schema.array(import_koishi2.Schema.object({
|
|
1193
|
+
suffix: import_koishi2.Schema.string().required().description("指令后缀(例如 4K,对应输入 -4K)"),
|
|
1194
|
+
provider: import_koishi2.Schema.union([
|
|
1195
|
+
import_koishi2.Schema.const("yunwu").description("云雾 Gemini 服务"),
|
|
1196
|
+
import_koishi2.Schema.const("gptgod").description("GPTGod 服务"),
|
|
1197
|
+
import_koishi2.Schema.const("gemini").description("Google Gemini 原生")
|
|
982
1198
|
]).description("可选:覆盖供应商"),
|
|
983
|
-
modelId:
|
|
1199
|
+
modelId: import_koishi2.Schema.string().required().description("触发该后缀时使用的模型 ID")
|
|
984
1200
|
})).role("table").default([]).description("根据 -后缀切换模型/供应商"),
|
|
985
|
-
apiTimeout:
|
|
986
|
-
commandTimeout:
|
|
1201
|
+
apiTimeout: import_koishi2.Schema.number().default(120).description("API请求超时时间(秒)"),
|
|
1202
|
+
commandTimeout: import_koishi2.Schema.number().default(180).description("命令执行总超时时间(秒)"),
|
|
987
1203
|
// 默认设置
|
|
988
|
-
defaultNumImages:
|
|
1204
|
+
defaultNumImages: import_koishi2.Schema.number().default(1).min(1).max(4).description("默认生成图片数量"),
|
|
989
1205
|
// 配额设置
|
|
990
|
-
dailyFreeLimit:
|
|
1206
|
+
dailyFreeLimit: import_koishi2.Schema.number().default(5).min(1).max(100).description("每日免费调用次数"),
|
|
991
1207
|
// 限流设置
|
|
992
|
-
rateLimitWindow:
|
|
993
|
-
rateLimitMax:
|
|
1208
|
+
rateLimitWindow: import_koishi2.Schema.number().default(300).min(60).max(3600).description("限流时间窗口(秒)"),
|
|
1209
|
+
rateLimitMax: import_koishi2.Schema.number().default(3).min(1).max(20).description("限流窗口内最大调用次数"),
|
|
994
1210
|
// 管理员设置
|
|
995
|
-
adminUsers:
|
|
1211
|
+
adminUsers: import_koishi2.Schema.array(import_koishi2.Schema.string()).default([]).description("管理员用户ID列表(不受每日使用限制)"),
|
|
996
1212
|
// 日志级别设置
|
|
997
|
-
logLevel:
|
|
998
|
-
|
|
999
|
-
|
|
1213
|
+
logLevel: import_koishi2.Schema.union([
|
|
1214
|
+
import_koishi2.Schema.const("info").description("普通信息"),
|
|
1215
|
+
import_koishi2.Schema.const("debug").description("完整的debug信息")
|
|
1000
1216
|
]).default("info").description("日志输出详细程度"),
|
|
1001
1217
|
// 安全策略拦截设置
|
|
1002
|
-
securityBlockWindow:
|
|
1003
|
-
securityBlockWarningThreshold:
|
|
1218
|
+
securityBlockWindow: import_koishi2.Schema.number().default(600).min(60).max(3600).description("安全策略拦截追踪时间窗口(秒),在此时间窗口内连续触发拦截会被记录"),
|
|
1219
|
+
securityBlockWarningThreshold: import_koishi2.Schema.number().default(3).min(1).max(10).description("安全策略拦截警示阈值,连续触发此次数拦截后将发送警示消息,再次触发将被扣除积分")
|
|
1004
1220
|
}),
|
|
1005
1221
|
// 自定义风格命令配置
|
|
1006
|
-
|
|
1007
|
-
styles:
|
|
1222
|
+
import_koishi2.Schema.object({
|
|
1223
|
+
styles: import_koishi2.Schema.array(StyleItemSchema).role("table").default([
|
|
1008
1224
|
{
|
|
1009
1225
|
commandName: "变手办",
|
|
1010
1226
|
prompt: "将这张照片变成手办模型。在它后面放置一个印有图像主体的盒子,桌子上有一台电脑显示Blender建模过程。在盒子前面添加一个圆形塑料底座,角色手办站在上面。如果可能的话,将场景设置在室内"
|
|
@@ -1015,128 +1231,33 @@ var Config = import_koishi.Schema.intersect([
|
|
|
1015
1231
|
}
|
|
1016
1232
|
]).description("自定义风格命令配置")
|
|
1017
1233
|
}),
|
|
1018
|
-
|
|
1019
|
-
styleGroups:
|
|
1020
|
-
prompts:
|
|
1234
|
+
import_koishi2.Schema.object({
|
|
1235
|
+
styleGroups: import_koishi2.Schema.dict(import_koishi2.Schema.object({
|
|
1236
|
+
prompts: import_koishi2.Schema.array(StyleItemSchema).role("table").default([]).description("属于该类型的 prompt 列表")
|
|
1021
1237
|
})).role("table").default({}).description("按类型管理的 prompt 组,键名即为分组名称")
|
|
1022
1238
|
})
|
|
1023
1239
|
]);
|
|
1024
1240
|
function apply(ctx, config) {
|
|
1025
1241
|
const logger = ctx.logger("aka-ai-generator");
|
|
1026
|
-
const
|
|
1027
|
-
const rateLimitMap = /* @__PURE__ */ new Map();
|
|
1028
|
-
const securityBlockMap = /* @__PURE__ */ new Map();
|
|
1029
|
-
const securityWarningMap = /* @__PURE__ */ new Map();
|
|
1030
|
-
const providerCache = /* @__PURE__ */ new Map();
|
|
1242
|
+
const userManager = new UserManager(ctx.baseDir, logger);
|
|
1031
1243
|
function getProviderInstance(providerType, modelId) {
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
ctx
|
|
1047
|
-
}));
|
|
1048
|
-
}
|
|
1049
|
-
return providerCache.get(cacheKey);
|
|
1244
|
+
return createImageProvider({
|
|
1245
|
+
provider: providerType,
|
|
1246
|
+
yunwuApiKey: config.yunwuApiKey,
|
|
1247
|
+
yunwuModelId: providerType === "yunwu" ? modelId || config.yunwuModelId : config.yunwuModelId,
|
|
1248
|
+
gptgodApiKey: config.gptgodApiKey,
|
|
1249
|
+
gptgodModelId: providerType === "gptgod" ? modelId || config.gptgodModelId : config.gptgodModelId,
|
|
1250
|
+
geminiApiKey: config.geminiApiKey,
|
|
1251
|
+
geminiModelId: providerType === "gemini" ? modelId || config.geminiModelId : config.geminiModelId,
|
|
1252
|
+
geminiApiBase: config.geminiApiBase,
|
|
1253
|
+
apiTimeout: config.apiTimeout,
|
|
1254
|
+
logLevel: config.logLevel,
|
|
1255
|
+
logger,
|
|
1256
|
+
ctx
|
|
1257
|
+
});
|
|
1050
1258
|
}
|
|
1051
1259
|
__name(getProviderInstance, "getProviderInstance");
|
|
1052
|
-
getProviderInstance(config.provider);
|
|
1053
1260
|
const modelMappingIndex = buildModelMappingIndex(config.modelMappings);
|
|
1054
|
-
function normalizeSuffix(value) {
|
|
1055
|
-
return value?.replace(/^\-+/, "").trim().toLowerCase();
|
|
1056
|
-
}
|
|
1057
|
-
__name(normalizeSuffix, "normalizeSuffix");
|
|
1058
|
-
function buildModelMappingIndex(mappings) {
|
|
1059
|
-
const map = /* @__PURE__ */ new Map();
|
|
1060
|
-
if (!Array.isArray(mappings)) return map;
|
|
1061
|
-
for (const mapping of mappings) {
|
|
1062
|
-
const key = normalizeSuffix(mapping?.suffix);
|
|
1063
|
-
if (!key || !mapping?.modelId) continue;
|
|
1064
|
-
map.set(key, mapping);
|
|
1065
|
-
}
|
|
1066
|
-
return map;
|
|
1067
|
-
}
|
|
1068
|
-
__name(buildModelMappingIndex, "buildModelMappingIndex");
|
|
1069
|
-
function parseStyleCommandModifiers(argv, imgParam) {
|
|
1070
|
-
const session = argv.session;
|
|
1071
|
-
let rawText = "";
|
|
1072
|
-
if (session?.content) {
|
|
1073
|
-
const elements = import_koishi.h.parse(session.content);
|
|
1074
|
-
rawText = import_koishi.h.select(elements, "text").map((e) => e.attrs.content).join(" ");
|
|
1075
|
-
}
|
|
1076
|
-
const argsList = rawText ? rawText.split(/\s+/).filter(Boolean) : [...argv.args || []].map((arg) => typeof arg === "string" ? arg.trim() : "").filter(Boolean);
|
|
1077
|
-
if (!rawText) {
|
|
1078
|
-
const restStr = typeof argv.rest === "string" ? argv.rest.trim() : "";
|
|
1079
|
-
if (restStr) {
|
|
1080
|
-
const restParts = restStr.split(/\s+/).filter(Boolean);
|
|
1081
|
-
argsList.push(...restParts);
|
|
1082
|
-
}
|
|
1083
|
-
if (imgParam && typeof imgParam === "string" && !imgParam.startsWith("http") && !imgParam.startsWith("data:")) {
|
|
1084
|
-
const imgParts = imgParam.split(/\s+/).filter(Boolean);
|
|
1085
|
-
argsList.push(...imgParts);
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
if (!argsList.length) return {};
|
|
1089
|
-
const modifiers = { customAdditions: [] };
|
|
1090
|
-
const flagCandidates = [];
|
|
1091
|
-
let index = 0;
|
|
1092
|
-
while (index < argsList.length) {
|
|
1093
|
-
const token = argsList[index];
|
|
1094
|
-
if (!token) {
|
|
1095
|
-
index++;
|
|
1096
|
-
continue;
|
|
1097
|
-
}
|
|
1098
|
-
const lower = token.toLowerCase();
|
|
1099
|
-
if (lower.startsWith("-prompt:")) {
|
|
1100
|
-
const promptHead = token.substring(token.indexOf(":") + 1);
|
|
1101
|
-
const restTokens = argsList.slice(index + 1);
|
|
1102
|
-
modifiers.customPromptSuffix = [promptHead, ...restTokens].join(" ").trim();
|
|
1103
|
-
break;
|
|
1104
|
-
}
|
|
1105
|
-
if (lower === "-add") {
|
|
1106
|
-
index++;
|
|
1107
|
-
const additionTokens = [];
|
|
1108
|
-
while (index < argsList.length) {
|
|
1109
|
-
const nextToken = argsList[index];
|
|
1110
|
-
if (nextToken.startsWith("-")) {
|
|
1111
|
-
const key = normalizeSuffix(nextToken);
|
|
1112
|
-
if (key && modelMappingIndex.has(key)) break;
|
|
1113
|
-
if (nextToken.toLowerCase().startsWith("-prompt:")) break;
|
|
1114
|
-
if (nextToken.toLowerCase() === "-add") break;
|
|
1115
|
-
}
|
|
1116
|
-
additionTokens.push(nextToken);
|
|
1117
|
-
index++;
|
|
1118
|
-
}
|
|
1119
|
-
if (additionTokens.length) {
|
|
1120
|
-
modifiers.customAdditions.push(additionTokens.join(" "));
|
|
1121
|
-
}
|
|
1122
|
-
continue;
|
|
1123
|
-
}
|
|
1124
|
-
flagCandidates.push(token);
|
|
1125
|
-
index++;
|
|
1126
|
-
}
|
|
1127
|
-
for (const arg of flagCandidates) {
|
|
1128
|
-
if (!arg.startsWith("-")) continue;
|
|
1129
|
-
const key = normalizeSuffix(arg);
|
|
1130
|
-
if (!key) continue;
|
|
1131
|
-
const mapping = modelMappingIndex.get(key);
|
|
1132
|
-
if (mapping) {
|
|
1133
|
-
modifiers.modelMapping = mapping;
|
|
1134
|
-
break;
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
return modifiers;
|
|
1138
|
-
}
|
|
1139
|
-
__name(parseStyleCommandModifiers, "parseStyleCommandModifiers");
|
|
1140
1261
|
const styleDefinitions = collectStyleDefinitions();
|
|
1141
1262
|
function collectStyleDefinitions() {
|
|
1142
1263
|
const unique = /* @__PURE__ */ new Map();
|
|
@@ -1191,230 +1312,18 @@ function apply(ctx, config) {
|
|
|
1191
1312
|
{ name: COMMANDS.RECHARGE_HISTORY, description: "查看充值历史记录(仅管理员)" }
|
|
1192
1313
|
]
|
|
1193
1314
|
};
|
|
1194
|
-
const dataDir = "./data/aka-ai-generator";
|
|
1195
|
-
const dataFile = (0, import_path.join)(dataDir, "users_data.json");
|
|
1196
|
-
const backupFile = (0, import_path.join)(dataDir, "users_data.json.backup");
|
|
1197
|
-
const rechargeHistoryFile = (0, import_path.join)(dataDir, "recharge_history.json");
|
|
1198
|
-
if (!(0, import_fs.existsSync)(dataDir)) {
|
|
1199
|
-
(0, import_fs.mkdirSync)(dataDir, { recursive: true });
|
|
1200
|
-
}
|
|
1201
|
-
function isAdmin(userId) {
|
|
1202
|
-
return config.adminUsers && config.adminUsers.includes(userId);
|
|
1203
|
-
}
|
|
1204
|
-
__name(isAdmin, "isAdmin");
|
|
1205
|
-
function checkRateLimit(userId) {
|
|
1206
|
-
const now = Date.now();
|
|
1207
|
-
const userTimestamps = rateLimitMap.get(userId) || [];
|
|
1208
|
-
const windowStart = now - config.rateLimitWindow * 1e3;
|
|
1209
|
-
const validTimestamps = userTimestamps.filter((timestamp) => timestamp > windowStart);
|
|
1210
|
-
if (validTimestamps.length >= config.rateLimitMax) {
|
|
1211
|
-
return {
|
|
1212
|
-
allowed: false,
|
|
1213
|
-
message: `操作过于频繁,请${Math.ceil((validTimestamps[0] + config.rateLimitWindow * 1e3 - now) / 1e3)}秒后再试`
|
|
1214
|
-
};
|
|
1215
|
-
}
|
|
1216
|
-
return { allowed: true };
|
|
1217
|
-
}
|
|
1218
|
-
__name(checkRateLimit, "checkRateLimit");
|
|
1219
|
-
function updateRateLimit(userId) {
|
|
1220
|
-
const now = Date.now();
|
|
1221
|
-
const userTimestamps = rateLimitMap.get(userId) || [];
|
|
1222
|
-
userTimestamps.push(now);
|
|
1223
|
-
rateLimitMap.set(userId, userTimestamps);
|
|
1224
|
-
}
|
|
1225
|
-
__name(updateRateLimit, "updateRateLimit");
|
|
1226
|
-
async function checkDailyLimit(userId, numImages = 1, updateRateLimitImmediately = true) {
|
|
1227
|
-
if (isAdmin(userId)) {
|
|
1228
|
-
return { allowed: true, isAdmin: true };
|
|
1229
|
-
}
|
|
1230
|
-
const rateLimitCheck = checkRateLimit(userId);
|
|
1231
|
-
if (!rateLimitCheck.allowed) {
|
|
1232
|
-
return { ...rateLimitCheck, isAdmin: false };
|
|
1233
|
-
}
|
|
1234
|
-
if (updateRateLimitImmediately) {
|
|
1235
|
-
updateRateLimit(userId);
|
|
1236
|
-
}
|
|
1237
|
-
const usersData = await loadUsersData();
|
|
1238
|
-
const userData = usersData[userId];
|
|
1239
|
-
if (!userData) {
|
|
1240
|
-
if (numImages > config.dailyFreeLimit) {
|
|
1241
|
-
return {
|
|
1242
|
-
allowed: false,
|
|
1243
|
-
message: `生成 ${numImages} 张图片需要 ${numImages} 次可用次数,但您的可用次数不足(今日免费:${config.dailyFreeLimit}次,充值:0次)`,
|
|
1244
|
-
isAdmin: false
|
|
1245
|
-
};
|
|
1246
|
-
}
|
|
1247
|
-
return { allowed: true, isAdmin: false };
|
|
1248
|
-
}
|
|
1249
|
-
const today = (/* @__PURE__ */ new Date()).toDateString();
|
|
1250
|
-
const lastReset = new Date(userData.lastDailyReset || userData.createdAt).toDateString();
|
|
1251
|
-
let dailyCount = userData.dailyUsageCount;
|
|
1252
|
-
if (today !== lastReset) {
|
|
1253
|
-
dailyCount = 0;
|
|
1254
|
-
userData.dailyUsageCount = 0;
|
|
1255
|
-
userData.lastDailyReset = (/* @__PURE__ */ new Date()).toISOString();
|
|
1256
|
-
}
|
|
1257
|
-
const remainingToday = Math.max(0, config.dailyFreeLimit - dailyCount);
|
|
1258
|
-
const totalAvailable = remainingToday + userData.remainingPurchasedCount;
|
|
1259
|
-
if (totalAvailable < numImages) {
|
|
1260
|
-
return {
|
|
1261
|
-
allowed: false,
|
|
1262
|
-
message: `生成 ${numImages} 张图片需要 ${numImages} 次可用次数,但您的可用次数不足(今日免费剩余:${remainingToday}次,充值剩余:${userData.remainingPurchasedCount}次,共${totalAvailable}次)`,
|
|
1263
|
-
isAdmin: false
|
|
1264
|
-
};
|
|
1265
|
-
}
|
|
1266
|
-
return { allowed: true, isAdmin: false };
|
|
1267
|
-
}
|
|
1268
|
-
__name(checkDailyLimit, "checkDailyLimit");
|
|
1269
1315
|
async function getPromptInput(session, message) {
|
|
1270
1316
|
await session.send(message);
|
|
1271
1317
|
const input = await session.prompt(3e4);
|
|
1272
1318
|
return input || null;
|
|
1273
1319
|
}
|
|
1274
1320
|
__name(getPromptInput, "getPromptInput");
|
|
1275
|
-
async function loadUsersData() {
|
|
1276
|
-
try {
|
|
1277
|
-
if ((0, import_fs.existsSync)(dataFile)) {
|
|
1278
|
-
const data = await import_fs.promises.readFile(dataFile, "utf-8");
|
|
1279
|
-
return JSON.parse(data);
|
|
1280
|
-
}
|
|
1281
|
-
} catch (error) {
|
|
1282
|
-
logger.error("读取用户数据失败", error);
|
|
1283
|
-
if ((0, import_fs.existsSync)(backupFile)) {
|
|
1284
|
-
try {
|
|
1285
|
-
const backupData = await import_fs.promises.readFile(backupFile, "utf-8");
|
|
1286
|
-
logger.warn("从备份文件恢复用户数据");
|
|
1287
|
-
return JSON.parse(backupData);
|
|
1288
|
-
} catch (backupError) {
|
|
1289
|
-
logger.error("备份文件也损坏,使用空数据", backupError);
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
return {};
|
|
1294
|
-
}
|
|
1295
|
-
__name(loadUsersData, "loadUsersData");
|
|
1296
|
-
async function saveUsersData(data) {
|
|
1297
|
-
try {
|
|
1298
|
-
if ((0, import_fs.existsSync)(dataFile)) {
|
|
1299
|
-
await import_fs.promises.copyFile(dataFile, backupFile);
|
|
1300
|
-
}
|
|
1301
|
-
await import_fs.promises.writeFile(dataFile, JSON.stringify(data, null, 2), "utf-8");
|
|
1302
|
-
} catch (error) {
|
|
1303
|
-
logger.error("保存用户数据失败", error);
|
|
1304
|
-
throw error;
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
__name(saveUsersData, "saveUsersData");
|
|
1308
|
-
async function loadRechargeHistory() {
|
|
1309
|
-
try {
|
|
1310
|
-
if ((0, import_fs.existsSync)(rechargeHistoryFile)) {
|
|
1311
|
-
const data = await import_fs.promises.readFile(rechargeHistoryFile, "utf-8");
|
|
1312
|
-
return JSON.parse(data);
|
|
1313
|
-
}
|
|
1314
|
-
} catch (error) {
|
|
1315
|
-
logger.error("读取充值历史失败", error);
|
|
1316
|
-
}
|
|
1317
|
-
return {
|
|
1318
|
-
version: "1.0.0",
|
|
1319
|
-
lastUpdate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1320
|
-
records: []
|
|
1321
|
-
};
|
|
1322
|
-
}
|
|
1323
|
-
__name(loadRechargeHistory, "loadRechargeHistory");
|
|
1324
|
-
async function saveRechargeHistory(history) {
|
|
1325
|
-
try {
|
|
1326
|
-
history.lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
1327
|
-
await import_fs.promises.writeFile(rechargeHistoryFile, JSON.stringify(history, null, 2), "utf-8");
|
|
1328
|
-
} catch (error) {
|
|
1329
|
-
logger.error("保存充值历史失败", error);
|
|
1330
|
-
throw error;
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
__name(saveRechargeHistory, "saveRechargeHistory");
|
|
1334
|
-
async function getUserData(userId, userName) {
|
|
1335
|
-
const usersData = await loadUsersData();
|
|
1336
|
-
if (!usersData[userId]) {
|
|
1337
|
-
usersData[userId] = {
|
|
1338
|
-
userId,
|
|
1339
|
-
userName,
|
|
1340
|
-
totalUsageCount: 0,
|
|
1341
|
-
dailyUsageCount: 0,
|
|
1342
|
-
lastDailyReset: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1343
|
-
purchasedCount: 0,
|
|
1344
|
-
remainingPurchasedCount: 0,
|
|
1345
|
-
donationCount: 0,
|
|
1346
|
-
donationAmount: 0,
|
|
1347
|
-
lastUsed: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1348
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1349
|
-
};
|
|
1350
|
-
await saveUsersData(usersData);
|
|
1351
|
-
logger.info("创建新用户数据", { userId, userName });
|
|
1352
|
-
}
|
|
1353
|
-
return usersData[userId];
|
|
1354
|
-
}
|
|
1355
|
-
__name(getUserData, "getUserData");
|
|
1356
|
-
async function updateUserData(userId, userName, commandName, numImages = 1) {
|
|
1357
|
-
const usersData = await loadUsersData();
|
|
1358
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1359
|
-
const today = (/* @__PURE__ */ new Date()).toDateString();
|
|
1360
|
-
if (!usersData[userId]) {
|
|
1361
|
-
usersData[userId] = {
|
|
1362
|
-
userId,
|
|
1363
|
-
userName: userId,
|
|
1364
|
-
totalUsageCount: numImages,
|
|
1365
|
-
dailyUsageCount: numImages,
|
|
1366
|
-
lastDailyReset: now,
|
|
1367
|
-
purchasedCount: 0,
|
|
1368
|
-
remainingPurchasedCount: 0,
|
|
1369
|
-
donationCount: 0,
|
|
1370
|
-
donationAmount: 0,
|
|
1371
|
-
lastUsed: now,
|
|
1372
|
-
createdAt: now
|
|
1373
|
-
};
|
|
1374
|
-
await saveUsersData(usersData);
|
|
1375
|
-
return { userData: usersData[userId], consumptionType: "free", freeUsed: numImages, purchasedUsed: 0 };
|
|
1376
|
-
}
|
|
1377
|
-
usersData[userId].totalUsageCount += numImages;
|
|
1378
|
-
usersData[userId].lastUsed = now;
|
|
1379
|
-
const lastReset = new Date(usersData[userId].lastDailyReset || usersData[userId].createdAt).toDateString();
|
|
1380
|
-
if (today !== lastReset) {
|
|
1381
|
-
usersData[userId].dailyUsageCount = 0;
|
|
1382
|
-
usersData[userId].lastDailyReset = now;
|
|
1383
|
-
}
|
|
1384
|
-
let remainingToConsume = numImages;
|
|
1385
|
-
let freeUsed = 0;
|
|
1386
|
-
let purchasedUsed = 0;
|
|
1387
|
-
const availableFree = Math.max(0, config.dailyFreeLimit - usersData[userId].dailyUsageCount);
|
|
1388
|
-
if (availableFree > 0) {
|
|
1389
|
-
const freeToUse = Math.min(availableFree, remainingToConsume);
|
|
1390
|
-
usersData[userId].dailyUsageCount += freeToUse;
|
|
1391
|
-
freeUsed = freeToUse;
|
|
1392
|
-
remainingToConsume -= freeToUse;
|
|
1393
|
-
}
|
|
1394
|
-
if (remainingToConsume > 0) {
|
|
1395
|
-
const purchasedToUse = Math.min(usersData[userId].remainingPurchasedCount, remainingToConsume);
|
|
1396
|
-
usersData[userId].remainingPurchasedCount -= purchasedToUse;
|
|
1397
|
-
purchasedUsed = purchasedToUse;
|
|
1398
|
-
remainingToConsume -= purchasedToUse;
|
|
1399
|
-
}
|
|
1400
|
-
await saveUsersData(usersData);
|
|
1401
|
-
let consumptionType;
|
|
1402
|
-
if (freeUsed > 0 && purchasedUsed > 0) {
|
|
1403
|
-
consumptionType = "mixed";
|
|
1404
|
-
} else if (freeUsed > 0) {
|
|
1405
|
-
consumptionType = "free";
|
|
1406
|
-
} else {
|
|
1407
|
-
consumptionType = "purchased";
|
|
1408
|
-
}
|
|
1409
|
-
return { userData: usersData[userId], consumptionType, freeUsed, purchasedUsed };
|
|
1410
|
-
}
|
|
1411
|
-
__name(updateUserData, "updateUserData");
|
|
1412
1321
|
async function recordUserUsage(session, commandName, numImages = 1) {
|
|
1413
1322
|
const userId = session.userId;
|
|
1414
1323
|
const userName = session.username || session.userId || "未知用户";
|
|
1415
1324
|
if (!userId) return;
|
|
1416
|
-
const { userData, consumptionType, freeUsed, purchasedUsed } = await
|
|
1417
|
-
if (isAdmin(userId)) {
|
|
1325
|
+
const { userData, consumptionType, freeUsed, purchasedUsed } = await userManager.consumeQuota(userId, userName, commandName, numImages, config);
|
|
1326
|
+
if (userManager.isAdmin(userId, config)) {
|
|
1418
1327
|
await session.send(`📊 使用统计 [管理员]
|
|
1419
1328
|
用户:${userData.userName}
|
|
1420
1329
|
总调用次数:${userData.totalUsageCount}次
|
|
@@ -1448,38 +1357,27 @@ function apply(ctx, config) {
|
|
|
1448
1357
|
totalUsageCount: userData.totalUsageCount,
|
|
1449
1358
|
dailyUsageCount: userData.dailyUsageCount,
|
|
1450
1359
|
remainingPurchasedCount: userData.remainingPurchasedCount,
|
|
1451
|
-
isAdmin: isAdmin(userId)
|
|
1360
|
+
isAdmin: userManager.isAdmin(userId, config)
|
|
1452
1361
|
});
|
|
1453
1362
|
}
|
|
1454
1363
|
__name(recordUserUsage, "recordUserUsage");
|
|
1455
1364
|
async function recordSecurityBlock(session, numImages = 1) {
|
|
1456
1365
|
const userId = session.userId;
|
|
1457
1366
|
if (!userId) return;
|
|
1458
|
-
|
|
1459
|
-
return;
|
|
1460
|
-
}
|
|
1461
|
-
const now = Date.now();
|
|
1462
|
-
const windowMs = config.securityBlockWindow * 1e3;
|
|
1463
|
-
const windowStart = now - windowMs;
|
|
1464
|
-
let blockTimestamps = securityBlockMap.get(userId) || [];
|
|
1465
|
-
blockTimestamps = blockTimestamps.filter((timestamp) => timestamp > windowStart);
|
|
1466
|
-
blockTimestamps.push(now);
|
|
1467
|
-
securityBlockMap.set(userId, blockTimestamps);
|
|
1468
|
-
const blockCount = blockTimestamps.length;
|
|
1469
|
-
const hasWarning = securityWarningMap.get(userId) || false;
|
|
1367
|
+
const { shouldWarn, shouldDeduct, blockCount } = await userManager.recordSecurityBlock(userId, config);
|
|
1470
1368
|
logger.info("安全策略拦截记录", {
|
|
1471
1369
|
userId,
|
|
1472
1370
|
blockCount,
|
|
1473
1371
|
threshold: config.securityBlockWarningThreshold,
|
|
1474
|
-
|
|
1372
|
+
shouldWarn,
|
|
1373
|
+
shouldDeduct,
|
|
1475
1374
|
numImages
|
|
1476
1375
|
});
|
|
1477
|
-
if (
|
|
1478
|
-
securityWarningMap.set(userId, true);
|
|
1376
|
+
if (shouldWarn) {
|
|
1479
1377
|
await session.send(`⚠️ 安全策略警示
|
|
1480
1378
|
您已连续${config.securityBlockWarningThreshold}次触发安全策略拦截,再次发送被拦截内容将被扣除积分`);
|
|
1481
1379
|
logger.warn("用户收到安全策略警示", { userId, blockCount, threshold: config.securityBlockWarningThreshold });
|
|
1482
|
-
} else if (
|
|
1380
|
+
} else if (shouldDeduct) {
|
|
1483
1381
|
const commandName = "安全策略拦截";
|
|
1484
1382
|
await recordUserUsage(session, commandName, numImages);
|
|
1485
1383
|
logger.warn("用户因安全策略拦截被扣除积分", { userId, numImages });
|
|
@@ -1496,12 +1394,12 @@ function apply(ctx, config) {
|
|
|
1496
1394
|
await session.send("请输入画面描述");
|
|
1497
1395
|
const msg = await session.prompt(3e4);
|
|
1498
1396
|
if (!msg) return { error: "等待超时" };
|
|
1499
|
-
const elements =
|
|
1500
|
-
const images =
|
|
1397
|
+
const elements = import_koishi2.h.parse(msg);
|
|
1398
|
+
const images = import_koishi2.h.select(elements, "img");
|
|
1501
1399
|
if (images.length > 0) {
|
|
1502
1400
|
return { error: "检测到图片,本功能仅支持文字输入" };
|
|
1503
1401
|
}
|
|
1504
|
-
const text =
|
|
1402
|
+
const text = import_koishi2.h.select(elements, "text").map((e) => e.attrs.content).join(" ").trim();
|
|
1505
1403
|
if (!text) {
|
|
1506
1404
|
return { error: "未检测到描述,操作已取消" };
|
|
1507
1405
|
}
|
|
@@ -1517,7 +1415,7 @@ function apply(ctx, config) {
|
|
|
1517
1415
|
}
|
|
1518
1416
|
}
|
|
1519
1417
|
if (session.quote?.elements) {
|
|
1520
|
-
const quoteImages =
|
|
1418
|
+
const quoteImages = import_koishi2.h.select(session.quote.elements, "img");
|
|
1521
1419
|
for (const img of quoteImages) {
|
|
1522
1420
|
if (img.attrs.src) collectedImages.push(img.attrs.src);
|
|
1523
1421
|
}
|
|
@@ -1536,9 +1434,9 @@ function apply(ctx, config) {
|
|
|
1536
1434
|
while (true) {
|
|
1537
1435
|
const msg = await session.prompt(mode === "multiple" ? 6e4 : 3e4);
|
|
1538
1436
|
if (!msg) return { error: "等待超时" };
|
|
1539
|
-
const elements =
|
|
1540
|
-
const images =
|
|
1541
|
-
const textElements =
|
|
1437
|
+
const elements = import_koishi2.h.parse(msg);
|
|
1438
|
+
const images = import_koishi2.h.select(elements, "img");
|
|
1439
|
+
const textElements = import_koishi2.h.select(elements, "text");
|
|
1542
1440
|
const text = textElements.map((el) => el.attrs.content).join(" ").trim();
|
|
1543
1441
|
if (images.length > 0) {
|
|
1544
1442
|
for (const img of images) {
|
|
@@ -1584,14 +1482,18 @@ function apply(ctx, config) {
|
|
|
1584
1482
|
}
|
|
1585
1483
|
__name(requestProviderImages, "requestProviderImages");
|
|
1586
1484
|
async function processImageWithTimeout(session, img, prompt, styleName, requestContext, displayInfo, mode = "single") {
|
|
1485
|
+
const userId = session.userId;
|
|
1486
|
+
let isTimeout = false;
|
|
1587
1487
|
return Promise.race([
|
|
1588
|
-
processImage(session, img, prompt, styleName, requestContext, displayInfo, mode),
|
|
1488
|
+
processImage(session, img, prompt, styleName, requestContext, displayInfo, mode, () => isTimeout),
|
|
1589
1489
|
new Promise(
|
|
1590
|
-
(_, reject) => setTimeout(() =>
|
|
1490
|
+
(_, reject) => setTimeout(() => {
|
|
1491
|
+
isTimeout = true;
|
|
1492
|
+
reject(new Error("命令执行超时"));
|
|
1493
|
+
}, config.commandTimeout * 1e3)
|
|
1591
1494
|
)
|
|
1592
1495
|
]).catch(async (error) => {
|
|
1593
|
-
|
|
1594
|
-
if (userId) activeTasks.delete(userId);
|
|
1496
|
+
if (userId) userManager.endTask(userId);
|
|
1595
1497
|
const sanitizedError = sanitizeError(error);
|
|
1596
1498
|
logger.error("图像处理超时或失败", { userId, error: sanitizedError });
|
|
1597
1499
|
if (error?.message !== "命令执行超时") {
|
|
@@ -1607,99 +1509,88 @@ function apply(ctx, config) {
|
|
|
1607
1509
|
});
|
|
1608
1510
|
}
|
|
1609
1511
|
__name(processImageWithTimeout, "processImageWithTimeout");
|
|
1610
|
-
async function processImage(session, img, prompt, styleName, requestContext, displayInfo, mode = "single") {
|
|
1512
|
+
async function processImage(session, img, prompt, styleName, requestContext, displayInfo, mode = "single", checkTimeout) {
|
|
1611
1513
|
const userId = session.userId;
|
|
1612
|
-
if (
|
|
1514
|
+
if (!userManager.startTask(userId)) {
|
|
1613
1515
|
return "您有一个图像处理任务正在进行中,请等待完成";
|
|
1614
1516
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
finalPrompt
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1517
|
+
try {
|
|
1518
|
+
const imageCount = requestContext?.numImages || config.defaultNumImages;
|
|
1519
|
+
if (imageCount < 1 || imageCount > 4) {
|
|
1520
|
+
return "生成数量必须在 1-4 之间";
|
|
1521
|
+
}
|
|
1522
|
+
const inputResult = await getInputData(session, img, mode);
|
|
1523
|
+
if ("error" in inputResult) {
|
|
1524
|
+
return inputResult.error;
|
|
1525
|
+
}
|
|
1526
|
+
if (checkTimeout && checkTimeout()) throw new Error("命令执行超时");
|
|
1527
|
+
const { images: imageUrls, text: extraText } = inputResult;
|
|
1528
|
+
let finalPrompt = prompt;
|
|
1529
|
+
if (extraText) {
|
|
1530
|
+
finalPrompt += " " + extraText;
|
|
1531
|
+
}
|
|
1532
|
+
finalPrompt = finalPrompt.trim();
|
|
1533
|
+
if (!finalPrompt) {
|
|
1534
|
+
await session.send("请发送画面描述");
|
|
1535
|
+
const promptMsg = await session.prompt(3e4);
|
|
1536
|
+
if (!promptMsg) {
|
|
1537
|
+
return "未检测到描述,操作已取消";
|
|
1538
|
+
}
|
|
1539
|
+
const elements = import_koishi2.h.parse(promptMsg);
|
|
1540
|
+
const images2 = import_koishi2.h.select(elements, "img");
|
|
1541
|
+
if (images2.length > 0) {
|
|
1542
|
+
return "检测到图片,本功能仅支持文字输入";
|
|
1543
|
+
}
|
|
1544
|
+
const text = import_koishi2.h.select(elements, "text").map((e) => e.attrs.content).join(" ").trim();
|
|
1545
|
+
if (text) {
|
|
1546
|
+
finalPrompt = text;
|
|
1547
|
+
} else {
|
|
1548
|
+
return "未检测到有效文字描述,操作已取消";
|
|
1549
|
+
}
|
|
1634
1550
|
}
|
|
1635
|
-
|
|
1636
|
-
const
|
|
1637
|
-
|
|
1638
|
-
|
|
1551
|
+
if (checkTimeout && checkTimeout()) throw new Error("命令执行超时");
|
|
1552
|
+
const providerType = requestContext?.provider || config.provider;
|
|
1553
|
+
const providerModelId = requestContext?.modelId || (providerType === "yunwu" ? config.yunwuModelId : config.gptgodModelId);
|
|
1554
|
+
logger.info("开始图像处理", {
|
|
1555
|
+
userId,
|
|
1556
|
+
imageUrls,
|
|
1557
|
+
styleName,
|
|
1558
|
+
prompt: finalPrompt,
|
|
1559
|
+
numImages: imageCount,
|
|
1560
|
+
provider: providerType,
|
|
1561
|
+
modelId: providerModelId
|
|
1562
|
+
});
|
|
1563
|
+
let statusMessage = `开始处理图片(${styleName})`;
|
|
1564
|
+
const infoParts = [];
|
|
1565
|
+
if (displayInfo?.customAdditions && displayInfo.customAdditions.length > 0) {
|
|
1566
|
+
infoParts.push(`自定义内容:${displayInfo.customAdditions.join(";")}`);
|
|
1639
1567
|
}
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
} else {
|
|
1644
|
-
return "未检测到有效文字描述,操作已取消";
|
|
1568
|
+
if (displayInfo?.modelId) {
|
|
1569
|
+
const modelDesc = displayInfo.modelDescription || displayInfo.modelId;
|
|
1570
|
+
infoParts.push(`使用模型:${modelDesc}`);
|
|
1645
1571
|
}
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
const providerModelId = requestContext?.modelId || (providerType === "yunwu" ? config.yunwuModelId : config.gptgodModelId);
|
|
1649
|
-
logger.info("开始图像处理", {
|
|
1650
|
-
userId,
|
|
1651
|
-
imageUrls,
|
|
1652
|
-
styleName,
|
|
1653
|
-
prompt: finalPrompt,
|
|
1654
|
-
numImages: imageCount,
|
|
1655
|
-
provider: providerType,
|
|
1656
|
-
modelId: providerModelId
|
|
1657
|
-
});
|
|
1658
|
-
let statusMessage = `开始处理图片(${styleName})`;
|
|
1659
|
-
const infoParts = [];
|
|
1660
|
-
if (displayInfo?.customAdditions && displayInfo.customAdditions.length > 0) {
|
|
1661
|
-
infoParts.push(`自定义内容:${displayInfo.customAdditions.join(";")}`);
|
|
1662
|
-
}
|
|
1663
|
-
if (displayInfo?.modelId) {
|
|
1664
|
-
const modelDesc = displayInfo.modelDescription || displayInfo.modelId;
|
|
1665
|
-
infoParts.push(`使用模型:${modelDesc}`);
|
|
1666
|
-
}
|
|
1667
|
-
if (infoParts.length > 0) {
|
|
1668
|
-
statusMessage += `
|
|
1572
|
+
if (infoParts.length > 0) {
|
|
1573
|
+
statusMessage += `
|
|
1669
1574
|
${infoParts.join("\n")}`;
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
try {
|
|
1674
|
-
activeTasks.set(userId, "processing");
|
|
1575
|
+
}
|
|
1576
|
+
statusMessage += "...";
|
|
1577
|
+
await session.send(statusMessage);
|
|
1675
1578
|
const images = await requestProviderImages(finalPrompt, imageUrls, imageCount, requestContext);
|
|
1579
|
+
if (checkTimeout && checkTimeout()) throw new Error("命令执行超时");
|
|
1676
1580
|
if (images.length === 0) {
|
|
1677
|
-
activeTasks.delete(userId);
|
|
1678
1581
|
return "图像处理失败:未能生成图片";
|
|
1679
1582
|
}
|
|
1680
1583
|
await session.send("图像处理完成!");
|
|
1681
1584
|
for (let i = 0; i < images.length; i++) {
|
|
1682
|
-
|
|
1585
|
+
if (checkTimeout && checkTimeout()) break;
|
|
1586
|
+
await session.send(import_koishi2.h.image(images[i]));
|
|
1683
1587
|
if (images.length > 1 && i < images.length - 1) {
|
|
1684
1588
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1685
1589
|
}
|
|
1686
1590
|
}
|
|
1687
1591
|
await recordUserUsage(session, styleName, images.length);
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
activeTasks.delete(userId);
|
|
1691
|
-
const sanitizedError = sanitizeError(error);
|
|
1692
|
-
logger.error("图像处理失败", { userId, error: sanitizedError });
|
|
1693
|
-
const errorMessage = error?.message || "";
|
|
1694
|
-
const isSecurityBlock = errorMessage.includes("内容被安全策略拦截") || errorMessage.includes("内容被安全策略阻止") || errorMessage.includes("内容被阻止") || errorMessage.includes("被阻止") || errorMessage.includes("SAFETY") || errorMessage.includes("RECITATION");
|
|
1695
|
-
if (isSecurityBlock) {
|
|
1696
|
-
await recordSecurityBlock(session, imageCount);
|
|
1697
|
-
}
|
|
1698
|
-
if (error?.message) {
|
|
1699
|
-
const safeMessage = sanitizeString(error.message);
|
|
1700
|
-
return `图像处理失败:${safeMessage}`;
|
|
1701
|
-
}
|
|
1702
|
-
return "图像处理失败,请稍后重试";
|
|
1592
|
+
} finally {
|
|
1593
|
+
userManager.endTask(userId);
|
|
1703
1594
|
}
|
|
1704
1595
|
}
|
|
1705
1596
|
__name(processImage, "processImage");
|
|
@@ -1709,7 +1600,7 @@ ${infoParts.join("\n")}`;
|
|
|
1709
1600
|
ctx.command(`${style.commandName} [img:text]`, "图像风格转换").option("num", "-n <num:number> 生成图片数量 (1-4)").option("multiple", "-m 允许多图输入").action(async (argv, img) => {
|
|
1710
1601
|
const { session, options } = argv;
|
|
1711
1602
|
if (!session?.userId) return "会话无效";
|
|
1712
|
-
const modifiers = parseStyleCommandModifiers(argv, img);
|
|
1603
|
+
const modifiers = parseStyleCommandModifiers(argv, img, modelMappingIndex);
|
|
1713
1604
|
let userPromptParts = [];
|
|
1714
1605
|
if (modifiers.customAdditions?.length) {
|
|
1715
1606
|
userPromptParts.push(...modifiers.customAdditions);
|
|
@@ -1719,7 +1610,7 @@ ${infoParts.join("\n")}`;
|
|
|
1719
1610
|
}
|
|
1720
1611
|
const userPromptText = userPromptParts.join(" - ");
|
|
1721
1612
|
const numImages = options?.num || config.defaultNumImages;
|
|
1722
|
-
const limitCheck = await checkDailyLimit(session.userId, numImages);
|
|
1613
|
+
const limitCheck = await userManager.checkDailyLimit(session.userId, config, numImages);
|
|
1723
1614
|
if (!limitCheck.allowed) {
|
|
1724
1615
|
return limitCheck.message;
|
|
1725
1616
|
}
|
|
@@ -1755,7 +1646,7 @@ ${infoParts.join("\n")}`;
|
|
|
1755
1646
|
ctx.command(`${COMMANDS.TXT_TO_IMG} [prompt:text]`, "根据文字描述生成图像").option("num", "-n <num:number> 生成图片数量 (1-4)").action(async ({ session, options }, prompt) => {
|
|
1756
1647
|
if (!session?.userId) return "会话无效";
|
|
1757
1648
|
const numImages = options?.num || config.defaultNumImages;
|
|
1758
|
-
const limitCheck = await checkDailyLimit(session.userId, numImages);
|
|
1649
|
+
const limitCheck = await userManager.checkDailyLimit(session.userId, config, numImages);
|
|
1759
1650
|
if (!limitCheck.allowed) {
|
|
1760
1651
|
return limitCheck.message;
|
|
1761
1652
|
}
|
|
@@ -1768,7 +1659,7 @@ ${infoParts.join("\n")}`;
|
|
|
1768
1659
|
if (!session?.userId) return "会话无效";
|
|
1769
1660
|
const numImages = options?.num || config.defaultNumImages;
|
|
1770
1661
|
const mode = options?.multiple ? "multiple" : "single";
|
|
1771
|
-
const limitCheck = await checkDailyLimit(session.userId, numImages);
|
|
1662
|
+
const limitCheck = await userManager.checkDailyLimit(session.userId, config, numImages);
|
|
1772
1663
|
if (!limitCheck.allowed) {
|
|
1773
1664
|
return limitCheck.message;
|
|
1774
1665
|
}
|
|
@@ -1779,106 +1670,99 @@ ${infoParts.join("\n")}`;
|
|
|
1779
1670
|
});
|
|
1780
1671
|
ctx.command(COMMANDS.COMPOSE_IMAGE, "合成多张图片,使用自定义prompt控制合成效果").option("num", "-n <num:number> 生成图片数量 (1-4)").action(async ({ session, options }) => {
|
|
1781
1672
|
if (!session?.userId) return "会话无效";
|
|
1673
|
+
const userId = session.userId;
|
|
1674
|
+
if (!userManager.startTask(userId)) {
|
|
1675
|
+
return "您有一个图像处理任务正在进行中,请等待完成";
|
|
1676
|
+
}
|
|
1677
|
+
userManager.endTask(userId);
|
|
1678
|
+
let isTimeout = false;
|
|
1782
1679
|
return Promise.race([
|
|
1783
1680
|
(async () => {
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1681
|
+
if (!userManager.startTask(userId)) return "您有一个图像处理任务正在进行中";
|
|
1682
|
+
try {
|
|
1683
|
+
await session.send("多张图片+描述");
|
|
1684
|
+
const collectedImages = [];
|
|
1685
|
+
let prompt = "";
|
|
1686
|
+
while (true) {
|
|
1687
|
+
const msg = await session.prompt(6e4);
|
|
1688
|
+
if (!msg) {
|
|
1689
|
+
return "等待超时,请重试";
|
|
1690
|
+
}
|
|
1691
|
+
if (isTimeout) throw new Error("命令执行超时");
|
|
1692
|
+
const elements = import_koishi2.h.parse(msg);
|
|
1693
|
+
const images = import_koishi2.h.select(elements, "img");
|
|
1694
|
+
const textElements = import_koishi2.h.select(elements, "text");
|
|
1695
|
+
const text = textElements.map((el) => el.attrs.content).join(" ").trim();
|
|
1696
|
+
if (images.length > 0) {
|
|
1697
|
+
for (const img of images) {
|
|
1698
|
+
collectedImages.push(img.attrs.src);
|
|
1699
|
+
}
|
|
1700
|
+
if (text) {
|
|
1701
|
+
prompt = text;
|
|
1702
|
+
break;
|
|
1703
|
+
}
|
|
1704
|
+
await session.send(`已收到 ${collectedImages.length} 张图片,继续发送或输入描述`);
|
|
1705
|
+
continue;
|
|
1804
1706
|
}
|
|
1805
1707
|
if (text) {
|
|
1708
|
+
if (collectedImages.length < 2) {
|
|
1709
|
+
return `需要至少两张图片进行合成,当前只有 ${collectedImages.length} 张图片`;
|
|
1710
|
+
}
|
|
1806
1711
|
prompt = text;
|
|
1807
1712
|
break;
|
|
1808
1713
|
}
|
|
1809
|
-
|
|
1810
|
-
continue;
|
|
1714
|
+
return "未检测到有效内容,操作已取消";
|
|
1811
1715
|
}
|
|
1812
|
-
if (
|
|
1813
|
-
|
|
1814
|
-
return `需要至少两张图片进行合成,当前只有 ${collectedImages.length} 张图片`;
|
|
1815
|
-
}
|
|
1816
|
-
prompt = text;
|
|
1817
|
-
break;
|
|
1716
|
+
if (collectedImages.length < 2) {
|
|
1717
|
+
return "需要至少两张图片进行合成,请重新发送";
|
|
1818
1718
|
}
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
numImages: imageCount,
|
|
1840
|
-
imageCount: collectedImages.length
|
|
1841
|
-
});
|
|
1842
|
-
await session.send(`开始合成图(${collectedImages.length}张)...
|
|
1719
|
+
if (!prompt) {
|
|
1720
|
+
return "未检测到prompt描述,请重新发送";
|
|
1721
|
+
}
|
|
1722
|
+
const imageCount = options?.num || config.defaultNumImages;
|
|
1723
|
+
if (imageCount < 1 || imageCount > 4) {
|
|
1724
|
+
return "生成数量必须在 1-4 之间";
|
|
1725
|
+
}
|
|
1726
|
+
const limitCheck = await userManager.checkDailyLimit(userId, config, imageCount);
|
|
1727
|
+
if (!limitCheck.allowed) {
|
|
1728
|
+
return limitCheck.message;
|
|
1729
|
+
}
|
|
1730
|
+
if (isTimeout) throw new Error("命令执行超时");
|
|
1731
|
+
logger.info("开始图片合成处理", {
|
|
1732
|
+
userId,
|
|
1733
|
+
imageUrls: collectedImages,
|
|
1734
|
+
prompt,
|
|
1735
|
+
numImages: imageCount,
|
|
1736
|
+
imageCount: collectedImages.length
|
|
1737
|
+
});
|
|
1738
|
+
await session.send(`开始合成图(${collectedImages.length}张)...
|
|
1843
1739
|
Prompt: ${prompt}`);
|
|
1844
|
-
try {
|
|
1845
|
-
activeTasks.set(userId, "processing");
|
|
1846
1740
|
const resultImages = await requestProviderImages(prompt, collectedImages, imageCount);
|
|
1741
|
+
if (isTimeout) throw new Error("命令执行超时");
|
|
1847
1742
|
if (resultImages.length === 0) {
|
|
1848
|
-
activeTasks.delete(userId);
|
|
1849
1743
|
return "图片合成失败:未能生成图片";
|
|
1850
1744
|
}
|
|
1851
1745
|
await session.send("图片合成完成!");
|
|
1852
1746
|
for (let i = 0; i < resultImages.length; i++) {
|
|
1853
|
-
|
|
1747
|
+
if (isTimeout) break;
|
|
1748
|
+
await session.send(import_koishi2.h.image(resultImages[i]));
|
|
1854
1749
|
if (resultImages.length > 1 && i < resultImages.length - 1) {
|
|
1855
1750
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1856
1751
|
}
|
|
1857
1752
|
}
|
|
1858
1753
|
await recordUserUsage(session, COMMANDS.COMPOSE_IMAGE, resultImages.length);
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
activeTasks.delete(userId);
|
|
1862
|
-
const sanitizedError = sanitizeError(error);
|
|
1863
|
-
logger.error("图片合成失败", { userId, error: sanitizedError });
|
|
1864
|
-
const errorMessage = error?.message || "";
|
|
1865
|
-
const isSecurityBlock = errorMessage.includes("内容被安全策略拦截") || errorMessage.includes("内容被安全策略阻止") || errorMessage.includes("内容被阻止") || errorMessage.includes("被阻止") || errorMessage.includes("SAFETY") || errorMessage.includes("RECITATION");
|
|
1866
|
-
if (isSecurityBlock) {
|
|
1867
|
-
await recordSecurityBlock(session, imageCount);
|
|
1868
|
-
}
|
|
1869
|
-
if (error?.message) {
|
|
1870
|
-
const safeMessage = sanitizeString(error.message);
|
|
1871
|
-
return `图片合成失败:${safeMessage}`;
|
|
1872
|
-
}
|
|
1873
|
-
return "图片合成失败,请稍后重试";
|
|
1754
|
+
} finally {
|
|
1755
|
+
userManager.endTask(userId);
|
|
1874
1756
|
}
|
|
1875
1757
|
})(),
|
|
1876
1758
|
new Promise(
|
|
1877
|
-
(_, reject) => setTimeout(() =>
|
|
1759
|
+
(_, reject) => setTimeout(() => {
|
|
1760
|
+
isTimeout = true;
|
|
1761
|
+
reject(new Error("命令执行超时"));
|
|
1762
|
+
}, config.commandTimeout * 1e3)
|
|
1878
1763
|
)
|
|
1879
1764
|
]).catch(async (error) => {
|
|
1880
|
-
|
|
1881
|
-
if (userId) activeTasks.delete(userId);
|
|
1765
|
+
if (userId) userManager.endTask(userId);
|
|
1882
1766
|
const sanitizedError = sanitizeError(error);
|
|
1883
1767
|
logger.error("图片合成超时或失败", { userId, error: sanitizedError });
|
|
1884
1768
|
if (error?.message !== "命令执行超时") {
|
|
@@ -1895,14 +1779,14 @@ Prompt: ${prompt}`);
|
|
|
1895
1779
|
});
|
|
1896
1780
|
ctx.command(`${COMMANDS.RECHARGE} [content:text]`, "为用户充值次数(仅管理员)").action(async ({ session }, content) => {
|
|
1897
1781
|
if (!session?.userId) return "会话无效";
|
|
1898
|
-
if (!isAdmin(session.userId)) {
|
|
1782
|
+
if (!userManager.isAdmin(session.userId, config)) {
|
|
1899
1783
|
return "权限不足,仅管理员可操作";
|
|
1900
1784
|
}
|
|
1901
1785
|
const inputContent = content || await getPromptInput(session, "请输入充值信息,格式:\n@用户1 @用户2 充值次数 [备注]");
|
|
1902
1786
|
if (!inputContent) return "输入超时或无效";
|
|
1903
|
-
const elements =
|
|
1904
|
-
const atElements =
|
|
1905
|
-
const textElements =
|
|
1787
|
+
const elements = import_koishi2.h.parse(inputContent);
|
|
1788
|
+
const atElements = import_koishi2.h.select(elements, "at");
|
|
1789
|
+
const textElements = import_koishi2.h.select(elements, "text");
|
|
1906
1790
|
const text = textElements.map((el) => el.attrs.content).join(" ").trim();
|
|
1907
1791
|
if (atElements.length === 0) {
|
|
1908
1792
|
return "未找到@用户,请使用@用户的方式";
|
|
@@ -1921,66 +1805,64 @@ Prompt: ${prompt}`);
|
|
|
1921
1805
|
return "未找到有效的用户,请使用@用户的方式";
|
|
1922
1806
|
}
|
|
1923
1807
|
try {
|
|
1924
|
-
const usersData = await loadUsersData();
|
|
1925
|
-
const rechargeHistory = await loadRechargeHistory();
|
|
1926
1808
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1927
1809
|
const recordId = `recharge_${now.replace(/[-:T.]/g, "").slice(0, 14)}_${Math.random().toString(36).substr(2, 3)}`;
|
|
1928
1810
|
const targets = [];
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
userName =
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1811
|
+
let totalAmount = 0;
|
|
1812
|
+
await userManager.updateUsersBatch((usersData) => {
|
|
1813
|
+
for (const userId of userIds) {
|
|
1814
|
+
if (!userId) continue;
|
|
1815
|
+
let userName = userId;
|
|
1816
|
+
if (usersData[userId]) {
|
|
1817
|
+
userName = usersData[userId].userName || userId;
|
|
1818
|
+
} else {
|
|
1819
|
+
usersData[userId] = {
|
|
1820
|
+
userId,
|
|
1821
|
+
userName: userId,
|
|
1822
|
+
totalUsageCount: 0,
|
|
1823
|
+
dailyUsageCount: 0,
|
|
1824
|
+
lastDailyReset: now,
|
|
1825
|
+
purchasedCount: 0,
|
|
1826
|
+
remainingPurchasedCount: 0,
|
|
1827
|
+
donationCount: 0,
|
|
1828
|
+
donationAmount: 0,
|
|
1829
|
+
lastUsed: now,
|
|
1830
|
+
createdAt: now
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
const beforeBalance = usersData[userId].remainingPurchasedCount;
|
|
1834
|
+
usersData[userId].purchasedCount += amount;
|
|
1835
|
+
usersData[userId].remainingPurchasedCount += amount;
|
|
1836
|
+
targets.push({
|
|
1937
1837
|
userId,
|
|
1938
|
-
userName
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
remainingPurchasedCount: 0,
|
|
1944
|
-
donationCount: 0,
|
|
1945
|
-
donationAmount: 0,
|
|
1946
|
-
lastUsed: now,
|
|
1947
|
-
createdAt: now
|
|
1948
|
-
};
|
|
1838
|
+
userName,
|
|
1839
|
+
amount,
|
|
1840
|
+
beforeBalance,
|
|
1841
|
+
afterBalance: usersData[userId].remainingPurchasedCount
|
|
1842
|
+
});
|
|
1949
1843
|
}
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
targets.push({
|
|
1954
|
-
userId,
|
|
1955
|
-
userName,
|
|
1956
|
-
amount,
|
|
1957
|
-
beforeBalance,
|
|
1958
|
-
afterBalance: usersData[userId].remainingPurchasedCount
|
|
1959
|
-
});
|
|
1960
|
-
}
|
|
1961
|
-
await saveUsersData(usersData);
|
|
1962
|
-
const record = {
|
|
1844
|
+
totalAmount = amount * targets.length;
|
|
1845
|
+
});
|
|
1846
|
+
await userManager.addRechargeRecord({
|
|
1963
1847
|
id: recordId,
|
|
1964
1848
|
timestamp: now,
|
|
1965
|
-
type:
|
|
1849
|
+
type: targets.length > 1 ? "batch" : "single",
|
|
1966
1850
|
operator: {
|
|
1967
1851
|
userId: session.userId,
|
|
1968
1852
|
userName: session.username || session.userId
|
|
1969
1853
|
},
|
|
1970
1854
|
targets,
|
|
1971
|
-
totalAmount
|
|
1972
|
-
note
|
|
1855
|
+
totalAmount,
|
|
1856
|
+
note,
|
|
1973
1857
|
metadata: {}
|
|
1974
|
-
};
|
|
1975
|
-
rechargeHistory.records.push(record);
|
|
1976
|
-
await saveRechargeHistory(rechargeHistory);
|
|
1858
|
+
});
|
|
1977
1859
|
const userList = targets.map((t) => `${t.userName}(${t.afterBalance}次)`).join(", ");
|
|
1978
1860
|
return `✅ 充值成功
|
|
1979
1861
|
目标用户:${userList}
|
|
1980
1862
|
充值次数:${amount}次/人
|
|
1981
|
-
总充值:${
|
|
1982
|
-
操作员:${
|
|
1983
|
-
备注:${
|
|
1863
|
+
总充值:${totalAmount}次
|
|
1864
|
+
操作员:${session.username}
|
|
1865
|
+
备注:${note}`;
|
|
1984
1866
|
} catch (error) {
|
|
1985
1867
|
logger.error("充值操作失败", error);
|
|
1986
1868
|
return "充值失败,请稍后重试";
|
|
@@ -1988,13 +1870,13 @@ Prompt: ${prompt}`);
|
|
|
1988
1870
|
});
|
|
1989
1871
|
ctx.command(`${COMMANDS.RECHARGE_ALL} [content:text]`, "为所有用户充值次数(活动派发,仅管理员)").action(async ({ session }, content) => {
|
|
1990
1872
|
if (!session?.userId) return "会话无效";
|
|
1991
|
-
if (!isAdmin(session.userId)) {
|
|
1873
|
+
if (!userManager.isAdmin(session.userId, config)) {
|
|
1992
1874
|
return "权限不足,仅管理员可操作";
|
|
1993
1875
|
}
|
|
1994
1876
|
const inputContent = content || await getPromptInput(session, "请输入活动充值信息,格式:\n充值次数 [备注]\n例如:20 或 20 春节活动奖励");
|
|
1995
1877
|
if (!inputContent) return "输入超时或无效";
|
|
1996
|
-
const elements =
|
|
1997
|
-
const textElements =
|
|
1878
|
+
const elements = import_koishi2.h.parse(inputContent);
|
|
1879
|
+
const textElements = import_koishi2.h.select(elements, "text");
|
|
1998
1880
|
const text = textElements.map((el) => el.attrs.content).join(" ").trim();
|
|
1999
1881
|
const parts = text.split(/\s+/).filter((p) => p);
|
|
2000
1882
|
if (parts.length === 0) {
|
|
@@ -2006,32 +1888,34 @@ Prompt: ${prompt}`);
|
|
|
2006
1888
|
return "充值次数必须大于0";
|
|
2007
1889
|
}
|
|
2008
1890
|
try {
|
|
2009
|
-
const usersData = await loadUsersData();
|
|
2010
|
-
const rechargeHistory = await loadRechargeHistory();
|
|
2011
1891
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2012
1892
|
const recordId = `recharge_all_${now.replace(/[-:T.]/g, "").slice(0, 14)}_${Math.random().toString(36).substr(2, 3)}`;
|
|
2013
|
-
const allUserIds = Object.keys(usersData).filter((userId) => userId && usersData[userId]);
|
|
2014
|
-
if (allUserIds.length === 0) {
|
|
2015
|
-
return "当前没有使用过插件的用户,无法进行活动充值";
|
|
2016
|
-
}
|
|
2017
1893
|
const targets = [];
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
const
|
|
2022
|
-
const
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
1894
|
+
let totalAmount = 0;
|
|
1895
|
+
let successCount = 0;
|
|
1896
|
+
await userManager.updateUsersBatch((usersData) => {
|
|
1897
|
+
const allUserIds = Object.keys(usersData);
|
|
1898
|
+
for (const userId of allUserIds) {
|
|
1899
|
+
if (!userId || !usersData[userId]) continue;
|
|
1900
|
+
const userData = usersData[userId];
|
|
1901
|
+
const beforeBalance = userData.remainingPurchasedCount;
|
|
1902
|
+
userData.purchasedCount += amount;
|
|
1903
|
+
userData.remainingPurchasedCount += amount;
|
|
1904
|
+
targets.push({
|
|
1905
|
+
userId,
|
|
1906
|
+
userName: userData.userName || userId,
|
|
1907
|
+
amount,
|
|
1908
|
+
beforeBalance,
|
|
1909
|
+
afterBalance: userData.remainingPurchasedCount
|
|
1910
|
+
});
|
|
1911
|
+
successCount++;
|
|
1912
|
+
}
|
|
1913
|
+
totalAmount = amount * successCount;
|
|
1914
|
+
});
|
|
1915
|
+
if (successCount === 0) {
|
|
1916
|
+
return "当前没有使用过插件的用户,无法进行活动充值";
|
|
2032
1917
|
}
|
|
2033
|
-
await
|
|
2034
|
-
const record = {
|
|
1918
|
+
await userManager.addRechargeRecord({
|
|
2035
1919
|
id: recordId,
|
|
2036
1920
|
timestamp: now,
|
|
2037
1921
|
type: "all",
|
|
@@ -2040,18 +1924,16 @@ Prompt: ${prompt}`);
|
|
|
2040
1924
|
userName: session.username || session.userId
|
|
2041
1925
|
},
|
|
2042
1926
|
targets,
|
|
2043
|
-
totalAmount
|
|
2044
|
-
note
|
|
1927
|
+
totalAmount,
|
|
1928
|
+
note,
|
|
2045
1929
|
metadata: { all: true }
|
|
2046
|
-
};
|
|
2047
|
-
rechargeHistory.records.push(record);
|
|
2048
|
-
await saveRechargeHistory(rechargeHistory);
|
|
1930
|
+
});
|
|
2049
1931
|
return `✅ 活动充值成功
|
|
2050
|
-
目标用户数:${
|
|
1932
|
+
目标用户数:${successCount}人
|
|
2051
1933
|
充值次数:${amount}次/人
|
|
2052
|
-
总充值:${
|
|
2053
|
-
操作员:${
|
|
2054
|
-
备注:${
|
|
1934
|
+
总充值:${totalAmount}次
|
|
1935
|
+
操作员:${session.username}
|
|
1936
|
+
备注:${note}`;
|
|
2055
1937
|
} catch (error) {
|
|
2056
1938
|
logger.error("活动充值操作失败", error);
|
|
2057
1939
|
return "活动充值失败,请稍后重试";
|
|
@@ -2059,7 +1941,7 @@ Prompt: ${prompt}`);
|
|
|
2059
1941
|
});
|
|
2060
1942
|
ctx.command(`${COMMANDS.QUERY_QUOTA} [target:text]`, "查询用户额度信息").action(async ({ session }, target) => {
|
|
2061
1943
|
if (!session?.userId) return "会话无效";
|
|
2062
|
-
const userIsAdmin = isAdmin(session.userId);
|
|
1944
|
+
const userIsAdmin = userManager.isAdmin(session.userId, config);
|
|
2063
1945
|
let targetUserId = session.userId;
|
|
2064
1946
|
let targetUserName = session.username || session.userId;
|
|
2065
1947
|
if (target && userIsAdmin) {
|
|
@@ -2072,15 +1954,7 @@ Prompt: ${prompt}`);
|
|
|
2072
1954
|
return "权限不足,仅管理员可查询其他用户";
|
|
2073
1955
|
}
|
|
2074
1956
|
try {
|
|
2075
|
-
const
|
|
2076
|
-
const userData = usersData[targetUserId];
|
|
2077
|
-
if (!userData) {
|
|
2078
|
-
return `👤 用户信息
|
|
2079
|
-
用户:${targetUserName}
|
|
2080
|
-
状态:新用户
|
|
2081
|
-
今日剩余免费:${config.dailyFreeLimit}次
|
|
2082
|
-
充值剩余:0次`;
|
|
2083
|
-
}
|
|
1957
|
+
const userData = await userManager.getUserData(targetUserId, targetUserName);
|
|
2084
1958
|
const remainingToday = Math.max(0, config.dailyFreeLimit - userData.dailyUsageCount);
|
|
2085
1959
|
const totalAvailable = remainingToday + userData.remainingPurchasedCount;
|
|
2086
1960
|
return `👤 用户额度信息
|
|
@@ -2097,11 +1971,11 @@ Prompt: ${prompt}`);
|
|
|
2097
1971
|
});
|
|
2098
1972
|
ctx.command(`${COMMANDS.RECHARGE_HISTORY} [page:number]`, "查看充值历史记录(仅管理员)").action(async ({ session }, page = 1) => {
|
|
2099
1973
|
if (!session?.userId) return "会话无效";
|
|
2100
|
-
if (!isAdmin(session.userId)) {
|
|
1974
|
+
if (!userManager.isAdmin(session.userId, config)) {
|
|
2101
1975
|
return "权限不足,仅管理员可查看充值记录";
|
|
2102
1976
|
}
|
|
2103
1977
|
try {
|
|
2104
|
-
const history = await loadRechargeHistory();
|
|
1978
|
+
const history = await userManager.loadRechargeHistory();
|
|
2105
1979
|
const pageSize = 10;
|
|
2106
1980
|
const totalPages = Math.ceil(history.records.length / pageSize);
|
|
2107
1981
|
const startIndex = (page - 1) * pageSize;
|
|
@@ -2135,7 +2009,7 @@ Prompt: ${prompt}`);
|
|
|
2135
2009
|
ctx.command(COMMANDS.FUNCTION_LIST, "查看所有可用的图像处理功能").action(async ({ session }) => {
|
|
2136
2010
|
if (!session?.userId) return "会话无效";
|
|
2137
2011
|
try {
|
|
2138
|
-
const userIsAdmin = isAdmin(session.userId);
|
|
2012
|
+
const userIsAdmin = userManager.isAdmin(session.userId, config);
|
|
2139
2013
|
let result = "🎨 图像处理功能列表\n\n";
|
|
2140
2014
|
result += "📝 用户指令:\n";
|
|
2141
2015
|
commandRegistry.userCommands.forEach((cmd) => {
|