koishi-plugin-aka-ai-generator 0.0.1 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +67 -0
- package/lib/index.js +1356 -0
- package/lib/providers/gptgod.d.ts +10 -0
- package/lib/providers/index.d.ts +20 -0
- package/lib/providers/types.d.ts +16 -0
- package/lib/providers/yunwu.d.ts +10 -0
- package/package.json +2 -2
package/lib/index.js
ADDED
|
@@ -0,0 +1,1356 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name2 in all)
|
|
8
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
Config: () => Config,
|
|
24
|
+
apply: () => apply,
|
|
25
|
+
name: () => name
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(src_exports);
|
|
28
|
+
var import_koishi = require("koishi");
|
|
29
|
+
var import_fs = require("fs");
|
|
30
|
+
var import_path = require("path");
|
|
31
|
+
|
|
32
|
+
// src/providers/yunwu.ts
|
|
33
|
+
async function downloadImageAsBase64(ctx, url, timeout, logger) {
|
|
34
|
+
try {
|
|
35
|
+
const response = await ctx.http.get(url, {
|
|
36
|
+
responseType: "arraybuffer",
|
|
37
|
+
timeout: timeout * 1e3
|
|
38
|
+
});
|
|
39
|
+
const buffer = Buffer.from(response);
|
|
40
|
+
const base64 = buffer.toString("base64");
|
|
41
|
+
let mimeType = "image/jpeg";
|
|
42
|
+
const contentType = response.headers?.["content-type"] || response.headers?.["Content-Type"];
|
|
43
|
+
if (contentType && contentType.startsWith("image/")) {
|
|
44
|
+
mimeType = contentType;
|
|
45
|
+
} else {
|
|
46
|
+
const urlLower = url.toLowerCase();
|
|
47
|
+
if (urlLower.endsWith(".png")) {
|
|
48
|
+
mimeType = "image/png";
|
|
49
|
+
} else if (urlLower.endsWith(".webp")) {
|
|
50
|
+
mimeType = "image/webp";
|
|
51
|
+
} else if (urlLower.endsWith(".gif")) {
|
|
52
|
+
mimeType = "image/gif";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
logger.debug("图片下载并转换为Base64", { url, mimeType, size: base64.length });
|
|
56
|
+
return { data: base64, mimeType };
|
|
57
|
+
} catch (error) {
|
|
58
|
+
logger.error("下载图片失败", { url, error });
|
|
59
|
+
throw new Error("下载图片失败,请检查图片链接是否有效");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
__name(downloadImageAsBase64, "downloadImageAsBase64");
|
|
63
|
+
function parseYunwuResponse(response) {
|
|
64
|
+
try {
|
|
65
|
+
const images = [];
|
|
66
|
+
if (response.candidates && response.candidates.length > 0) {
|
|
67
|
+
for (const candidate of response.candidates) {
|
|
68
|
+
if (candidate.content && candidate.content.parts) {
|
|
69
|
+
for (const part of candidate.content.parts) {
|
|
70
|
+
if (part.inlineData && part.inlineData.data) {
|
|
71
|
+
const base64Data = part.inlineData.data;
|
|
72
|
+
const mimeType = part.inlineData.mimeType || "image/jpeg";
|
|
73
|
+
const dataUrl = `data:${mimeType};base64,${base64Data}`;
|
|
74
|
+
images.push(dataUrl);
|
|
75
|
+
} else if (part.inline_data && part.inline_data.data) {
|
|
76
|
+
const base64Data = part.inline_data.data;
|
|
77
|
+
const mimeType = part.inline_data.mime_type || "image/jpeg";
|
|
78
|
+
const dataUrl = `data:${mimeType};base64,${base64Data}`;
|
|
79
|
+
images.push(dataUrl);
|
|
80
|
+
} else if (part.fileData && part.fileData.fileUri) {
|
|
81
|
+
images.push(part.fileData.fileUri);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return images;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
__name(parseYunwuResponse, "parseYunwuResponse");
|
|
93
|
+
var YunwuProvider = class {
|
|
94
|
+
static {
|
|
95
|
+
__name(this, "YunwuProvider");
|
|
96
|
+
}
|
|
97
|
+
config;
|
|
98
|
+
constructor(config) {
|
|
99
|
+
this.config = config;
|
|
100
|
+
}
|
|
101
|
+
async generateImages(prompt, imageUrls, numImages) {
|
|
102
|
+
const urls = Array.isArray(imageUrls) ? imageUrls : [imageUrls];
|
|
103
|
+
const logger = this.config.logger;
|
|
104
|
+
const ctx = this.config.ctx;
|
|
105
|
+
logger.debug("开始下载图片并转换为Base64", { urls });
|
|
106
|
+
const imageParts = [];
|
|
107
|
+
for (const url of urls) {
|
|
108
|
+
const { data, mimeType } = await downloadImageAsBase64(
|
|
109
|
+
ctx,
|
|
110
|
+
url,
|
|
111
|
+
this.config.apiTimeout,
|
|
112
|
+
logger
|
|
113
|
+
);
|
|
114
|
+
imageParts.push({
|
|
115
|
+
inline_data: {
|
|
116
|
+
mime_type: mimeType,
|
|
117
|
+
data
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const allImages = [];
|
|
122
|
+
for (let i = 0; i < numImages; i++) {
|
|
123
|
+
const requestData = {
|
|
124
|
+
contents: [
|
|
125
|
+
{
|
|
126
|
+
role: "user",
|
|
127
|
+
parts: [
|
|
128
|
+
{ text: prompt },
|
|
129
|
+
...imageParts
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
generationConfig: {
|
|
134
|
+
responseModalities: ["IMAGE"]
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
logger.debug("调用云雾图像编辑 API", { prompt, imageCount: urls.length, numImages, current: i + 1 });
|
|
138
|
+
try {
|
|
139
|
+
const response = await ctx.http.post(
|
|
140
|
+
`https://yunwu.ai/v1beta/models/${this.config.modelId}:generateContent`,
|
|
141
|
+
requestData,
|
|
142
|
+
{
|
|
143
|
+
headers: {
|
|
144
|
+
"Content-Type": "application/json"
|
|
145
|
+
},
|
|
146
|
+
params: {
|
|
147
|
+
key: this.config.apiKey
|
|
148
|
+
},
|
|
149
|
+
timeout: this.config.apiTimeout * 1e3
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
const images = parseYunwuResponse(response);
|
|
153
|
+
allImages.push(...images);
|
|
154
|
+
logger.success("云雾图像编辑 API 调用成功", { current: i + 1, total: numImages });
|
|
155
|
+
} catch (error) {
|
|
156
|
+
logger.error("云雾图像编辑 API 调用失败", {
|
|
157
|
+
message: error?.message || "未知错误",
|
|
158
|
+
code: error?.code,
|
|
159
|
+
status: error?.response?.status,
|
|
160
|
+
current: i + 1,
|
|
161
|
+
total: numImages
|
|
162
|
+
});
|
|
163
|
+
if (allImages.length > 0) {
|
|
164
|
+
logger.warn("部分图片生成失败,返回已生成的图片", { generated: allImages.length, requested: numImages });
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
throw new Error("图像处理API调用失败");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return allImages;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// src/providers/gptgod.ts
|
|
175
|
+
var GPTGOD_DEFAULT_API_URL = "https://api.gptgod.online/v1/chat/completions";
|
|
176
|
+
async function downloadImageAsBase642(ctx, url, timeout, logger) {
|
|
177
|
+
try {
|
|
178
|
+
const response = await ctx.http.get(url, {
|
|
179
|
+
responseType: "arraybuffer",
|
|
180
|
+
timeout: timeout * 1e3
|
|
181
|
+
});
|
|
182
|
+
const buffer = Buffer.from(response);
|
|
183
|
+
const base64 = buffer.toString("base64");
|
|
184
|
+
let mimeType = "image/jpeg";
|
|
185
|
+
const contentType = response.headers?.["content-type"] || response.headers?.["Content-Type"];
|
|
186
|
+
if (contentType && contentType.startsWith("image/")) {
|
|
187
|
+
mimeType = contentType;
|
|
188
|
+
} else {
|
|
189
|
+
const urlLower = url.toLowerCase();
|
|
190
|
+
if (urlLower.endsWith(".png")) {
|
|
191
|
+
mimeType = "image/png";
|
|
192
|
+
} else if (urlLower.endsWith(".webp")) {
|
|
193
|
+
mimeType = "image/webp";
|
|
194
|
+
} else if (urlLower.endsWith(".gif")) {
|
|
195
|
+
mimeType = "image/gif";
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (logger) {
|
|
199
|
+
logger.debug("图片下载并转换为Base64", { url, mimeType, size: base64.length });
|
|
200
|
+
}
|
|
201
|
+
return { data: base64, mimeType };
|
|
202
|
+
} catch (error) {
|
|
203
|
+
logger.error("下载图片失败", { url, error });
|
|
204
|
+
throw new Error("下载图片失败,请检查图片链接是否有效");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
__name(downloadImageAsBase642, "downloadImageAsBase64");
|
|
208
|
+
function parseGptGodResponse(response, logger) {
|
|
209
|
+
try {
|
|
210
|
+
const images = [];
|
|
211
|
+
if (Array.isArray(response.images)) {
|
|
212
|
+
if (logger) {
|
|
213
|
+
logger.debug("从 response.images 数组提取图片", { count: response.images.length });
|
|
214
|
+
}
|
|
215
|
+
return response.images;
|
|
216
|
+
}
|
|
217
|
+
if (response.image && typeof response.image === "string") {
|
|
218
|
+
if (response.image.startsWith("data:") || response.image.startsWith("http")) {
|
|
219
|
+
if (logger) {
|
|
220
|
+
logger.debug("从 response.image 提取图片");
|
|
221
|
+
}
|
|
222
|
+
images.push(response.image);
|
|
223
|
+
return images;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (response?.choices?.length > 0) {
|
|
227
|
+
const firstChoice = response.choices[0];
|
|
228
|
+
const messageContent = firstChoice.message?.content;
|
|
229
|
+
let contentText = "";
|
|
230
|
+
if (typeof messageContent === "string") {
|
|
231
|
+
contentText = messageContent;
|
|
232
|
+
} else if (Array.isArray(messageContent)) {
|
|
233
|
+
for (const part of messageContent) {
|
|
234
|
+
if (part?.type === "image_url" && part?.image_url?.url) {
|
|
235
|
+
if (logger) {
|
|
236
|
+
logger.debug("从 content 数组的 image_url 提取图片");
|
|
237
|
+
}
|
|
238
|
+
images.push(part.image_url.url);
|
|
239
|
+
} else if (part?.type === "text" && part?.text) {
|
|
240
|
+
contentText += part.text + "\n";
|
|
241
|
+
} else if (part?.text) {
|
|
242
|
+
contentText += part.text + "\n";
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} else if (messageContent?.text) {
|
|
246
|
+
contentText = messageContent.text;
|
|
247
|
+
}
|
|
248
|
+
if (images.length > 0) {
|
|
249
|
+
return images;
|
|
250
|
+
}
|
|
251
|
+
if (contentText) {
|
|
252
|
+
const mdImageRegex = /!\[.*?\]\((https?:\/\/[^\)]+)\)/g;
|
|
253
|
+
let match;
|
|
254
|
+
while ((match = mdImageRegex.exec(contentText)) !== null) {
|
|
255
|
+
images.push(match[1]);
|
|
256
|
+
}
|
|
257
|
+
if (images.length === 0) {
|
|
258
|
+
const urlRegex = /(https?:\/\/[^\s"')<>]+\.(?:png|jpg|jpeg|webp|gif|bmp))/gi;
|
|
259
|
+
let urlMatch;
|
|
260
|
+
while ((urlMatch = urlRegex.exec(contentText)) !== null) {
|
|
261
|
+
images.push(urlMatch[1]);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (images.length === 0 && contentText.trim().startsWith("http")) {
|
|
265
|
+
const trimmedUrl = contentText.trim().split(/\s/)[0];
|
|
266
|
+
if (trimmedUrl.match(/^https?:\/\//)) {
|
|
267
|
+
images.push(trimmedUrl);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const dataUrlRegex = /(data:image\/[^;]+;base64,[^\s"')<>]+)/gi;
|
|
271
|
+
let dataUrlMatch;
|
|
272
|
+
while ((dataUrlMatch = dataUrlRegex.exec(contentText)) !== null) {
|
|
273
|
+
images.push(dataUrlMatch[1]);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (images.length === 0 && firstChoice.message) {
|
|
277
|
+
if (firstChoice.message.image_url) {
|
|
278
|
+
if (logger) {
|
|
279
|
+
logger.debug("从 message.image_url 提取图片");
|
|
280
|
+
}
|
|
281
|
+
images.push(firstChoice.message.image_url);
|
|
282
|
+
}
|
|
283
|
+
if (Array.isArray(firstChoice.message.images)) {
|
|
284
|
+
if (logger) {
|
|
285
|
+
logger.debug("从 message.images 数组提取图片", { count: firstChoice.message.images.length });
|
|
286
|
+
}
|
|
287
|
+
return firstChoice.message.images;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (images.length === 0) {
|
|
292
|
+
if (response.data) {
|
|
293
|
+
if (Array.isArray(response.data)) {
|
|
294
|
+
const dataImages = response.data.filter(
|
|
295
|
+
(item) => item?.url || item?.image_url || typeof item === "string" && (item.startsWith("http") || item.startsWith("data:"))
|
|
296
|
+
).map((item) => item?.url || item?.image_url || item);
|
|
297
|
+
if (dataImages.length > 0) {
|
|
298
|
+
if (logger) {
|
|
299
|
+
logger.debug("从 response.data 数组提取图片", { count: dataImages.length });
|
|
300
|
+
}
|
|
301
|
+
return dataImages;
|
|
302
|
+
}
|
|
303
|
+
} else if (response.data.url || response.data.image_url) {
|
|
304
|
+
if (logger) {
|
|
305
|
+
logger.debug("从 response.data 提取图片");
|
|
306
|
+
}
|
|
307
|
+
images.push(response.data.url || response.data.image_url);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (response.result) {
|
|
311
|
+
if (Array.isArray(response.result)) {
|
|
312
|
+
const resultImages = response.result.filter(
|
|
313
|
+
(item) => item?.url || item?.image_url || typeof item === "string" && (item.startsWith("http") || item.startsWith("data:"))
|
|
314
|
+
).map((item) => item?.url || item?.image_url || item);
|
|
315
|
+
if (resultImages.length > 0) {
|
|
316
|
+
if (logger) {
|
|
317
|
+
logger.debug("从 response.result 数组提取图片", { count: resultImages.length });
|
|
318
|
+
}
|
|
319
|
+
return resultImages;
|
|
320
|
+
}
|
|
321
|
+
} else if (typeof response.result === "string" && (response.result.startsWith("http") || response.result.startsWith("data:"))) {
|
|
322
|
+
if (logger) {
|
|
323
|
+
logger.debug("从 response.result 提取图片");
|
|
324
|
+
}
|
|
325
|
+
images.push(response.result);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (images.length > 0) {
|
|
330
|
+
if (logger) {
|
|
331
|
+
logger.debug("成功提取图片", { count: images.length });
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
if (logger) {
|
|
335
|
+
logger.warn("未能从响应中提取图片", {
|
|
336
|
+
responseStructure: {
|
|
337
|
+
hasChoices: !!response?.choices,
|
|
338
|
+
hasImages: !!response?.images,
|
|
339
|
+
hasImage: !!response?.image,
|
|
340
|
+
hasData: !!response?.data,
|
|
341
|
+
hasResult: !!response?.result,
|
|
342
|
+
keys: Object.keys(response || {})
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return images;
|
|
348
|
+
} catch (error) {
|
|
349
|
+
logger?.error("解析响应时出错", { error });
|
|
350
|
+
return [];
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
__name(parseGptGodResponse, "parseGptGodResponse");
|
|
354
|
+
var GptGodProvider = class {
|
|
355
|
+
static {
|
|
356
|
+
__name(this, "GptGodProvider");
|
|
357
|
+
}
|
|
358
|
+
config;
|
|
359
|
+
constructor(config) {
|
|
360
|
+
this.config = config;
|
|
361
|
+
}
|
|
362
|
+
async generateImages(prompt, imageUrls, numImages) {
|
|
363
|
+
const urls = Array.isArray(imageUrls) ? imageUrls : [imageUrls];
|
|
364
|
+
const logger = this.config.logger;
|
|
365
|
+
const ctx = this.config.ctx;
|
|
366
|
+
if (!this.config.apiKey) {
|
|
367
|
+
throw new Error("GPTGod 配置不完整,请检查 API Key");
|
|
368
|
+
}
|
|
369
|
+
if (this.config.logLevel === "debug") {
|
|
370
|
+
logger.debug("调用 GPTGod 图像编辑 API", { prompt, imageCount: urls.length, numImages });
|
|
371
|
+
}
|
|
372
|
+
const contentParts = [
|
|
373
|
+
{
|
|
374
|
+
type: "text",
|
|
375
|
+
text: `${prompt}
|
|
376
|
+
请生成 ${numImages} 张图片。`
|
|
377
|
+
}
|
|
378
|
+
];
|
|
379
|
+
for (const url of urls) {
|
|
380
|
+
const { data, mimeType } = await downloadImageAsBase642(
|
|
381
|
+
ctx,
|
|
382
|
+
url,
|
|
383
|
+
this.config.apiTimeout,
|
|
384
|
+
logger
|
|
385
|
+
);
|
|
386
|
+
contentParts.push({
|
|
387
|
+
type: "image_url",
|
|
388
|
+
image_url: {
|
|
389
|
+
url: `data:${mimeType};base64,${data}`
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
const requestData = {
|
|
394
|
+
model: this.config.modelId,
|
|
395
|
+
stream: false,
|
|
396
|
+
n: numImages,
|
|
397
|
+
// 使用 n 参数指定生成数量
|
|
398
|
+
messages: [
|
|
399
|
+
{
|
|
400
|
+
role: "user",
|
|
401
|
+
content: contentParts
|
|
402
|
+
}
|
|
403
|
+
]
|
|
404
|
+
};
|
|
405
|
+
try {
|
|
406
|
+
const response = await ctx.http.post(
|
|
407
|
+
GPTGOD_DEFAULT_API_URL,
|
|
408
|
+
requestData,
|
|
409
|
+
{
|
|
410
|
+
headers: {
|
|
411
|
+
"Content-Type": "application/json",
|
|
412
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
413
|
+
},
|
|
414
|
+
timeout: this.config.apiTimeout * 1e3
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
logger.success("GPTGod 图像编辑 API 调用成功");
|
|
418
|
+
if (this.config.logLevel === "debug") {
|
|
419
|
+
logger.debug("GPTGod API 响应结构", {
|
|
420
|
+
hasChoices: !!response?.choices,
|
|
421
|
+
choicesLength: response?.choices?.length,
|
|
422
|
+
hasImages: !!response?.images,
|
|
423
|
+
hasImage: !!response?.image,
|
|
424
|
+
responseKeys: Object.keys(response || {}),
|
|
425
|
+
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"
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
const images = parseGptGodResponse(response, this.config.logLevel === "debug" ? logger : null);
|
|
429
|
+
if (images.length < numImages) {
|
|
430
|
+
const warnData = {
|
|
431
|
+
requested: numImages,
|
|
432
|
+
received: images.length
|
|
433
|
+
};
|
|
434
|
+
if (this.config.logLevel === "debug") {
|
|
435
|
+
warnData.responsePreview = JSON.stringify(response).substring(0, 500);
|
|
436
|
+
}
|
|
437
|
+
logger.warn("生成的图片数量不足", warnData);
|
|
438
|
+
}
|
|
439
|
+
return images;
|
|
440
|
+
} catch (error) {
|
|
441
|
+
logger.error("GPTGod 图像编辑 API 调用失败", {
|
|
442
|
+
message: error?.message || "未知错误",
|
|
443
|
+
code: error?.code,
|
|
444
|
+
status: error?.response?.status,
|
|
445
|
+
data: error?.response?.data
|
|
446
|
+
});
|
|
447
|
+
throw new Error("图像处理API调用失败");
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// src/providers/index.ts
|
|
453
|
+
function createImageProvider(config) {
|
|
454
|
+
switch (config.provider) {
|
|
455
|
+
case "yunwu":
|
|
456
|
+
return new YunwuProvider({
|
|
457
|
+
apiKey: config.yunwuApiKey,
|
|
458
|
+
modelId: config.yunwuModelId,
|
|
459
|
+
apiTimeout: config.apiTimeout,
|
|
460
|
+
logLevel: config.logLevel,
|
|
461
|
+
logger: config.logger,
|
|
462
|
+
ctx: config.ctx
|
|
463
|
+
});
|
|
464
|
+
case "gptgod":
|
|
465
|
+
return new GptGodProvider({
|
|
466
|
+
apiKey: config.gptgodApiKey,
|
|
467
|
+
modelId: config.gptgodModelId,
|
|
468
|
+
apiTimeout: config.apiTimeout,
|
|
469
|
+
logLevel: config.logLevel,
|
|
470
|
+
logger: config.logger,
|
|
471
|
+
ctx: config.ctx
|
|
472
|
+
});
|
|
473
|
+
default:
|
|
474
|
+
throw new Error(`不支持的供应商类型: ${config.provider}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
__name(createImageProvider, "createImageProvider");
|
|
478
|
+
|
|
479
|
+
// src/index.ts
|
|
480
|
+
var name = "aka-ai-generator";
|
|
481
|
+
var COMMANDS = {
|
|
482
|
+
GENERATE_IMAGE: "生成图像",
|
|
483
|
+
COMPOSE_IMAGE: "合成图像",
|
|
484
|
+
QUERY_QUOTA: "图像额度",
|
|
485
|
+
RECHARGE: "图像充值",
|
|
486
|
+
RECHARGE_HISTORY: "图像充值记录",
|
|
487
|
+
FUNCTION_LIST: "图像功能"
|
|
488
|
+
};
|
|
489
|
+
var Config = import_koishi.Schema.intersect([
|
|
490
|
+
import_koishi.Schema.object({
|
|
491
|
+
provider: import_koishi.Schema.union([
|
|
492
|
+
import_koishi.Schema.const("yunwu").description("云雾 Gemini 服务"),
|
|
493
|
+
import_koishi.Schema.const("gptgod").description("GPTGod 服务")
|
|
494
|
+
]).default("yunwu").description("图像生成供应商"),
|
|
495
|
+
yunwuApiKey: import_koishi.Schema.string().description("云雾API密钥").role("secret").required(),
|
|
496
|
+
yunwuModelId: import_koishi.Schema.string().default("gemini-2.5-flash-image").description("云雾图像生成模型ID"),
|
|
497
|
+
gptgodApiKey: import_koishi.Schema.string().description("GPTGod API 密钥").role("secret").default(""),
|
|
498
|
+
gptgodModelId: import_koishi.Schema.string().default("nano-banana").description("GPTGod 模型ID"),
|
|
499
|
+
apiTimeout: import_koishi.Schema.number().default(120).description("API请求超时时间(秒)"),
|
|
500
|
+
commandTimeout: import_koishi.Schema.number().default(180).description("命令执行总超时时间(秒)"),
|
|
501
|
+
// 默认设置
|
|
502
|
+
defaultNumImages: import_koishi.Schema.number().default(1).min(1).max(4).description("默认生成图片数量"),
|
|
503
|
+
// 配额设置
|
|
504
|
+
dailyFreeLimit: import_koishi.Schema.number().default(5).min(1).max(100).description("每日免费调用次数"),
|
|
505
|
+
// 限流设置
|
|
506
|
+
rateLimitWindow: import_koishi.Schema.number().default(300).min(60).max(3600).description("限流时间窗口(秒)"),
|
|
507
|
+
rateLimitMax: import_koishi.Schema.number().default(3).min(1).max(20).description("限流窗口内最大调用次数"),
|
|
508
|
+
// 管理员设置
|
|
509
|
+
adminUsers: import_koishi.Schema.array(import_koishi.Schema.string()).default([]).description("管理员用户ID列表(不受每日使用限制)"),
|
|
510
|
+
// 日志级别设置
|
|
511
|
+
logLevel: import_koishi.Schema.union([
|
|
512
|
+
import_koishi.Schema.const("info").description("普通信息"),
|
|
513
|
+
import_koishi.Schema.const("debug").description("完整的debug信息")
|
|
514
|
+
]).default("info").description("日志输出详细程度")
|
|
515
|
+
}),
|
|
516
|
+
// 自定义风格命令配置
|
|
517
|
+
import_koishi.Schema.object({
|
|
518
|
+
styles: import_koishi.Schema.array(import_koishi.Schema.object({
|
|
519
|
+
commandName: import_koishi.Schema.string().required().description("命令名称(不含前缀斜杠)"),
|
|
520
|
+
commandDescription: import_koishi.Schema.string().required().description("命令描述"),
|
|
521
|
+
prompt: import_koishi.Schema.string().role("textarea", { rows: 4 }).required().description("生成 prompt"),
|
|
522
|
+
enabled: import_koishi.Schema.boolean().default(true).description("是否启用此命令")
|
|
523
|
+
})).role("table").default([
|
|
524
|
+
{
|
|
525
|
+
commandName: "变手办",
|
|
526
|
+
commandDescription: "转换为手办风格",
|
|
527
|
+
prompt: "将这张照片变成手办模型。在它后面放置一个印有图像主体的盒子,桌子上有一台电脑显示Blender建模过程。在盒子前面添加一个圆形塑料底座,角色手办站在上面。如果可能的话,将场景设置在室内",
|
|
528
|
+
enabled: true
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
commandName: "变写实",
|
|
532
|
+
commandDescription: "以真实摄影风格重建主体",
|
|
533
|
+
prompt: "请根据用户提供的图片,在保持主体身份、外观与姿态的前提下生成一张超写实的摄影作品。确保光影、皮肤质感、服饰纹理与背景环境都贴近真实世界,可以适度优化噪点与瑕疵,但不要改变主体特征或添加额外元素,整体效果需像专业摄影棚拍摄。",
|
|
534
|
+
enabled: true
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
commandName: "角色设定",
|
|
538
|
+
commandDescription: "生成人物角色设定",
|
|
539
|
+
prompt: "为我生成人物的角色设定(Character Design), 比例设定(不同身高对比、头身比等), 三视图(正面、侧面、背面), 表情设定(Expression Sheet) , 动作设定(Pose Sheet) → 各种常见姿势, 服装设定(Costume Design)",
|
|
540
|
+
enabled: true
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
commandName: "道具设定",
|
|
544
|
+
commandDescription: "生成游戏道具设定(武器、载具等)",
|
|
545
|
+
prompt: "为我生成游戏道具的完整设定(Prop/Item Design),包含以下内容:功能结构图(Functional Components)、状态变化展示(State Variations)、细节特写(Detail Close-ups)",
|
|
546
|
+
enabled: true
|
|
547
|
+
}
|
|
548
|
+
]).description("自定义风格命令配置")
|
|
549
|
+
})
|
|
550
|
+
]);
|
|
551
|
+
function apply(ctx, config) {
|
|
552
|
+
const logger = ctx.logger("aka-ai-generator");
|
|
553
|
+
const activeTasks = /* @__PURE__ */ new Map();
|
|
554
|
+
const rateLimitMap = /* @__PURE__ */ new Map();
|
|
555
|
+
const imageProvider = createImageProvider({
|
|
556
|
+
provider: config.provider,
|
|
557
|
+
yunwuApiKey: config.yunwuApiKey,
|
|
558
|
+
yunwuModelId: config.yunwuModelId,
|
|
559
|
+
gptgodApiKey: config.gptgodApiKey,
|
|
560
|
+
gptgodModelId: config.gptgodModelId,
|
|
561
|
+
apiTimeout: config.apiTimeout,
|
|
562
|
+
logLevel: config.logLevel,
|
|
563
|
+
logger,
|
|
564
|
+
ctx
|
|
565
|
+
});
|
|
566
|
+
function getStyleCommands() {
|
|
567
|
+
if (!config.styles || !Array.isArray(config.styles)) return [];
|
|
568
|
+
return config.styles.filter((style) => style.enabled && style.commandName && style.prompt).map((style) => ({
|
|
569
|
+
name: style.commandName,
|
|
570
|
+
description: style.commandDescription || "图像风格转换"
|
|
571
|
+
}));
|
|
572
|
+
}
|
|
573
|
+
__name(getStyleCommands, "getStyleCommands");
|
|
574
|
+
const commandRegistry = {
|
|
575
|
+
// 非管理员指令(包含动态风格指令)
|
|
576
|
+
userCommands: [
|
|
577
|
+
...getStyleCommands(),
|
|
578
|
+
{ name: COMMANDS.GENERATE_IMAGE, description: "使用自定义prompt进行图像处理" },
|
|
579
|
+
{ name: COMMANDS.COMPOSE_IMAGE, description: "合成多张图片,使用自定义prompt控制合成效果" },
|
|
580
|
+
{ name: COMMANDS.QUERY_QUOTA, description: "查询用户额度信息" }
|
|
581
|
+
],
|
|
582
|
+
// 管理员指令
|
|
583
|
+
adminCommands: [
|
|
584
|
+
{ name: COMMANDS.RECHARGE, description: "为用户充值次数(仅管理员)" },
|
|
585
|
+
{ name: COMMANDS.RECHARGE_HISTORY, description: "查看充值历史记录(仅管理员)" }
|
|
586
|
+
]
|
|
587
|
+
};
|
|
588
|
+
const dataDir = "./data/aka-ai-generator";
|
|
589
|
+
const dataFile = (0, import_path.join)(dataDir, "users_data.json");
|
|
590
|
+
const backupFile = (0, import_path.join)(dataDir, "users_data.json.backup");
|
|
591
|
+
const rechargeHistoryFile = (0, import_path.join)(dataDir, "recharge_history.json");
|
|
592
|
+
if (!(0, import_fs.existsSync)(dataDir)) {
|
|
593
|
+
(0, import_fs.mkdirSync)(dataDir, { recursive: true });
|
|
594
|
+
}
|
|
595
|
+
function isAdmin(userId) {
|
|
596
|
+
return config.adminUsers && config.adminUsers.includes(userId);
|
|
597
|
+
}
|
|
598
|
+
__name(isAdmin, "isAdmin");
|
|
599
|
+
function checkRateLimit(userId) {
|
|
600
|
+
const now = Date.now();
|
|
601
|
+
const userTimestamps = rateLimitMap.get(userId) || [];
|
|
602
|
+
const windowStart = now - config.rateLimitWindow * 1e3;
|
|
603
|
+
const validTimestamps = userTimestamps.filter((timestamp) => timestamp > windowStart);
|
|
604
|
+
if (validTimestamps.length >= config.rateLimitMax) {
|
|
605
|
+
return {
|
|
606
|
+
allowed: false,
|
|
607
|
+
message: `操作过于频繁,请${Math.ceil((validTimestamps[0] + config.rateLimitWindow * 1e3 - now) / 1e3)}秒后再试`
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
return { allowed: true };
|
|
611
|
+
}
|
|
612
|
+
__name(checkRateLimit, "checkRateLimit");
|
|
613
|
+
function updateRateLimit(userId) {
|
|
614
|
+
const now = Date.now();
|
|
615
|
+
const userTimestamps = rateLimitMap.get(userId) || [];
|
|
616
|
+
userTimestamps.push(now);
|
|
617
|
+
rateLimitMap.set(userId, userTimestamps);
|
|
618
|
+
}
|
|
619
|
+
__name(updateRateLimit, "updateRateLimit");
|
|
620
|
+
async function checkDailyLimit(userId) {
|
|
621
|
+
if (isAdmin(userId)) {
|
|
622
|
+
return { allowed: true, isAdmin: true };
|
|
623
|
+
}
|
|
624
|
+
const rateLimitCheck = checkRateLimit(userId);
|
|
625
|
+
if (!rateLimitCheck.allowed) {
|
|
626
|
+
return { ...rateLimitCheck, isAdmin: false };
|
|
627
|
+
}
|
|
628
|
+
const usersData = await loadUsersData();
|
|
629
|
+
const userData = usersData[userId];
|
|
630
|
+
if (!userData) {
|
|
631
|
+
return { allowed: true, isAdmin: false };
|
|
632
|
+
}
|
|
633
|
+
const today = (/* @__PURE__ */ new Date()).toDateString();
|
|
634
|
+
const lastReset = new Date(userData.lastDailyReset || userData.createdAt).toDateString();
|
|
635
|
+
if (today !== lastReset) {
|
|
636
|
+
userData.dailyUsageCount = 0;
|
|
637
|
+
userData.lastDailyReset = (/* @__PURE__ */ new Date()).toISOString();
|
|
638
|
+
}
|
|
639
|
+
if (userData.dailyUsageCount < config.dailyFreeLimit) {
|
|
640
|
+
return { allowed: true, isAdmin: false };
|
|
641
|
+
}
|
|
642
|
+
if (userData.remainingPurchasedCount > 0) {
|
|
643
|
+
return { allowed: true, isAdmin: false };
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
allowed: false,
|
|
647
|
+
message: `今日免费次数已用完(${config.dailyFreeLimit}次),充值次数也已用完。请联系管理员充值或明天再试`,
|
|
648
|
+
isAdmin: false
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
__name(checkDailyLimit, "checkDailyLimit");
|
|
652
|
+
async function getPromptInput(session, message) {
|
|
653
|
+
await session.send(message);
|
|
654
|
+
const input = await session.prompt(3e4);
|
|
655
|
+
return input || null;
|
|
656
|
+
}
|
|
657
|
+
__name(getPromptInput, "getPromptInput");
|
|
658
|
+
async function loadUsersData() {
|
|
659
|
+
try {
|
|
660
|
+
if ((0, import_fs.existsSync)(dataFile)) {
|
|
661
|
+
const data = await import_fs.promises.readFile(dataFile, "utf-8");
|
|
662
|
+
return JSON.parse(data);
|
|
663
|
+
}
|
|
664
|
+
} catch (error) {
|
|
665
|
+
logger.error("读取用户数据失败", error);
|
|
666
|
+
if ((0, import_fs.existsSync)(backupFile)) {
|
|
667
|
+
try {
|
|
668
|
+
const backupData = await import_fs.promises.readFile(backupFile, "utf-8");
|
|
669
|
+
logger.warn("从备份文件恢复用户数据");
|
|
670
|
+
return JSON.parse(backupData);
|
|
671
|
+
} catch (backupError) {
|
|
672
|
+
logger.error("备份文件也损坏,使用空数据", backupError);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return {};
|
|
677
|
+
}
|
|
678
|
+
__name(loadUsersData, "loadUsersData");
|
|
679
|
+
async function saveUsersData(data) {
|
|
680
|
+
try {
|
|
681
|
+
if ((0, import_fs.existsSync)(dataFile)) {
|
|
682
|
+
await import_fs.promises.copyFile(dataFile, backupFile);
|
|
683
|
+
}
|
|
684
|
+
await import_fs.promises.writeFile(dataFile, JSON.stringify(data, null, 2), "utf-8");
|
|
685
|
+
} catch (error) {
|
|
686
|
+
logger.error("保存用户数据失败", error);
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
__name(saveUsersData, "saveUsersData");
|
|
691
|
+
async function loadRechargeHistory() {
|
|
692
|
+
try {
|
|
693
|
+
if ((0, import_fs.existsSync)(rechargeHistoryFile)) {
|
|
694
|
+
const data = await import_fs.promises.readFile(rechargeHistoryFile, "utf-8");
|
|
695
|
+
return JSON.parse(data);
|
|
696
|
+
}
|
|
697
|
+
} catch (error) {
|
|
698
|
+
logger.error("读取充值历史失败", error);
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
version: "1.0.0",
|
|
702
|
+
lastUpdate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
703
|
+
records: []
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
__name(loadRechargeHistory, "loadRechargeHistory");
|
|
707
|
+
async function saveRechargeHistory(history) {
|
|
708
|
+
try {
|
|
709
|
+
history.lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
710
|
+
await import_fs.promises.writeFile(rechargeHistoryFile, JSON.stringify(history, null, 2), "utf-8");
|
|
711
|
+
} catch (error) {
|
|
712
|
+
logger.error("保存充值历史失败", error);
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
__name(saveRechargeHistory, "saveRechargeHistory");
|
|
717
|
+
async function getUserData(userId, userName) {
|
|
718
|
+
const usersData = await loadUsersData();
|
|
719
|
+
if (!usersData[userId]) {
|
|
720
|
+
usersData[userId] = {
|
|
721
|
+
userId,
|
|
722
|
+
userName,
|
|
723
|
+
totalUsageCount: 0,
|
|
724
|
+
dailyUsageCount: 0,
|
|
725
|
+
lastDailyReset: (/* @__PURE__ */ new Date()).toISOString(),
|
|
726
|
+
purchasedCount: 0,
|
|
727
|
+
remainingPurchasedCount: 0,
|
|
728
|
+
donationCount: 0,
|
|
729
|
+
donationAmount: 0,
|
|
730
|
+
lastUsed: (/* @__PURE__ */ new Date()).toISOString(),
|
|
731
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
732
|
+
};
|
|
733
|
+
await saveUsersData(usersData);
|
|
734
|
+
logger.info("创建新用户数据", { userId, userName });
|
|
735
|
+
}
|
|
736
|
+
return usersData[userId];
|
|
737
|
+
}
|
|
738
|
+
__name(getUserData, "getUserData");
|
|
739
|
+
async function updateUserData(userId, userName, commandName) {
|
|
740
|
+
const usersData = await loadUsersData();
|
|
741
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
742
|
+
const today = (/* @__PURE__ */ new Date()).toDateString();
|
|
743
|
+
if (!usersData[userId]) {
|
|
744
|
+
usersData[userId] = {
|
|
745
|
+
userId,
|
|
746
|
+
userName: userId,
|
|
747
|
+
totalUsageCount: 1,
|
|
748
|
+
dailyUsageCount: 1,
|
|
749
|
+
lastDailyReset: now,
|
|
750
|
+
purchasedCount: 0,
|
|
751
|
+
remainingPurchasedCount: 0,
|
|
752
|
+
donationCount: 0,
|
|
753
|
+
donationAmount: 0,
|
|
754
|
+
lastUsed: now,
|
|
755
|
+
createdAt: now
|
|
756
|
+
};
|
|
757
|
+
await saveUsersData(usersData);
|
|
758
|
+
return { userData: usersData[userId], consumptionType: "free" };
|
|
759
|
+
}
|
|
760
|
+
usersData[userId].totalUsageCount += 1;
|
|
761
|
+
usersData[userId].lastUsed = now;
|
|
762
|
+
const lastReset = new Date(usersData[userId].lastDailyReset || usersData[userId].createdAt).toDateString();
|
|
763
|
+
if (today !== lastReset) {
|
|
764
|
+
usersData[userId].dailyUsageCount = 0;
|
|
765
|
+
usersData[userId].lastDailyReset = now;
|
|
766
|
+
}
|
|
767
|
+
if (usersData[userId].dailyUsageCount < config.dailyFreeLimit) {
|
|
768
|
+
usersData[userId].dailyUsageCount += 1;
|
|
769
|
+
await saveUsersData(usersData);
|
|
770
|
+
return { userData: usersData[userId], consumptionType: "free" };
|
|
771
|
+
}
|
|
772
|
+
if (usersData[userId].remainingPurchasedCount > 0) {
|
|
773
|
+
usersData[userId].remainingPurchasedCount -= 1;
|
|
774
|
+
await saveUsersData(usersData);
|
|
775
|
+
return { userData: usersData[userId], consumptionType: "purchased" };
|
|
776
|
+
}
|
|
777
|
+
await saveUsersData(usersData);
|
|
778
|
+
return { userData: usersData[userId], consumptionType: "free" };
|
|
779
|
+
}
|
|
780
|
+
__name(updateUserData, "updateUserData");
|
|
781
|
+
async function recordUserUsage(session, commandName) {
|
|
782
|
+
const userId = session.userId;
|
|
783
|
+
const userName = session.username || session.userId || "未知用户";
|
|
784
|
+
if (!userId) return;
|
|
785
|
+
updateRateLimit(userId);
|
|
786
|
+
const { userData, consumptionType } = await updateUserData(userId, userName, commandName);
|
|
787
|
+
if (isAdmin(userId)) {
|
|
788
|
+
await session.send(`📊 使用统计 [管理员]
|
|
789
|
+
用户:${userData.userName}
|
|
790
|
+
总调用次数:${userData.totalUsageCount}次
|
|
791
|
+
状态:无限制使用`);
|
|
792
|
+
} else {
|
|
793
|
+
const remainingToday = Math.max(0, config.dailyFreeLimit - userData.dailyUsageCount);
|
|
794
|
+
const consumptionText = consumptionType === "free" ? "每日免费次数" : "充值次数";
|
|
795
|
+
await session.send(`📊 使用统计
|
|
796
|
+
用户:${userData.userName}
|
|
797
|
+
本次消费:${consumptionText} -1
|
|
798
|
+
总调用次数:${userData.totalUsageCount}次
|
|
799
|
+
今日剩余免费:${remainingToday}次
|
|
800
|
+
充值剩余:${userData.remainingPurchasedCount}次`);
|
|
801
|
+
}
|
|
802
|
+
logger.info("用户调用记录", {
|
|
803
|
+
userId,
|
|
804
|
+
userName: userData.userName,
|
|
805
|
+
commandName,
|
|
806
|
+
totalUsageCount: userData.totalUsageCount,
|
|
807
|
+
dailyUsageCount: userData.dailyUsageCount,
|
|
808
|
+
remainingPurchasedCount: userData.remainingPurchasedCount,
|
|
809
|
+
consumptionType,
|
|
810
|
+
isAdmin: isAdmin(userId)
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
__name(recordUserUsage, "recordUserUsage");
|
|
814
|
+
async function getImageUrl(img, session) {
|
|
815
|
+
let url = null;
|
|
816
|
+
if (img) {
|
|
817
|
+
url = img.attrs?.src || null;
|
|
818
|
+
if (url) {
|
|
819
|
+
if (config.logLevel === "debug") {
|
|
820
|
+
logger.debug("从命令参数获取图片", { url });
|
|
821
|
+
}
|
|
822
|
+
return url;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
let elements = session.quote?.elements;
|
|
826
|
+
if (elements) {
|
|
827
|
+
const images2 = import_koishi.h.select(elements, "img");
|
|
828
|
+
if (images2.length > 0) {
|
|
829
|
+
if (images2.length > 1) {
|
|
830
|
+
await session.send('本功能仅支持处理一张图片,检测到多张图片。如需合成多张图片请使用"合成图像"命令');
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
url = images2[0].attrs.src;
|
|
834
|
+
if (config.logLevel === "debug") {
|
|
835
|
+
logger.debug("从引用消息获取图片", { url });
|
|
836
|
+
}
|
|
837
|
+
return url;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
await session.send("请在30秒内发送一张图片");
|
|
841
|
+
const msg = await session.prompt(3e4);
|
|
842
|
+
if (!msg) {
|
|
843
|
+
await session.send("等待超时");
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
elements = import_koishi.h.parse(msg);
|
|
847
|
+
const images = import_koishi.h.select(elements, "img");
|
|
848
|
+
if (images.length === 0) {
|
|
849
|
+
await session.send("未检测到图片,请重试");
|
|
850
|
+
return null;
|
|
851
|
+
}
|
|
852
|
+
if (images.length > 1) {
|
|
853
|
+
await session.send('本功能仅支持处理一张图片,检测到多张图片。如需合成多张图片请使用"合成图像"命令');
|
|
854
|
+
return null;
|
|
855
|
+
}
|
|
856
|
+
url = images[0].attrs.src;
|
|
857
|
+
if (config.logLevel === "debug") {
|
|
858
|
+
logger.debug("从用户输入获取图片", { url });
|
|
859
|
+
}
|
|
860
|
+
return url;
|
|
861
|
+
}
|
|
862
|
+
__name(getImageUrl, "getImageUrl");
|
|
863
|
+
async function requestProviderImages(prompt, imageUrls, numImages) {
|
|
864
|
+
return await imageProvider.generateImages(prompt, imageUrls, numImages);
|
|
865
|
+
}
|
|
866
|
+
__name(requestProviderImages, "requestProviderImages");
|
|
867
|
+
async function processImageWithTimeout(session, img, prompt, styleName, numImages) {
|
|
868
|
+
return Promise.race([
|
|
869
|
+
processImage(session, img, prompt, styleName, numImages),
|
|
870
|
+
new Promise(
|
|
871
|
+
(_, reject) => setTimeout(() => reject(new Error("命令执行超时")), config.commandTimeout * 1e3)
|
|
872
|
+
)
|
|
873
|
+
]).catch((error) => {
|
|
874
|
+
const userId = session.userId;
|
|
875
|
+
if (userId) activeTasks.delete(userId);
|
|
876
|
+
logger.error("图像处理超时或失败", { userId, error });
|
|
877
|
+
return error.message === "命令执行超时" ? "图像处理超时,请重试" : "图像处理失败,请稍后重试";
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
__name(processImageWithTimeout, "processImageWithTimeout");
|
|
881
|
+
async function processImage(session, img, prompt, styleName, numImages) {
|
|
882
|
+
const userId = session.userId;
|
|
883
|
+
if (activeTasks.has(userId)) {
|
|
884
|
+
return "您有一个图像处理任务正在进行中,请等待完成";
|
|
885
|
+
}
|
|
886
|
+
const imageCount = numImages || config.defaultNumImages;
|
|
887
|
+
if (imageCount < 1 || imageCount > 4) {
|
|
888
|
+
return "生成数量必须在 1-4 之间";
|
|
889
|
+
}
|
|
890
|
+
const imageUrl = await getImageUrl(img, session);
|
|
891
|
+
if (!imageUrl) {
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
logger.info("开始图像处理", {
|
|
895
|
+
userId,
|
|
896
|
+
imageUrl,
|
|
897
|
+
styleName,
|
|
898
|
+
prompt,
|
|
899
|
+
numImages: imageCount
|
|
900
|
+
});
|
|
901
|
+
await session.send(`开始处理图片(${styleName})...`);
|
|
902
|
+
try {
|
|
903
|
+
activeTasks.set(userId, "processing");
|
|
904
|
+
const images = await requestProviderImages(prompt, imageUrl, imageCount);
|
|
905
|
+
if (images.length === 0) {
|
|
906
|
+
activeTasks.delete(userId);
|
|
907
|
+
return "图像处理失败:未能生成图片";
|
|
908
|
+
}
|
|
909
|
+
await session.send("图像处理完成!");
|
|
910
|
+
for (let i = 0; i < images.length; i++) {
|
|
911
|
+
await session.send(import_koishi.h.image(images[i]));
|
|
912
|
+
if (images.length > 1 && i < images.length - 1) {
|
|
913
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
await recordUserUsage(session, styleName);
|
|
917
|
+
activeTasks.delete(userId);
|
|
918
|
+
} catch (error) {
|
|
919
|
+
activeTasks.delete(userId);
|
|
920
|
+
logger.error("图像处理失败", { userId, error });
|
|
921
|
+
return "图像处理失败,请稍后重试";
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
__name(processImage, "processImage");
|
|
925
|
+
if (config.styles && Array.isArray(config.styles)) {
|
|
926
|
+
for (const style of config.styles) {
|
|
927
|
+
if (style.enabled && style.commandName && style.prompt) {
|
|
928
|
+
ctx.command(`${style.commandName} [img:text]`, style.commandDescription || "图像风格转换").option("num", "-n <num:number> 生成图片数量 (1-4)").action(async ({ session, options }, img) => {
|
|
929
|
+
if (!session?.userId) return "会话无效";
|
|
930
|
+
const limitCheck = await checkDailyLimit(session.userId);
|
|
931
|
+
if (!limitCheck.allowed) {
|
|
932
|
+
return limitCheck.message;
|
|
933
|
+
}
|
|
934
|
+
return processImageWithTimeout(session, img, style.prompt, style.commandName, options?.num);
|
|
935
|
+
});
|
|
936
|
+
logger.info(`已注册命令: ${style.commandName}`);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
ctx.command(COMMANDS.GENERATE_IMAGE, "使用自定义prompt进行图像处理").option("num", "-n <num:number> 生成图片数量 (1-4)").action(async ({ session, options }) => {
|
|
941
|
+
if (!session?.userId) return "会话无效";
|
|
942
|
+
const limitCheck = await checkDailyLimit(session.userId);
|
|
943
|
+
if (!limitCheck.allowed) {
|
|
944
|
+
return limitCheck.message;
|
|
945
|
+
}
|
|
946
|
+
return Promise.race([
|
|
947
|
+
(async () => {
|
|
948
|
+
const userId = session.userId;
|
|
949
|
+
if (!userId) return "会话无效";
|
|
950
|
+
if (activeTasks.has(userId)) {
|
|
951
|
+
return "您有一个图像处理任务正在进行中,请等待完成";
|
|
952
|
+
}
|
|
953
|
+
await session.send('请发送一张图片和prompt,支持两种方式:\n1. 同时发送:[图片] + prompt描述\n2. 分步发送:先发送一张图片,再发送prompt文字\n\n例如:[图片] 让这张图片变成油画风格\n\n注意:本功能仅支持处理一张图片,多张图片请使用"合成图像"命令');
|
|
954
|
+
const collectedImages = [];
|
|
955
|
+
let prompt = "";
|
|
956
|
+
while (true) {
|
|
957
|
+
const msg = await session.prompt(6e4);
|
|
958
|
+
if (!msg) {
|
|
959
|
+
return "等待超时,请重试";
|
|
960
|
+
}
|
|
961
|
+
const elements = import_koishi.h.parse(msg);
|
|
962
|
+
const images = import_koishi.h.select(elements, "img");
|
|
963
|
+
const textElements = import_koishi.h.select(elements, "text");
|
|
964
|
+
const text = textElements.map((el) => el.attrs.content).join(" ").trim();
|
|
965
|
+
if (images.length > 0) {
|
|
966
|
+
if (collectedImages.length > 0) {
|
|
967
|
+
return '本功能仅支持处理一张图片,如需合成多张图片请使用"合成图像"命令';
|
|
968
|
+
}
|
|
969
|
+
if (images.length > 1) {
|
|
970
|
+
return '本功能仅支持处理一张图片,检测到多张图片。如需合成多张图片请使用"合成图像"命令';
|
|
971
|
+
}
|
|
972
|
+
for (const img of images) {
|
|
973
|
+
collectedImages.push(img.attrs.src);
|
|
974
|
+
}
|
|
975
|
+
if (text) {
|
|
976
|
+
prompt = text;
|
|
977
|
+
break;
|
|
978
|
+
}
|
|
979
|
+
await session.send("已收到图片,请发送 prompt 描述文字");
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
if (text) {
|
|
983
|
+
if (collectedImages.length === 0) {
|
|
984
|
+
return "未检测到图片,请先发送图片";
|
|
985
|
+
}
|
|
986
|
+
prompt = text;
|
|
987
|
+
break;
|
|
988
|
+
}
|
|
989
|
+
return "未检测到有效内容,请重新发送";
|
|
990
|
+
}
|
|
991
|
+
if (collectedImages.length === 0) {
|
|
992
|
+
return "未检测到图片,请重新发送";
|
|
993
|
+
}
|
|
994
|
+
if (collectedImages.length > 1) {
|
|
995
|
+
return '本功能仅支持处理一张图片,检测到多张图片。如需合成多张图片请使用"合成图像"命令';
|
|
996
|
+
}
|
|
997
|
+
if (!prompt) {
|
|
998
|
+
return "未检测到prompt描述,请重新发送";
|
|
999
|
+
}
|
|
1000
|
+
const imageUrl = collectedImages[0];
|
|
1001
|
+
const imageCount = options?.num || config.defaultNumImages;
|
|
1002
|
+
if (imageCount < 1 || imageCount > 4) {
|
|
1003
|
+
return "生成数量必须在 1-4 之间";
|
|
1004
|
+
}
|
|
1005
|
+
logger.info("开始自定义图像处理", {
|
|
1006
|
+
userId,
|
|
1007
|
+
imageUrl,
|
|
1008
|
+
prompt,
|
|
1009
|
+
numImages: imageCount
|
|
1010
|
+
});
|
|
1011
|
+
await session.send(`开始处理图片(自定义prompt)...
|
|
1012
|
+
Prompt: ${prompt}`);
|
|
1013
|
+
try {
|
|
1014
|
+
activeTasks.set(userId, "processing");
|
|
1015
|
+
const resultImages = await requestProviderImages(prompt, imageUrl, imageCount);
|
|
1016
|
+
if (resultImages.length === 0) {
|
|
1017
|
+
activeTasks.delete(userId);
|
|
1018
|
+
return "图像处理失败:未能生成图片";
|
|
1019
|
+
}
|
|
1020
|
+
await session.send("图像处理完成!");
|
|
1021
|
+
for (let i = 0; i < resultImages.length; i++) {
|
|
1022
|
+
await session.send(import_koishi.h.image(resultImages[i]));
|
|
1023
|
+
if (resultImages.length > 1 && i < resultImages.length - 1) {
|
|
1024
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
await recordUserUsage(session, COMMANDS.GENERATE_IMAGE);
|
|
1028
|
+
activeTasks.delete(userId);
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
activeTasks.delete(userId);
|
|
1031
|
+
logger.error("自定义图像处理失败", { userId, error });
|
|
1032
|
+
return "图像处理失败,请稍后重试";
|
|
1033
|
+
}
|
|
1034
|
+
})(),
|
|
1035
|
+
new Promise(
|
|
1036
|
+
(_, reject) => setTimeout(() => reject(new Error("命令执行超时")), config.commandTimeout * 1e3)
|
|
1037
|
+
)
|
|
1038
|
+
]).catch((error) => {
|
|
1039
|
+
const userId = session.userId;
|
|
1040
|
+
if (userId) activeTasks.delete(userId);
|
|
1041
|
+
logger.error("自定义图像处理超时或失败", { userId, error });
|
|
1042
|
+
return error.message === "命令执行超时" ? "图像处理超时,请重试" : "图像处理失败,请稍后重试";
|
|
1043
|
+
});
|
|
1044
|
+
});
|
|
1045
|
+
ctx.command(COMMANDS.COMPOSE_IMAGE, "合成多张图片,使用自定义prompt控制合成效果").option("num", "-n <num:number> 生成图片数量 (1-4)").action(async ({ session, options }) => {
|
|
1046
|
+
if (!session?.userId) return "会话无效";
|
|
1047
|
+
const limitCheck = await checkDailyLimit(session.userId);
|
|
1048
|
+
if (!limitCheck.allowed) {
|
|
1049
|
+
return limitCheck.message;
|
|
1050
|
+
}
|
|
1051
|
+
return Promise.race([
|
|
1052
|
+
(async () => {
|
|
1053
|
+
const userId = session.userId;
|
|
1054
|
+
if (!userId) return "会话无效";
|
|
1055
|
+
if (activeTasks.has(userId)) {
|
|
1056
|
+
return "您有一个图像处理任务正在进行中,请等待完成";
|
|
1057
|
+
}
|
|
1058
|
+
await session.send("请发送多张图片和prompt,支持两种方式:\n1. 同时发送:[图片1] [图片2]... + prompt描述\n2. 分步发送:先发送多张图片,再发送prompt文字\n\n例如:[图片1] [图片2] 将这两张图片合成一张");
|
|
1059
|
+
const collectedImages = [];
|
|
1060
|
+
let prompt = "";
|
|
1061
|
+
while (true) {
|
|
1062
|
+
const msg = await session.prompt(6e4);
|
|
1063
|
+
if (!msg) {
|
|
1064
|
+
return "等待超时,请重试";
|
|
1065
|
+
}
|
|
1066
|
+
const elements = import_koishi.h.parse(msg);
|
|
1067
|
+
const images = import_koishi.h.select(elements, "img");
|
|
1068
|
+
const textElements = import_koishi.h.select(elements, "text");
|
|
1069
|
+
const text = textElements.map((el) => el.attrs.content).join(" ").trim();
|
|
1070
|
+
if (images.length > 0) {
|
|
1071
|
+
for (const img of images) {
|
|
1072
|
+
collectedImages.push(img.attrs.src);
|
|
1073
|
+
}
|
|
1074
|
+
if (text) {
|
|
1075
|
+
prompt = text;
|
|
1076
|
+
break;
|
|
1077
|
+
}
|
|
1078
|
+
await session.send(`已收到 ${collectedImages.length} 张图片,请继续发送图片或发送 prompt 文字`);
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
if (text) {
|
|
1082
|
+
if (collectedImages.length < 2) {
|
|
1083
|
+
return `需要至少两张图片进行合成,当前只有 ${collectedImages.length} 张图片`;
|
|
1084
|
+
}
|
|
1085
|
+
prompt = text;
|
|
1086
|
+
break;
|
|
1087
|
+
}
|
|
1088
|
+
return "未检测到有效内容,请重新发送";
|
|
1089
|
+
}
|
|
1090
|
+
if (collectedImages.length < 2) {
|
|
1091
|
+
return "需要至少两张图片进行合成,请重新发送";
|
|
1092
|
+
}
|
|
1093
|
+
if (!prompt) {
|
|
1094
|
+
return "未检测到prompt描述,请重新发送";
|
|
1095
|
+
}
|
|
1096
|
+
const imageCount = options?.num || config.defaultNumImages;
|
|
1097
|
+
if (imageCount < 1 || imageCount > 4) {
|
|
1098
|
+
return "生成数量必须在 1-4 之间";
|
|
1099
|
+
}
|
|
1100
|
+
logger.info("开始图片合成处理", {
|
|
1101
|
+
userId,
|
|
1102
|
+
imageUrls: collectedImages,
|
|
1103
|
+
prompt,
|
|
1104
|
+
numImages: imageCount,
|
|
1105
|
+
imageCount: collectedImages.length
|
|
1106
|
+
});
|
|
1107
|
+
await session.send(`开始合成图像(${collectedImages.length}张)...
|
|
1108
|
+
Prompt: ${prompt}`);
|
|
1109
|
+
try {
|
|
1110
|
+
activeTasks.set(userId, "processing");
|
|
1111
|
+
const resultImages = await requestProviderImages(prompt, collectedImages, imageCount);
|
|
1112
|
+
if (resultImages.length === 0) {
|
|
1113
|
+
activeTasks.delete(userId);
|
|
1114
|
+
return "图片合成失败:未能生成图片";
|
|
1115
|
+
}
|
|
1116
|
+
await session.send("图片合成完成!");
|
|
1117
|
+
for (let i = 0; i < resultImages.length; i++) {
|
|
1118
|
+
await session.send(import_koishi.h.image(resultImages[i]));
|
|
1119
|
+
if (resultImages.length > 1 && i < resultImages.length - 1) {
|
|
1120
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
await recordUserUsage(session, COMMANDS.COMPOSE_IMAGE);
|
|
1124
|
+
activeTasks.delete(userId);
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
activeTasks.delete(userId);
|
|
1127
|
+
logger.error("图片合成失败", { userId, error });
|
|
1128
|
+
return "图片合成失败,请稍后重试";
|
|
1129
|
+
}
|
|
1130
|
+
})(),
|
|
1131
|
+
new Promise(
|
|
1132
|
+
(_, reject) => setTimeout(() => reject(new Error("命令执行超时")), config.commandTimeout * 1e3)
|
|
1133
|
+
)
|
|
1134
|
+
]).catch((error) => {
|
|
1135
|
+
const userId = session.userId;
|
|
1136
|
+
if (userId) activeTasks.delete(userId);
|
|
1137
|
+
logger.error("图片合成超时或失败", { userId, error });
|
|
1138
|
+
return error.message === "命令执行超时" ? "图片合成超时,请重试" : "图片合成失败,请稍后重试";
|
|
1139
|
+
});
|
|
1140
|
+
});
|
|
1141
|
+
ctx.command(`${COMMANDS.RECHARGE} [content:text]`, "为用户充值次数(仅管理员)").action(async ({ session }, content) => {
|
|
1142
|
+
if (!session?.userId) return "会话无效";
|
|
1143
|
+
if (!isAdmin(session.userId)) {
|
|
1144
|
+
return "权限不足,仅管理员可操作";
|
|
1145
|
+
}
|
|
1146
|
+
const inputContent = content || await getPromptInput(session, "请输入充值信息,格式:\n@用户1 @用户2 充值次数 [备注]\n\n例如:\n@难捅一号 10 测试充值");
|
|
1147
|
+
if (!inputContent) return "输入超时或无效";
|
|
1148
|
+
const elements = import_koishi.h.parse(inputContent);
|
|
1149
|
+
const atElements = import_koishi.h.select(elements, "at");
|
|
1150
|
+
const textElements = import_koishi.h.select(elements, "text");
|
|
1151
|
+
const text = textElements.map((el) => el.attrs.content).join(" ").trim();
|
|
1152
|
+
if (atElements.length === 0) {
|
|
1153
|
+
return "未找到@用户,请使用@用户的方式";
|
|
1154
|
+
}
|
|
1155
|
+
const parts = text.split(/\s+/).filter((p) => p);
|
|
1156
|
+
if (parts.length === 0) {
|
|
1157
|
+
return "请输入充值次数";
|
|
1158
|
+
}
|
|
1159
|
+
const amount = parseInt(parts[0]);
|
|
1160
|
+
const note = parts.slice(1).join(" ") || "管理员充值";
|
|
1161
|
+
if (!amount || amount <= 0) {
|
|
1162
|
+
return "充值次数必须大于0";
|
|
1163
|
+
}
|
|
1164
|
+
const userIds = atElements.map((el) => el.attrs.id).filter(Boolean);
|
|
1165
|
+
if (userIds.length === 0) {
|
|
1166
|
+
return "未找到有效的用户,请使用@用户的方式";
|
|
1167
|
+
}
|
|
1168
|
+
try {
|
|
1169
|
+
const usersData = await loadUsersData();
|
|
1170
|
+
const rechargeHistory = await loadRechargeHistory();
|
|
1171
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1172
|
+
const recordId = `recharge_${now.replace(/[-:T.]/g, "").slice(0, 14)}_${Math.random().toString(36).substr(2, 3)}`;
|
|
1173
|
+
const targets = [];
|
|
1174
|
+
for (const userId of userIds) {
|
|
1175
|
+
if (!userId) continue;
|
|
1176
|
+
let userName = userId;
|
|
1177
|
+
if (usersData[userId]) {
|
|
1178
|
+
userName = usersData[userId].userName || userId;
|
|
1179
|
+
}
|
|
1180
|
+
if (!usersData[userId]) {
|
|
1181
|
+
usersData[userId] = {
|
|
1182
|
+
userId,
|
|
1183
|
+
userName: userId,
|
|
1184
|
+
totalUsageCount: 0,
|
|
1185
|
+
dailyUsageCount: 0,
|
|
1186
|
+
lastDailyReset: now,
|
|
1187
|
+
purchasedCount: 0,
|
|
1188
|
+
remainingPurchasedCount: 0,
|
|
1189
|
+
donationCount: 0,
|
|
1190
|
+
donationAmount: 0,
|
|
1191
|
+
lastUsed: now,
|
|
1192
|
+
createdAt: now
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
const beforeBalance = usersData[userId].remainingPurchasedCount;
|
|
1196
|
+
usersData[userId].purchasedCount += amount;
|
|
1197
|
+
usersData[userId].remainingPurchasedCount += amount;
|
|
1198
|
+
targets.push({
|
|
1199
|
+
userId,
|
|
1200
|
+
userName,
|
|
1201
|
+
amount,
|
|
1202
|
+
beforeBalance,
|
|
1203
|
+
afterBalance: usersData[userId].remainingPurchasedCount
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
await saveUsersData(usersData);
|
|
1207
|
+
const record = {
|
|
1208
|
+
id: recordId,
|
|
1209
|
+
timestamp: now,
|
|
1210
|
+
type: userIds.length > 1 ? "batch" : "single",
|
|
1211
|
+
operator: {
|
|
1212
|
+
userId: session.userId,
|
|
1213
|
+
userName: session.username || session.userId
|
|
1214
|
+
},
|
|
1215
|
+
targets,
|
|
1216
|
+
totalAmount: amount * userIds.length,
|
|
1217
|
+
note: note || "管理员充值",
|
|
1218
|
+
metadata: {}
|
|
1219
|
+
};
|
|
1220
|
+
rechargeHistory.records.push(record);
|
|
1221
|
+
await saveRechargeHistory(rechargeHistory);
|
|
1222
|
+
const userList = targets.map((t) => `${t.userName}(${t.afterBalance}次)`).join(", ");
|
|
1223
|
+
return `✅ 充值成功
|
|
1224
|
+
目标用户:${userList}
|
|
1225
|
+
充值次数:${amount}次/人
|
|
1226
|
+
总充值:${record.totalAmount}次
|
|
1227
|
+
操作员:${record.operator.userName}
|
|
1228
|
+
备注:${record.note}`;
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
logger.error("充值操作失败", error);
|
|
1231
|
+
return "充值失败,请稍后重试";
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
ctx.command(`${COMMANDS.QUERY_QUOTA} [target:text]`, "查询用户额度信息").action(async ({ session }, target) => {
|
|
1235
|
+
if (!session?.userId) return "会话无效";
|
|
1236
|
+
const userIsAdmin = isAdmin(session.userId);
|
|
1237
|
+
let targetUserId = session.userId;
|
|
1238
|
+
let targetUserName = session.username || session.userId;
|
|
1239
|
+
if (target && userIsAdmin) {
|
|
1240
|
+
const userMatch = target.match(/<at id="([^"]+)"/);
|
|
1241
|
+
if (userMatch) {
|
|
1242
|
+
targetUserId = userMatch[1];
|
|
1243
|
+
targetUserName = "目标用户";
|
|
1244
|
+
}
|
|
1245
|
+
} else if (target && !userIsAdmin) {
|
|
1246
|
+
return "权限不足,仅管理员可查询其他用户";
|
|
1247
|
+
}
|
|
1248
|
+
try {
|
|
1249
|
+
const usersData = await loadUsersData();
|
|
1250
|
+
const userData = usersData[targetUserId];
|
|
1251
|
+
if (!userData) {
|
|
1252
|
+
return `👤 用户信息
|
|
1253
|
+
用户:${targetUserName}
|
|
1254
|
+
状态:新用户
|
|
1255
|
+
今日剩余免费:${config.dailyFreeLimit}次
|
|
1256
|
+
充值剩余:0次`;
|
|
1257
|
+
}
|
|
1258
|
+
const remainingToday = Math.max(0, config.dailyFreeLimit - userData.dailyUsageCount);
|
|
1259
|
+
const totalAvailable = remainingToday + userData.remainingPurchasedCount;
|
|
1260
|
+
return `👤 用户额度信息
|
|
1261
|
+
用户:${userData.userName}
|
|
1262
|
+
今日剩余免费:${remainingToday}次
|
|
1263
|
+
充值剩余:${userData.remainingPurchasedCount}次
|
|
1264
|
+
总可用次数:${totalAvailable}次
|
|
1265
|
+
历史总调用:${userData.totalUsageCount}次
|
|
1266
|
+
历史总充值:${userData.purchasedCount}次`;
|
|
1267
|
+
} catch (error) {
|
|
1268
|
+
logger.error("查询额度失败", error);
|
|
1269
|
+
return "查询失败,请稍后重试";
|
|
1270
|
+
}
|
|
1271
|
+
});
|
|
1272
|
+
ctx.command(`${COMMANDS.RECHARGE_HISTORY} [page:number]`, "查看充值历史记录(仅管理员)").action(async ({ session }, page = 1) => {
|
|
1273
|
+
if (!session?.userId) return "会话无效";
|
|
1274
|
+
if (!isAdmin(session.userId)) {
|
|
1275
|
+
return "权限不足,仅管理员可查看充值记录";
|
|
1276
|
+
}
|
|
1277
|
+
try {
|
|
1278
|
+
const history = await loadRechargeHistory();
|
|
1279
|
+
const pageSize = 10;
|
|
1280
|
+
const totalPages = Math.ceil(history.records.length / pageSize);
|
|
1281
|
+
const startIndex = (page - 1) * pageSize;
|
|
1282
|
+
const endIndex = startIndex + pageSize;
|
|
1283
|
+
const records = history.records.slice(startIndex, endIndex).reverse();
|
|
1284
|
+
if (records.length === 0) {
|
|
1285
|
+
return `📋 充值记录
|
|
1286
|
+
当前页:${page}/${totalPages}
|
|
1287
|
+
暂无充值记录`;
|
|
1288
|
+
}
|
|
1289
|
+
let result = `📋 充值记录 (第${page}/${totalPages}页)
|
|
1290
|
+
|
|
1291
|
+
`;
|
|
1292
|
+
for (const record of records) {
|
|
1293
|
+
const date = new Date(record.timestamp).toLocaleString("zh-CN");
|
|
1294
|
+
const userList = record.targets.map((t) => `${t.userName}(${t.amount}次)`).join(", ");
|
|
1295
|
+
result += `🕐 ${date}
|
|
1296
|
+
👤 操作员:${record.operator.userName}
|
|
1297
|
+
👥 目标:${userList}
|
|
1298
|
+
💰 总充值:${record.totalAmount}次
|
|
1299
|
+
📝 备注:${record.note}
|
|
1300
|
+
|
|
1301
|
+
`;
|
|
1302
|
+
}
|
|
1303
|
+
return result;
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
logger.error("查询充值记录失败", error);
|
|
1306
|
+
return "查询失败,请稍后重试";
|
|
1307
|
+
}
|
|
1308
|
+
});
|
|
1309
|
+
ctx.command(COMMANDS.FUNCTION_LIST, "查看所有可用的图像处理功能").action(async ({ session }) => {
|
|
1310
|
+
if (!session?.userId) return "会话无效";
|
|
1311
|
+
try {
|
|
1312
|
+
const userIsAdmin = isAdmin(session.userId);
|
|
1313
|
+
let result = "🎨 图像处理功能列表\n\n";
|
|
1314
|
+
result += "📝 用户指令:\n";
|
|
1315
|
+
commandRegistry.userCommands.forEach((cmd) => {
|
|
1316
|
+
result += `• ${cmd.name} - ${cmd.description}
|
|
1317
|
+
`;
|
|
1318
|
+
});
|
|
1319
|
+
if (userIsAdmin) {
|
|
1320
|
+
result += "\n🔧 管理员指令:\n";
|
|
1321
|
+
commandRegistry.adminCommands.forEach((cmd) => {
|
|
1322
|
+
result += `• ${cmd.name} - ${cmd.description}
|
|
1323
|
+
`;
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
result += "\n💡 使用提示:\n";
|
|
1327
|
+
result += "• 发送图片后使用相应指令进行图像处理\n";
|
|
1328
|
+
result += "• 支持直接传参:.指令名 [图片] 参数\n";
|
|
1329
|
+
result += "• 支持交互式输入:.指令名 然后按提示操作\n";
|
|
1330
|
+
if (userIsAdmin) {
|
|
1331
|
+
result += "\n🔑 管理员提示:\n";
|
|
1332
|
+
result += "• 可使用所有功能,无使用限制\n";
|
|
1333
|
+
result += "• 可以查看充值记录\n";
|
|
1334
|
+
result += "• 可以为其他用户充值次数\n";
|
|
1335
|
+
} else {
|
|
1336
|
+
result += "\n👤 普通用户提示:\n";
|
|
1337
|
+
result += "• 每日有免费使用次数限制\n";
|
|
1338
|
+
result += "• 可使用充值次数进行额外调用\n";
|
|
1339
|
+
result += "• 使用 .图像额度 查看剩余次数\n";
|
|
1340
|
+
}
|
|
1341
|
+
return result;
|
|
1342
|
+
} catch (error) {
|
|
1343
|
+
logger.error("获取功能列表失败", error);
|
|
1344
|
+
return "获取功能列表失败,请稍后重试";
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
const providerLabel = config.provider === "gptgod" ? "GPTGod" : "云雾 Gemini 2.5 Flash Image";
|
|
1348
|
+
logger.info(`aka-ai-generator 插件已启动 (${providerLabel})`);
|
|
1349
|
+
}
|
|
1350
|
+
__name(apply, "apply");
|
|
1351
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1352
|
+
0 && (module.exports = {
|
|
1353
|
+
Config,
|
|
1354
|
+
apply,
|
|
1355
|
+
name
|
|
1356
|
+
});
|