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.js CHANGED
@@ -25,11 +25,9 @@ __export(src_exports, {
25
25
  name: () => name
26
26
  });
27
27
  module.exports = __toCommonJS(src_exports);
28
- var import_koishi = require("koishi");
29
- var import_fs = require("fs");
30
- var import_path = require("path");
28
+ var import_koishi2 = require("koishi");
31
29
 
32
- // src/providers/types.ts
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 === "key" || lowerKey === "authorization" || lowerKey === "token" || lowerKey === "secret" || lowerKey === "password" || lowerKey === "x-goog-api-key") {
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
- let sanitized = str.replace(/[?&]key=[^&\s"']+/gi, "?key=[REDACTED]").replace(/[?&]apikey=[^&\s"']+/gi, "&apikey=[REDACTED]").replace(/[?&]api_key=[^&\s"']+/gi, "&api_key=[REDACTED]");
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 sanitizeUrl(url) {
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
- const urlLower = url.toLowerCase();
94
- if (urlLower.endsWith(".png")) {
95
- mimeType = "image/png";
96
- } else if (urlLower.endsWith(".webp")) {
97
- mimeType = "image/webp";
98
- } else if (urlLower.endsWith(".gif")) {
99
- mimeType = "image/gif";
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 downloadImageAsBase642(ctx, url, timeout, logger);
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
- const imagePart = await buildImageContentPart(
452
- ctx,
453
- url,
454
- this.config.apiTimeout,
455
- logger
456
- );
457
- imageParts.push(imagePart);
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
- const { data, mimeType } = await downloadImageAsBase643(
792
- ctx,
793
- url,
794
- this.config.apiTimeout,
795
- logger
796
- );
797
- imageParts.push({
798
- inlineData: {
799
- mimeType,
800
- data
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
- const errorDetails = {
690
+ logger.error("Gemini API 调用失败", {
864
691
  message: safeMessage,
865
692
  code: error?.code,
866
693
  status: error?.response?.status,
867
- statusText: error?.response?.statusText,
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
- errorType: error?.name || error?.constructor?.name || "Unknown"
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 YunwuProvider({
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 = import_koishi.Schema.object({
959
- commandName: import_koishi.Schema.string().required().description("命令名称").role("table-cell", { width: 100 }),
960
- prompt: import_koishi.Schema.string().role("textarea", { rows: 4 }).required().description("生成 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 = import_koishi.Schema.intersect([
963
- import_koishi.Schema.object({
964
- provider: import_koishi.Schema.union([
965
- import_koishi.Schema.const("yunwu").description("云雾 Gemini 服务"),
966
- import_koishi.Schema.const("gptgod").description("GPTGod 服务"),
967
- import_koishi.Schema.const("gemini").description("Google Gemini 原生")
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: import_koishi.Schema.string().description("云雾API密钥").role("secret").required(),
970
- yunwuModelId: import_koishi.Schema.string().default("gemini-2.5-flash-image").description("云雾图像生成模型ID"),
971
- gptgodApiKey: import_koishi.Schema.string().description("GPTGod API 密钥").role("secret").default(""),
972
- gptgodModelId: import_koishi.Schema.string().default("nano-banana").description("GPTGod 模型ID"),
973
- geminiApiKey: import_koishi.Schema.string().description("Gemini API 密钥").role("secret").default(""),
974
- geminiModelId: import_koishi.Schema.string().default("gemini-2.5-flash").description("Gemini 模型ID"),
975
- geminiApiBase: import_koishi.Schema.string().default("https://generativelanguage.googleapis.com").description("Gemini API 基础地址"),
976
- modelMappings: import_koishi.Schema.array(import_koishi.Schema.object({
977
- suffix: import_koishi.Schema.string().required().description("指令后缀(例如 4K,对应输入 -4K)"),
978
- provider: import_koishi.Schema.union([
979
- import_koishi.Schema.const("yunwu").description("云雾 Gemini 服务"),
980
- import_koishi.Schema.const("gptgod").description("GPTGod 服务"),
981
- import_koishi.Schema.const("gemini").description("Google Gemini 原生")
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: import_koishi.Schema.string().required().description("触发该后缀时使用的模型 ID")
1199
+ modelId: import_koishi2.Schema.string().required().description("触发该后缀时使用的模型 ID")
984
1200
  })).role("table").default([]).description("根据 -后缀切换模型/供应商"),
985
- apiTimeout: import_koishi.Schema.number().default(120).description("API请求超时时间(秒)"),
986
- commandTimeout: import_koishi.Schema.number().default(180).description("命令执行总超时时间(秒)"),
1201
+ apiTimeout: import_koishi2.Schema.number().default(120).description("API请求超时时间(秒)"),
1202
+ commandTimeout: import_koishi2.Schema.number().default(180).description("命令执行总超时时间(秒)"),
987
1203
  // 默认设置
988
- defaultNumImages: import_koishi.Schema.number().default(1).min(1).max(4).description("默认生成图片数量"),
1204
+ defaultNumImages: import_koishi2.Schema.number().default(1).min(1).max(4).description("默认生成图片数量"),
989
1205
  // 配额设置
990
- dailyFreeLimit: import_koishi.Schema.number().default(5).min(1).max(100).description("每日免费调用次数"),
1206
+ dailyFreeLimit: import_koishi2.Schema.number().default(5).min(1).max(100).description("每日免费调用次数"),
991
1207
  // 限流设置
992
- rateLimitWindow: import_koishi.Schema.number().default(300).min(60).max(3600).description("限流时间窗口(秒)"),
993
- rateLimitMax: import_koishi.Schema.number().default(3).min(1).max(20).description("限流窗口内最大调用次数"),
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: import_koishi.Schema.array(import_koishi.Schema.string()).default([]).description("管理员用户ID列表(不受每日使用限制)"),
1211
+ adminUsers: import_koishi2.Schema.array(import_koishi2.Schema.string()).default([]).description("管理员用户ID列表(不受每日使用限制)"),
996
1212
  // 日志级别设置
997
- logLevel: import_koishi.Schema.union([
998
- import_koishi.Schema.const("info").description("普通信息"),
999
- import_koishi.Schema.const("debug").description("完整的debug信息")
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: import_koishi.Schema.number().default(600).min(60).max(3600).description("安全策略拦截追踪时间窗口(秒),在此时间窗口内连续触发拦截会被记录"),
1003
- securityBlockWarningThreshold: import_koishi.Schema.number().default(3).min(1).max(10).description("安全策略拦截警示阈值,连续触发此次数拦截后将发送警示消息,再次触发将被扣除积分")
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
- import_koishi.Schema.object({
1007
- styles: import_koishi.Schema.array(StyleItemSchema).role("table").default([
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
- import_koishi.Schema.object({
1019
- styleGroups: import_koishi.Schema.dict(import_koishi.Schema.object({
1020
- prompts: import_koishi.Schema.array(StyleItemSchema).role("table").default([]).description("属于该类型的 prompt 列表")
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 activeTasks = /* @__PURE__ */ new Map();
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
- const cacheKey = `${providerType}:${modelId || "default"}`;
1033
- if (!providerCache.has(cacheKey)) {
1034
- providerCache.set(cacheKey, createImageProvider({
1035
- provider: providerType,
1036
- yunwuApiKey: config.yunwuApiKey,
1037
- yunwuModelId: providerType === "yunwu" ? modelId || config.yunwuModelId : config.yunwuModelId,
1038
- gptgodApiKey: config.gptgodApiKey,
1039
- gptgodModelId: providerType === "gptgod" ? modelId || config.gptgodModelId : config.gptgodModelId,
1040
- geminiApiKey: config.geminiApiKey,
1041
- geminiModelId: providerType === "gemini" ? modelId || config.geminiModelId : config.geminiModelId,
1042
- geminiApiBase: config.geminiApiBase,
1043
- apiTimeout: config.apiTimeout,
1044
- logLevel: config.logLevel,
1045
- logger,
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 updateUserData(userId, userName, commandName, numImages);
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
- if (isAdmin(userId)) {
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
- hasWarning,
1372
+ shouldWarn,
1373
+ shouldDeduct,
1475
1374
  numImages
1476
1375
  });
1477
- if (blockCount >= config.securityBlockWarningThreshold && !hasWarning) {
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 (hasWarning) {
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 = import_koishi.h.parse(msg);
1500
- const images = import_koishi.h.select(elements, "img");
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 = import_koishi.h.select(elements, "text").map((e) => e.attrs.content).join(" ").trim();
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 = import_koishi.h.select(session.quote.elements, "img");
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 = import_koishi.h.parse(msg);
1540
- const images = import_koishi.h.select(elements, "img");
1541
- const textElements = import_koishi.h.select(elements, "text");
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(() => reject(new Error("命令执行超时")), config.commandTimeout * 1e3)
1490
+ (_, reject) => setTimeout(() => {
1491
+ isTimeout = true;
1492
+ reject(new Error("命令执行超时"));
1493
+ }, config.commandTimeout * 1e3)
1591
1494
  )
1592
1495
  ]).catch(async (error) => {
1593
- const userId = session.userId;
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 (activeTasks.has(userId)) {
1514
+ if (!userManager.startTask(userId)) {
1613
1515
  return "您有一个图像处理任务正在进行中,请等待完成";
1614
1516
  }
1615
- const imageCount = requestContext?.numImages || config.defaultNumImages;
1616
- if (imageCount < 1 || imageCount > 4) {
1617
- return "生成数量必须在 1-4 之间";
1618
- }
1619
- const inputResult = await getInputData(session, img, mode);
1620
- if ("error" in inputResult) {
1621
- return inputResult.error;
1622
- }
1623
- const { images: imageUrls, text: extraText } = inputResult;
1624
- let finalPrompt = prompt;
1625
- if (extraText) {
1626
- finalPrompt += " " + extraText;
1627
- }
1628
- finalPrompt = finalPrompt.trim();
1629
- if (!finalPrompt) {
1630
- await session.send("请发送画面描述");
1631
- const promptMsg = await session.prompt(3e4);
1632
- if (!promptMsg) {
1633
- return "未检测到描述,操作已取消";
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
- const elements = import_koishi.h.parse(promptMsg);
1636
- const images = import_koishi.h.select(elements, "img");
1637
- if (images.length > 0) {
1638
- return "检测到图片,本功能仅支持文字输入";
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
- const text = import_koishi.h.select(elements, "text").map((e) => e.attrs.content).join(" ").trim();
1641
- if (text) {
1642
- finalPrompt = text;
1643
- } else {
1644
- return "未检测到有效文字描述,操作已取消";
1568
+ if (displayInfo?.modelId) {
1569
+ const modelDesc = displayInfo.modelDescription || displayInfo.modelId;
1570
+ infoParts.push(`使用模型:${modelDesc}`);
1645
1571
  }
1646
- }
1647
- const providerType = requestContext?.provider || config.provider;
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
- statusMessage += "...";
1672
- await session.send(statusMessage);
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
- await session.send(import_koishi.h.image(images[i]));
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
- activeTasks.delete(userId);
1689
- } catch (error) {
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
- const userId = session.userId;
1785
- if (!userId) return "会话无效";
1786
- if (activeTasks.has(userId)) {
1787
- return "您有一个图像处理任务正在进行中,请等待完成";
1788
- }
1789
- await session.send("多张图片+描述");
1790
- const collectedImages = [];
1791
- let prompt = "";
1792
- while (true) {
1793
- const msg = await session.prompt(6e4);
1794
- if (!msg) {
1795
- return "等待超时,请重试";
1796
- }
1797
- const elements = import_koishi.h.parse(msg);
1798
- const images = import_koishi.h.select(elements, "img");
1799
- const textElements = import_koishi.h.select(elements, "text");
1800
- const text = textElements.map((el) => el.attrs.content).join(" ").trim();
1801
- if (images.length > 0) {
1802
- for (const img of images) {
1803
- collectedImages.push(img.attrs.src);
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
- await session.send(`已收到 ${collectedImages.length} 张图片,继续发送或输入描述`);
1810
- continue;
1714
+ return "未检测到有效内容,操作已取消";
1811
1715
  }
1812
- if (text) {
1813
- if (collectedImages.length < 2) {
1814
- return `需要至少两张图片进行合成,当前只有 ${collectedImages.length} 张图片`;
1815
- }
1816
- prompt = text;
1817
- break;
1716
+ if (collectedImages.length < 2) {
1717
+ return "需要至少两张图片进行合成,请重新发送";
1818
1718
  }
1819
- return "未检测到有效内容,操作已取消";
1820
- }
1821
- if (collectedImages.length < 2) {
1822
- return "需要至少两张图片进行合成,请重新发送";
1823
- }
1824
- if (!prompt) {
1825
- return "未检测到prompt描述,请重新发送";
1826
- }
1827
- const imageCount = options?.num || config.defaultNumImages;
1828
- if (imageCount < 1 || imageCount > 4) {
1829
- return "生成数量必须在 1-4 之间";
1830
- }
1831
- const limitCheck = await checkDailyLimit(userId, imageCount);
1832
- if (!limitCheck.allowed) {
1833
- return limitCheck.message;
1834
- }
1835
- logger.info("开始图片合成处理", {
1836
- userId,
1837
- imageUrls: collectedImages,
1838
- prompt,
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
- await session.send(import_koishi.h.image(resultImages[i]));
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
- activeTasks.delete(userId);
1860
- } catch (error) {
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(() => reject(new Error("命令执行超时")), config.commandTimeout * 1e3)
1759
+ (_, reject) => setTimeout(() => {
1760
+ isTimeout = true;
1761
+ reject(new Error("命令执行超时"));
1762
+ }, config.commandTimeout * 1e3)
1878
1763
  )
1879
1764
  ]).catch(async (error) => {
1880
- const userId = session.userId;
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 = import_koishi.h.parse(inputContent);
1904
- const atElements = import_koishi.h.select(elements, "at");
1905
- const textElements = import_koishi.h.select(elements, "text");
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
- for (const userId of userIds) {
1930
- if (!userId) continue;
1931
- let userName = userId;
1932
- if (usersData[userId]) {
1933
- userName = usersData[userId].userName || userId;
1934
- }
1935
- if (!usersData[userId]) {
1936
- usersData[userId] = {
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: userId,
1939
- totalUsageCount: 0,
1940
- dailyUsageCount: 0,
1941
- lastDailyReset: now,
1942
- purchasedCount: 0,
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
- const beforeBalance = usersData[userId].remainingPurchasedCount;
1951
- usersData[userId].purchasedCount += amount;
1952
- usersData[userId].remainingPurchasedCount += amount;
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: userIds.length > 1 ? "batch" : "single",
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: amount * userIds.length,
1972
- note: 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
- 总充值:${record.totalAmount}次
1982
- 操作员:${record.operator.userName}
1983
- 备注:${record.note}`;
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 = import_koishi.h.parse(inputContent);
1997
- const textElements = import_koishi.h.select(elements, "text");
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
- for (const userId of allUserIds) {
2019
- if (!userId) continue;
2020
- const userData = usersData[userId];
2021
- const userName = userData.userName || userId;
2022
- const beforeBalance = userData.remainingPurchasedCount;
2023
- userData.purchasedCount += amount;
2024
- userData.remainingPurchasedCount += amount;
2025
- targets.push({
2026
- userId,
2027
- userName,
2028
- amount,
2029
- beforeBalance,
2030
- afterBalance: userData.remainingPurchasedCount
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 saveUsersData(usersData);
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: amount * allUserIds.length,
2044
- note: 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
- 目标用户数:${allUserIds.length}人
1932
+ 目标用户数:${successCount}人
2051
1933
  充值次数:${amount}次/人
2052
- 总充值:${record.totalAmount}次
2053
- 操作员:${record.operator.userName}
2054
- 备注:${record.note}`;
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 usersData = await loadUsersData();
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) => {