koishi-plugin-aka-ai-generator 0.2.10 → 0.2.13
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 +231 -200
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -39,18 +39,13 @@ async function downloadImageAsBase64(ctx, url, timeout, logger) {
|
|
|
39
39
|
const buffer = Buffer.from(response);
|
|
40
40
|
const base64 = buffer.toString("base64");
|
|
41
41
|
let mimeType = "image/jpeg";
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
mimeType =
|
|
45
|
-
} else {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
} else if (urlLower.endsWith(".webp")) {
|
|
50
|
-
mimeType = "image/webp";
|
|
51
|
-
} else if (urlLower.endsWith(".gif")) {
|
|
52
|
-
mimeType = "image/gif";
|
|
53
|
-
}
|
|
42
|
+
const urlLower = url.toLowerCase();
|
|
43
|
+
if (urlLower.endsWith(".png")) {
|
|
44
|
+
mimeType = "image/png";
|
|
45
|
+
} else if (urlLower.endsWith(".webp")) {
|
|
46
|
+
mimeType = "image/webp";
|
|
47
|
+
} else if (urlLower.endsWith(".gif")) {
|
|
48
|
+
mimeType = "image/gif";
|
|
54
49
|
}
|
|
55
50
|
logger.debug("图片下载并转换为Base64", { url, mimeType, size: base64.length });
|
|
56
51
|
return { data: base64, mimeType };
|
|
@@ -184,18 +179,13 @@ async function downloadImageAsBase642(ctx, url, timeout, logger) {
|
|
|
184
179
|
const buffer = Buffer.from(response);
|
|
185
180
|
const base64 = buffer.toString("base64");
|
|
186
181
|
let mimeType = "image/jpeg";
|
|
187
|
-
const
|
|
188
|
-
if (
|
|
189
|
-
mimeType =
|
|
190
|
-
} else {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
} else if (urlLower.endsWith(".webp")) {
|
|
195
|
-
mimeType = "image/webp";
|
|
196
|
-
} else if (urlLower.endsWith(".gif")) {
|
|
197
|
-
mimeType = "image/gif";
|
|
198
|
-
}
|
|
182
|
+
const urlLower = url.toLowerCase();
|
|
183
|
+
if (urlLower.endsWith(".png")) {
|
|
184
|
+
mimeType = "image/png";
|
|
185
|
+
} else if (urlLower.endsWith(".webp")) {
|
|
186
|
+
mimeType = "image/webp";
|
|
187
|
+
} else if (urlLower.endsWith(".gif")) {
|
|
188
|
+
mimeType = "image/gif";
|
|
199
189
|
}
|
|
200
190
|
if (logger) {
|
|
201
191
|
logger.debug("图片下载并转换为Base64", { url, mimeType, size: base64.length });
|
|
@@ -404,13 +394,7 @@ var GptGodProvider = class {
|
|
|
404
394
|
if (this.config.logLevel === "debug") {
|
|
405
395
|
logger.debug("调用 GPTGod 图像编辑 API", { prompt, imageCount: urls.length, numImages });
|
|
406
396
|
}
|
|
407
|
-
const
|
|
408
|
-
{
|
|
409
|
-
type: "text",
|
|
410
|
-
text: `${prompt}
|
|
411
|
-
请生成 ${numImages} 张图片。`
|
|
412
|
-
}
|
|
413
|
-
];
|
|
397
|
+
const imageParts = [];
|
|
414
398
|
for (const url of urls) {
|
|
415
399
|
const imagePart = await buildImageContentPart(
|
|
416
400
|
ctx,
|
|
@@ -418,91 +402,91 @@ var GptGodProvider = class {
|
|
|
418
402
|
this.config.apiTimeout,
|
|
419
403
|
logger
|
|
420
404
|
);
|
|
421
|
-
|
|
422
|
-
}
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
n: numImages,
|
|
427
|
-
// 使用 n 参数指定生成数量
|
|
428
|
-
messages: [
|
|
405
|
+
imageParts.push(imagePart);
|
|
406
|
+
}
|
|
407
|
+
const allImages = [];
|
|
408
|
+
for (let i = 0; i < numImages; i++) {
|
|
409
|
+
const contentParts = [
|
|
429
410
|
{
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const requestBodySize = JSON.stringify(requestData).length;
|
|
440
|
-
if (this.config.logLevel === "debug") {
|
|
441
|
-
logger.debug(`GPTGod API 请求 (尝试 ${attempt}/${maxRetries})`, {
|
|
442
|
-
requestBodySize: `${(requestBodySize / 1024).toFixed(2)} KB`,
|
|
443
|
-
imageCount: urls.length
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
const response = await ctx.http.post(
|
|
447
|
-
GPTGOD_DEFAULT_API_URL,
|
|
448
|
-
requestData,
|
|
411
|
+
type: "text",
|
|
412
|
+
text: prompt
|
|
413
|
+
},
|
|
414
|
+
...imageParts
|
|
415
|
+
];
|
|
416
|
+
const requestData = {
|
|
417
|
+
model: this.config.modelId,
|
|
418
|
+
stream: false,
|
|
419
|
+
messages: [
|
|
449
420
|
{
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
"Authorization": `Bearer ${this.config.apiKey}`
|
|
453
|
-
},
|
|
454
|
-
timeout: this.config.apiTimeout * 1e3
|
|
455
|
-
}
|
|
456
|
-
);
|
|
457
|
-
logger.success("GPTGod 图像编辑 API 调用成功");
|
|
458
|
-
if (response?.choices?.length > 0) {
|
|
459
|
-
const firstChoice = response.choices[0];
|
|
460
|
-
const messageContent = firstChoice.message?.content;
|
|
461
|
-
let errorMessage = "";
|
|
462
|
-
if (typeof messageContent === "string") {
|
|
463
|
-
errorMessage = messageContent;
|
|
464
|
-
} else if (Array.isArray(messageContent)) {
|
|
465
|
-
const textParts = messageContent.filter((part) => part?.type === "text" && part?.text).map((part) => part.text).join(" ");
|
|
466
|
-
errorMessage = textParts;
|
|
467
|
-
} else if (messageContent?.text) {
|
|
468
|
-
errorMessage = messageContent.text;
|
|
421
|
+
role: "user",
|
|
422
|
+
content: contentParts
|
|
469
423
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
424
|
+
]
|
|
425
|
+
};
|
|
426
|
+
logger.debug("调用 GPTGod 图像编辑 API", { prompt, imageCount: urls.length, numImages, current: i + 1 });
|
|
427
|
+
const maxRetries = 3;
|
|
428
|
+
let lastError = null;
|
|
429
|
+
let success = false;
|
|
430
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
431
|
+
try {
|
|
432
|
+
const requestBodySize = JSON.stringify(requestData).length;
|
|
433
|
+
if (this.config.logLevel === "debug") {
|
|
434
|
+
logger.debug(`GPTGod API 请求 (尝试 ${attempt}/${maxRetries})`, {
|
|
435
|
+
requestBodySize: `${(requestBodySize / 1024).toFixed(2)} KB`,
|
|
436
|
+
imageCount: urls.length,
|
|
437
|
+
current: i + 1
|
|
474
438
|
});
|
|
475
|
-
throw new Error("内容被安全策略拦截");
|
|
476
439
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
440
|
+
const response = await ctx.http.post(
|
|
441
|
+
GPTGOD_DEFAULT_API_URL,
|
|
442
|
+
requestData,
|
|
443
|
+
{
|
|
444
|
+
headers: {
|
|
445
|
+
"Content-Type": "application/json",
|
|
446
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
447
|
+
},
|
|
448
|
+
timeout: this.config.apiTimeout * 1e3
|
|
449
|
+
}
|
|
450
|
+
);
|
|
451
|
+
if (response?.choices?.length > 0) {
|
|
452
|
+
const firstChoice = response.choices[0];
|
|
453
|
+
const messageContent = firstChoice.message?.content;
|
|
454
|
+
let errorMessage = "";
|
|
455
|
+
if (typeof messageContent === "string") {
|
|
456
|
+
errorMessage = messageContent;
|
|
457
|
+
} else if (Array.isArray(messageContent)) {
|
|
458
|
+
const textParts = messageContent.filter((part) => part?.type === "text" && part?.text).map((part) => part.text).join(" ");
|
|
459
|
+
errorMessage = textParts;
|
|
460
|
+
} else if (messageContent?.text) {
|
|
461
|
+
errorMessage = messageContent.text;
|
|
462
|
+
}
|
|
463
|
+
if (errorMessage && (errorMessage.includes("PROHIBITED_CONTENT") || errorMessage.includes("blocked by Google Gemini") || errorMessage.includes("prohibited under official usage policies") || errorMessage.toLowerCase().includes("content is prohibited"))) {
|
|
464
|
+
logger.error("内容被 Google Gemini 政策拦截", {
|
|
465
|
+
errorMessage: errorMessage.substring(0, 200),
|
|
466
|
+
finishReason: firstChoice.finish_reason
|
|
467
|
+
});
|
|
468
|
+
throw new Error("内容被安全策略拦截");
|
|
469
|
+
}
|
|
470
|
+
if (errorMessage && (errorMessage.toLowerCase().includes("error") || errorMessage.toLowerCase().includes("failed") || errorMessage.toLowerCase().includes("blocked")) && !errorMessage.match(/https?:\/\//)) {
|
|
471
|
+
logger.error("API 返回错误消息", {
|
|
472
|
+
errorMessage: errorMessage.substring(0, 200),
|
|
473
|
+
finishReason: firstChoice.finish_reason
|
|
474
|
+
});
|
|
475
|
+
const shortError = errorMessage.length > 50 ? errorMessage.substring(0, 50) + "..." : errorMessage;
|
|
476
|
+
throw new Error(`处理失败:${shortError}`);
|
|
477
|
+
}
|
|
484
478
|
}
|
|
485
|
-
}
|
|
486
|
-
if (this.config.logLevel === "debug") {
|
|
487
|
-
logger.debug("GPTGod API 响应结构", {
|
|
488
|
-
hasChoices: !!response?.choices,
|
|
489
|
-
choicesLength: response?.choices?.length,
|
|
490
|
-
hasImages: !!response?.images,
|
|
491
|
-
hasImage: !!response?.image,
|
|
492
|
-
responseKeys: Object.keys(response || {}),
|
|
493
|
-
firstChoiceContent: response?.choices?.[0]?.message?.content ? typeof response.choices[0].message.content === "string" ? response.choices[0].message.content.substring(0, 200) : JSON.stringify(response.choices[0].message.content).substring(0, 200) : "none"
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
const images = parseGptGodResponse(response, this.config.logLevel === "debug" ? logger : null);
|
|
497
|
-
if (images.length < numImages) {
|
|
498
|
-
const warnData = {
|
|
499
|
-
requested: numImages,
|
|
500
|
-
received: images.length
|
|
501
|
-
};
|
|
502
479
|
if (this.config.logLevel === "debug") {
|
|
503
|
-
|
|
480
|
+
logger.debug("GPTGod API 响应结构", {
|
|
481
|
+
hasChoices: !!response?.choices,
|
|
482
|
+
choicesLength: response?.choices?.length,
|
|
483
|
+
hasImages: !!response?.images,
|
|
484
|
+
hasImage: !!response?.image,
|
|
485
|
+
responseKeys: Object.keys(response || {}),
|
|
486
|
+
firstChoiceContent: response?.choices?.[0]?.message?.content ? typeof response.choices[0].message.content === "string" ? response.choices[0].message.content.substring(0, 200) : JSON.stringify(response.choices[0].message.content).substring(0, 200) : "none"
|
|
487
|
+
});
|
|
504
488
|
}
|
|
505
|
-
|
|
489
|
+
const images = parseGptGodResponse(response, this.config.logLevel === "debug" ? logger : null);
|
|
506
490
|
if (images.length === 0 && response?.choices?.[0]?.message?.content) {
|
|
507
491
|
const content = response.choices[0].message.content;
|
|
508
492
|
const contentText = typeof content === "string" ? content : Array.isArray(content) ? content.map((p) => p?.text || "").join(" ") : "";
|
|
@@ -511,56 +495,69 @@ var GptGodProvider = class {
|
|
|
511
495
|
throw new Error(`生成失败:${shortError}`);
|
|
512
496
|
}
|
|
513
497
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
498
|
+
allImages.push(...images);
|
|
499
|
+
logger.success("GPTGod 图像编辑 API 调用成功", { current: i + 1, total: numImages });
|
|
500
|
+
success = true;
|
|
501
|
+
break;
|
|
502
|
+
} catch (error) {
|
|
503
|
+
lastError = error;
|
|
504
|
+
if (error?.message && (error.message.includes("内容被安全策略拦截") || error.message.includes("生成失败") || error.message.includes("处理失败"))) {
|
|
505
|
+
throw error;
|
|
506
|
+
}
|
|
507
|
+
const isRetryableError = error?.cause?.code === "UND_ERR_SOCKET" || // Socket 错误
|
|
508
|
+
error?.code === "UND_ERR_SOCKET" || error?.message?.includes("other side closed") || error?.message?.includes("fetch failed") || error?.message?.includes("ECONNRESET") || error?.message?.includes("ETIMEDOUT") || error?.response?.status >= 500 && error?.response?.status < 600;
|
|
509
|
+
if (isRetryableError && attempt < maxRetries) {
|
|
510
|
+
const delay = Math.min(1e3 * Math.pow(2, attempt - 1), 5e3);
|
|
511
|
+
logger.warn(`GPTGod API 调用失败,将在 ${delay}ms 后重试 (${attempt}/${maxRetries})`, {
|
|
512
|
+
error: error?.message || error?.cause?.message || "连接错误",
|
|
513
|
+
code: error?.code || error?.cause?.code,
|
|
514
|
+
current: i + 1
|
|
515
|
+
});
|
|
516
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
logger.error("GPTGod 图像编辑 API 调用失败", {
|
|
520
|
+
message: error?.message || "未知错误",
|
|
521
|
+
name: error?.name,
|
|
522
|
+
code: error?.code,
|
|
523
|
+
status: error?.response?.status,
|
|
524
|
+
statusText: error?.response?.statusText,
|
|
525
|
+
data: error?.response?.data,
|
|
526
|
+
stack: error?.stack,
|
|
527
|
+
cause: error?.cause,
|
|
528
|
+
attempt,
|
|
529
|
+
maxRetries,
|
|
530
|
+
current: i + 1,
|
|
531
|
+
total: numImages,
|
|
532
|
+
// 如果是 axios 错误,通常会有 config 和 request 信息
|
|
533
|
+
url: error?.config?.url,
|
|
534
|
+
method: error?.config?.method,
|
|
535
|
+
headers: error?.config?.headers
|
|
528
536
|
});
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
maxRetries,
|
|
543
|
-
// 如果是 axios 错误,通常会有 config 和 request 信息
|
|
544
|
-
url: error?.config?.url,
|
|
545
|
-
method: error?.config?.method,
|
|
546
|
-
headers: error?.config?.headers
|
|
547
|
-
});
|
|
548
|
-
if (error?.cause?.code === "UND_ERR_SOCKET" || error?.message?.includes("other side closed")) {
|
|
549
|
-
throw new Error("图像处理失败:服务器连接中断,可能是服务器负载过高或网络不稳定,请稍后重试");
|
|
550
|
-
}
|
|
551
|
-
if (error?.message?.includes("fetch") && error?.message?.includes(GPTGOD_DEFAULT_API_URL)) {
|
|
552
|
-
throw new Error("图像处理失败:无法连接 GPTGod API 服务器,请检查网络连接或稍后重试");
|
|
553
|
-
}
|
|
554
|
-
if (error?.response?.status === 413) {
|
|
555
|
-
throw new Error("图像处理失败:请求体过大,请尝试使用较小的图片");
|
|
537
|
+
if (error?.cause?.code === "UND_ERR_SOCKET" || error?.message?.includes("other side closed")) {
|
|
538
|
+
throw new Error("图像处理失败:服务器连接中断,可能是服务器负载过高或网络不稳定,请稍后重试");
|
|
539
|
+
}
|
|
540
|
+
if (error?.message?.includes("fetch") && error?.message?.includes(GPTGOD_DEFAULT_API_URL)) {
|
|
541
|
+
throw new Error("图像处理失败:无法连接 GPTGod API 服务器,请检查网络连接或稍后重试");
|
|
542
|
+
}
|
|
543
|
+
if (error?.response?.status === 413) {
|
|
544
|
+
throw new Error("图像处理失败:请求体过大,请尝试使用较小的图片");
|
|
545
|
+
}
|
|
546
|
+
if (error?.response?.status === 429) {
|
|
547
|
+
throw new Error("图像处理失败:请求过于频繁,请稍后重试");
|
|
548
|
+
}
|
|
549
|
+
throw new Error("图像处理API调用失败");
|
|
556
550
|
}
|
|
557
|
-
|
|
558
|
-
|
|
551
|
+
}
|
|
552
|
+
if (!success) {
|
|
553
|
+
if (allImages.length > 0) {
|
|
554
|
+
logger.warn("部分图片生成失败,返回已生成的图片", { generated: allImages.length, requested: numImages });
|
|
555
|
+
break;
|
|
559
556
|
}
|
|
560
|
-
throw
|
|
557
|
+
throw lastError;
|
|
561
558
|
}
|
|
562
559
|
}
|
|
563
|
-
|
|
560
|
+
return allImages;
|
|
564
561
|
}
|
|
565
562
|
};
|
|
566
563
|
|
|
@@ -886,7 +883,7 @@ function apply(ctx, config) {
|
|
|
886
883
|
rateLimitMap.set(userId, userTimestamps);
|
|
887
884
|
}
|
|
888
885
|
__name(updateRateLimit, "updateRateLimit");
|
|
889
|
-
async function checkDailyLimit(userId) {
|
|
886
|
+
async function checkDailyLimit(userId, numImages = 1) {
|
|
890
887
|
if (isAdmin(userId)) {
|
|
891
888
|
return { allowed: true, isAdmin: true };
|
|
892
889
|
}
|
|
@@ -897,25 +894,33 @@ function apply(ctx, config) {
|
|
|
897
894
|
const usersData = await loadUsersData();
|
|
898
895
|
const userData = usersData[userId];
|
|
899
896
|
if (!userData) {
|
|
897
|
+
if (numImages > config.dailyFreeLimit) {
|
|
898
|
+
return {
|
|
899
|
+
allowed: false,
|
|
900
|
+
message: `生成 ${numImages} 张图片需要 ${numImages} 次可用次数,但您的可用次数不足(今日免费:${config.dailyFreeLimit}次,充值:0次)`,
|
|
901
|
+
isAdmin: false
|
|
902
|
+
};
|
|
903
|
+
}
|
|
900
904
|
return { allowed: true, isAdmin: false };
|
|
901
905
|
}
|
|
902
906
|
const today = (/* @__PURE__ */ new Date()).toDateString();
|
|
903
907
|
const lastReset = new Date(userData.lastDailyReset || userData.createdAt).toDateString();
|
|
908
|
+
let dailyCount = userData.dailyUsageCount;
|
|
904
909
|
if (today !== lastReset) {
|
|
910
|
+
dailyCount = 0;
|
|
905
911
|
userData.dailyUsageCount = 0;
|
|
906
912
|
userData.lastDailyReset = (/* @__PURE__ */ new Date()).toISOString();
|
|
907
913
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
914
|
+
const remainingToday = Math.max(0, config.dailyFreeLimit - dailyCount);
|
|
915
|
+
const totalAvailable = remainingToday + userData.remainingPurchasedCount;
|
|
916
|
+
if (totalAvailable < numImages) {
|
|
917
|
+
return {
|
|
918
|
+
allowed: false,
|
|
919
|
+
message: `生成 ${numImages} 张图片需要 ${numImages} 次可用次数,但您的可用次数不足(今日免费剩余:${remainingToday}次,充值剩余:${userData.remainingPurchasedCount}次,共${totalAvailable}次)`,
|
|
920
|
+
isAdmin: false
|
|
921
|
+
};
|
|
913
922
|
}
|
|
914
|
-
return {
|
|
915
|
-
allowed: false,
|
|
916
|
-
message: `今日免费次数已用完(${config.dailyFreeLimit}次),充值次数也已用完。请联系管理员充值或明天再试`,
|
|
917
|
-
isAdmin: false
|
|
918
|
-
};
|
|
923
|
+
return { allowed: true, isAdmin: false };
|
|
919
924
|
}
|
|
920
925
|
__name(checkDailyLimit, "checkDailyLimit");
|
|
921
926
|
async function getPromptInput(session, message) {
|
|
@@ -1005,7 +1010,7 @@ function apply(ctx, config) {
|
|
|
1005
1010
|
return usersData[userId];
|
|
1006
1011
|
}
|
|
1007
1012
|
__name(getUserData, "getUserData");
|
|
1008
|
-
async function updateUserData(userId, userName, commandName) {
|
|
1013
|
+
async function updateUserData(userId, userName, commandName, numImages = 1) {
|
|
1009
1014
|
const usersData = await loadUsersData();
|
|
1010
1015
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1011
1016
|
const today = (/* @__PURE__ */ new Date()).toDateString();
|
|
@@ -1013,8 +1018,8 @@ function apply(ctx, config) {
|
|
|
1013
1018
|
usersData[userId] = {
|
|
1014
1019
|
userId,
|
|
1015
1020
|
userName: userId,
|
|
1016
|
-
totalUsageCount:
|
|
1017
|
-
dailyUsageCount:
|
|
1021
|
+
totalUsageCount: numImages,
|
|
1022
|
+
dailyUsageCount: numImages,
|
|
1018
1023
|
lastDailyReset: now,
|
|
1019
1024
|
purchasedCount: 0,
|
|
1020
1025
|
remainingPurchasedCount: 0,
|
|
@@ -1024,35 +1029,49 @@ function apply(ctx, config) {
|
|
|
1024
1029
|
createdAt: now
|
|
1025
1030
|
};
|
|
1026
1031
|
await saveUsersData(usersData);
|
|
1027
|
-
return { userData: usersData[userId], consumptionType: "free" };
|
|
1032
|
+
return { userData: usersData[userId], consumptionType: "free", freeUsed: numImages, purchasedUsed: 0 };
|
|
1028
1033
|
}
|
|
1029
|
-
usersData[userId].totalUsageCount +=
|
|
1034
|
+
usersData[userId].totalUsageCount += numImages;
|
|
1030
1035
|
usersData[userId].lastUsed = now;
|
|
1031
1036
|
const lastReset = new Date(usersData[userId].lastDailyReset || usersData[userId].createdAt).toDateString();
|
|
1032
1037
|
if (today !== lastReset) {
|
|
1033
1038
|
usersData[userId].dailyUsageCount = 0;
|
|
1034
1039
|
usersData[userId].lastDailyReset = now;
|
|
1035
1040
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
usersData[userId].
|
|
1043
|
-
|
|
1044
|
-
|
|
1041
|
+
let remainingToConsume = numImages;
|
|
1042
|
+
let freeUsed = 0;
|
|
1043
|
+
let purchasedUsed = 0;
|
|
1044
|
+
const availableFree = Math.max(0, config.dailyFreeLimit - usersData[userId].dailyUsageCount);
|
|
1045
|
+
if (availableFree > 0) {
|
|
1046
|
+
const freeToUse = Math.min(availableFree, remainingToConsume);
|
|
1047
|
+
usersData[userId].dailyUsageCount += freeToUse;
|
|
1048
|
+
freeUsed = freeToUse;
|
|
1049
|
+
remainingToConsume -= freeToUse;
|
|
1050
|
+
}
|
|
1051
|
+
if (remainingToConsume > 0) {
|
|
1052
|
+
const purchasedToUse = Math.min(usersData[userId].remainingPurchasedCount, remainingToConsume);
|
|
1053
|
+
usersData[userId].remainingPurchasedCount -= purchasedToUse;
|
|
1054
|
+
purchasedUsed = purchasedToUse;
|
|
1055
|
+
remainingToConsume -= purchasedToUse;
|
|
1045
1056
|
}
|
|
1046
1057
|
await saveUsersData(usersData);
|
|
1047
|
-
|
|
1058
|
+
let consumptionType;
|
|
1059
|
+
if (freeUsed > 0 && purchasedUsed > 0) {
|
|
1060
|
+
consumptionType = "mixed";
|
|
1061
|
+
} else if (freeUsed > 0) {
|
|
1062
|
+
consumptionType = "free";
|
|
1063
|
+
} else {
|
|
1064
|
+
consumptionType = "purchased";
|
|
1065
|
+
}
|
|
1066
|
+
return { userData: usersData[userId], consumptionType, freeUsed, purchasedUsed };
|
|
1048
1067
|
}
|
|
1049
1068
|
__name(updateUserData, "updateUserData");
|
|
1050
|
-
async function recordUserUsage(session, commandName) {
|
|
1069
|
+
async function recordUserUsage(session, commandName, numImages = 1) {
|
|
1051
1070
|
const userId = session.userId;
|
|
1052
1071
|
const userName = session.username || session.userId || "未知用户";
|
|
1053
1072
|
if (!userId) return;
|
|
1054
1073
|
updateRateLimit(userId);
|
|
1055
|
-
const { userData, consumptionType } = await updateUserData(userId, userName, commandName);
|
|
1074
|
+
const { userData, consumptionType, freeUsed, purchasedUsed } = await updateUserData(userId, userName, commandName, numImages);
|
|
1056
1075
|
if (isAdmin(userId)) {
|
|
1057
1076
|
await session.send(`📊 使用统计 [管理员]
|
|
1058
1077
|
用户:${userData.userName}
|
|
@@ -1060,10 +1079,18 @@ function apply(ctx, config) {
|
|
|
1060
1079
|
状态:无限制使用`);
|
|
1061
1080
|
} else {
|
|
1062
1081
|
const remainingToday = Math.max(0, config.dailyFreeLimit - userData.dailyUsageCount);
|
|
1063
|
-
|
|
1082
|
+
let consumptionText = "";
|
|
1083
|
+
if (consumptionType === "mixed") {
|
|
1084
|
+
consumptionText = `每日免费次数 -${freeUsed},充值次数 -${purchasedUsed}`;
|
|
1085
|
+
} else if (consumptionType === "free") {
|
|
1086
|
+
consumptionText = `每日免费次数 -${freeUsed}`;
|
|
1087
|
+
} else {
|
|
1088
|
+
consumptionText = `充值次数 -${purchasedUsed}`;
|
|
1089
|
+
}
|
|
1064
1090
|
await session.send(`📊 使用统计
|
|
1065
1091
|
用户:${userData.userName}
|
|
1066
|
-
|
|
1092
|
+
本次生成:${numImages}张图片
|
|
1093
|
+
本次消费:${consumptionText}
|
|
1067
1094
|
总调用次数:${userData.totalUsageCount}次
|
|
1068
1095
|
今日剩余免费:${remainingToday}次
|
|
1069
1096
|
充值剩余:${userData.remainingPurchasedCount}次`);
|
|
@@ -1072,10 +1099,13 @@ function apply(ctx, config) {
|
|
|
1072
1099
|
userId,
|
|
1073
1100
|
userName: userData.userName,
|
|
1074
1101
|
commandName,
|
|
1102
|
+
numImages,
|
|
1103
|
+
consumptionType,
|
|
1104
|
+
freeUsed,
|
|
1105
|
+
purchasedUsed,
|
|
1075
1106
|
totalUsageCount: userData.totalUsageCount,
|
|
1076
1107
|
dailyUsageCount: userData.dailyUsageCount,
|
|
1077
1108
|
remainingPurchasedCount: userData.remainingPurchasedCount,
|
|
1078
|
-
consumptionType,
|
|
1079
1109
|
isAdmin: isAdmin(userId)
|
|
1080
1110
|
});
|
|
1081
1111
|
}
|
|
@@ -1210,7 +1240,7 @@ ${infoParts.join("\n")}`;
|
|
|
1210
1240
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1211
1241
|
}
|
|
1212
1242
|
}
|
|
1213
|
-
await recordUserUsage(session, styleName);
|
|
1243
|
+
await recordUserUsage(session, styleName, images.length);
|
|
1214
1244
|
activeTasks.delete(userId);
|
|
1215
1245
|
} catch (error) {
|
|
1216
1246
|
activeTasks.delete(userId);
|
|
@@ -1228,10 +1258,6 @@ ${infoParts.join("\n")}`;
|
|
|
1228
1258
|
ctx.command(`${style.commandName} [img:text]`, "图像风格转换").option("num", "-n <num:number> 生成图片数量 (1-4)").action(async (argv, img) => {
|
|
1229
1259
|
const { session, options } = argv;
|
|
1230
1260
|
if (!session?.userId) return "会话无效";
|
|
1231
|
-
const limitCheck = await checkDailyLimit(session.userId);
|
|
1232
|
-
if (!limitCheck.allowed) {
|
|
1233
|
-
return limitCheck.message;
|
|
1234
|
-
}
|
|
1235
1261
|
const modifiers = parseStyleCommandModifiers(argv, img);
|
|
1236
1262
|
let userPromptParts = [];
|
|
1237
1263
|
if (modifiers.customAdditions?.length) {
|
|
@@ -1253,13 +1279,18 @@ ${infoParts.join("\n")}`;
|
|
|
1253
1279
|
}
|
|
1254
1280
|
}
|
|
1255
1281
|
}
|
|
1282
|
+
const numImages = options?.num || promptNumImages || config.defaultNumImages;
|
|
1283
|
+
const limitCheck = await checkDailyLimit(session.userId, numImages);
|
|
1284
|
+
if (!limitCheck.allowed) {
|
|
1285
|
+
return limitCheck.message;
|
|
1286
|
+
}
|
|
1256
1287
|
const promptSegments = [style.prompt];
|
|
1257
1288
|
if (cleanedUserPrompt) {
|
|
1258
1289
|
promptSegments.push(cleanedUserPrompt);
|
|
1259
1290
|
}
|
|
1260
1291
|
const mergedPrompt = promptSegments.filter(Boolean).join(" - ");
|
|
1261
1292
|
const requestContext = {
|
|
1262
|
-
numImages
|
|
1293
|
+
numImages
|
|
1263
1294
|
};
|
|
1264
1295
|
if (modifiers.modelMapping?.provider) {
|
|
1265
1296
|
requestContext.provider = modifiers.modelMapping.provider;
|
|
@@ -1283,10 +1314,6 @@ ${infoParts.join("\n")}`;
|
|
|
1283
1314
|
}
|
|
1284
1315
|
ctx.command(COMMANDS.GENERATE_IMAGE, "使用自定义prompt进行图像处理").option("num", "-n <num:number> 生成图片数量 (1-4)").action(async ({ session, options }) => {
|
|
1285
1316
|
if (!session?.userId) return "会话无效";
|
|
1286
|
-
const limitCheck = await checkDailyLimit(session.userId);
|
|
1287
|
-
if (!limitCheck.allowed) {
|
|
1288
|
-
return limitCheck.message;
|
|
1289
|
-
}
|
|
1290
1317
|
return Promise.race([
|
|
1291
1318
|
(async () => {
|
|
1292
1319
|
const userId = session.userId;
|
|
@@ -1353,6 +1380,10 @@ ${infoParts.join("\n")}`;
|
|
|
1353
1380
|
if (imageCount < 1 || imageCount > 4) {
|
|
1354
1381
|
return "生成数量必须在 1-4 之间";
|
|
1355
1382
|
}
|
|
1383
|
+
const limitCheck = await checkDailyLimit(userId, imageCount);
|
|
1384
|
+
if (!limitCheck.allowed) {
|
|
1385
|
+
return limitCheck.message;
|
|
1386
|
+
}
|
|
1356
1387
|
logger.info("开始自定义图像处理", {
|
|
1357
1388
|
userId,
|
|
1358
1389
|
imageUrl,
|
|
@@ -1375,7 +1406,7 @@ Prompt: ${prompt}`);
|
|
|
1375
1406
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1376
1407
|
}
|
|
1377
1408
|
}
|
|
1378
|
-
await recordUserUsage(session, COMMANDS.GENERATE_IMAGE);
|
|
1409
|
+
await recordUserUsage(session, COMMANDS.GENERATE_IMAGE, resultImages.length);
|
|
1379
1410
|
activeTasks.delete(userId);
|
|
1380
1411
|
} catch (error) {
|
|
1381
1412
|
activeTasks.delete(userId);
|
|
@@ -1398,10 +1429,6 @@ Prompt: ${prompt}`);
|
|
|
1398
1429
|
});
|
|
1399
1430
|
ctx.command(COMMANDS.COMPOSE_IMAGE, "合成多张图片,使用自定义prompt控制合成效果").option("num", "-n <num:number> 生成图片数量 (1-4)").action(async ({ session, options }) => {
|
|
1400
1431
|
if (!session?.userId) return "会话无效";
|
|
1401
|
-
const limitCheck = await checkDailyLimit(session.userId);
|
|
1402
|
-
if (!limitCheck.allowed) {
|
|
1403
|
-
return limitCheck.message;
|
|
1404
|
-
}
|
|
1405
1432
|
return Promise.race([
|
|
1406
1433
|
(async () => {
|
|
1407
1434
|
const userId = session.userId;
|
|
@@ -1458,6 +1485,10 @@ Prompt: ${prompt}`);
|
|
|
1458
1485
|
if (imageCount < 1 || imageCount > 4) {
|
|
1459
1486
|
return "生成数量必须在 1-4 之间";
|
|
1460
1487
|
}
|
|
1488
|
+
const limitCheck = await checkDailyLimit(userId, imageCount);
|
|
1489
|
+
if (!limitCheck.allowed) {
|
|
1490
|
+
return limitCheck.message;
|
|
1491
|
+
}
|
|
1461
1492
|
logger.info("开始图片合成处理", {
|
|
1462
1493
|
userId,
|
|
1463
1494
|
imageUrls: collectedImages,
|
|
@@ -1481,7 +1512,7 @@ Prompt: ${prompt}`);
|
|
|
1481
1512
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1482
1513
|
}
|
|
1483
1514
|
}
|
|
1484
|
-
await recordUserUsage(session, COMMANDS.COMPOSE_IMAGE);
|
|
1515
|
+
await recordUserUsage(session, COMMANDS.COMPOSE_IMAGE, resultImages.length);
|
|
1485
1516
|
activeTasks.delete(userId);
|
|
1486
1517
|
} catch (error) {
|
|
1487
1518
|
activeTasks.delete(userId);
|