koishi-plugin-cunapp-val 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ import { Context } from 'koishi';
2
+ export interface Config {
3
+ mongodbUrl: string;
4
+ mongodbDb: string;
5
+ Key: string;
6
+ }
7
+ export declare function applySet(ctx: Context, config: Config): void;
@@ -0,0 +1,7 @@
1
+ import { Context } from 'koishi';
2
+ export interface Config {
3
+ mongodbUrl: string;
4
+ mongodbDb: string;
5
+ Key: string;
6
+ }
7
+ export declare function applySd(ctx: Context, config: Config): void;
@@ -0,0 +1,9 @@
1
+ import { Db } from 'mongodb';
2
+ export interface Config {
3
+ mongodbUrl: string;
4
+ mongodbDb: string;
5
+ }
6
+ export declare function connectMongo(config: Config): Promise<Db>;
7
+ export declare function getDb(): Db | null;
8
+ export declare function closeMongo(): Promise<void>;
9
+ export declare function getCollection(name: string, config: Config): Promise<import("mongodb").Collection<import("bson").Document>>;
package/lib/index.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { Context, Schema } from 'koishi';
2
+ export declare const name = "val-cunapp";
3
+ export interface Config {
4
+ mongodbUrl: string;
5
+ mongodbDb: string;
6
+ Key: string;
7
+ }
8
+ export declare const Config: Schema<Config>;
9
+ export declare function apply(ctx: Context, config: Config): void;
package/lib/index.js ADDED
@@ -0,0 +1,622 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
+ var __export = (target, all) => {
9
+ for (var name2 in all)
10
+ __defProp(target, name2, { get: all[name2], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ Config: () => Config,
34
+ apply: () => apply,
35
+ name: () => name
36
+ });
37
+ module.exports = __toCommonJS(src_exports);
38
+ var import_koishi3 = require("koishi");
39
+
40
+ // src/database/index.ts
41
+ var import_mongodb = require("mongodb");
42
+ var import_koishi = require("koishi");
43
+ var logger = new import_koishi.Logger("val-cunapp/database");
44
+ var mongoClient = null;
45
+ var mongoDb = null;
46
+ async function connectMongo(config) {
47
+ try {
48
+ if (mongoClient) {
49
+ await mongoClient.close();
50
+ }
51
+ } catch (e) {
52
+ logger.debug("关闭旧连接失败,忽略");
53
+ }
54
+ mongoClient = null;
55
+ mongoDb = null;
56
+ mongoClient = new import_mongodb.MongoClient(config.mongodbUrl);
57
+ await mongoClient.connect();
58
+ mongoDb = mongoClient.db(config.mongodbDb);
59
+ return mongoDb;
60
+ }
61
+ __name(connectMongo, "connectMongo");
62
+ function getDb() {
63
+ return mongoDb;
64
+ }
65
+ __name(getDb, "getDb");
66
+ async function closeMongo() {
67
+ if (mongoClient) {
68
+ try {
69
+ await mongoClient.close();
70
+ mongoClient = null;
71
+ mongoDb = null;
72
+ } catch (e) {
73
+ logger.error("关闭连接失败:", e);
74
+ }
75
+ }
76
+ }
77
+ __name(closeMongo, "closeMongo");
78
+
79
+ // src/utils/crypto.ts
80
+ var import_crypto = __toESM(require("crypto"));
81
+ var ALGORITHM = "aes-256-gcm";
82
+ var IV_LENGTH = 16;
83
+ var TAG_LENGTH = 16;
84
+ var SALT_LENGTH = 64;
85
+ var KEY_LENGTH = 32;
86
+ var ITERATIONS = 1e5;
87
+ function getKey(salt, passphrase) {
88
+ return import_crypto.default.pbkdf2Sync(passphrase, salt, ITERATIONS, KEY_LENGTH, "sha512");
89
+ }
90
+ __name(getKey, "getKey");
91
+ function encrypt(plaintext, passphrase) {
92
+ if (!passphrase) return plaintext;
93
+ const salt = import_crypto.default.randomBytes(SALT_LENGTH);
94
+ const iv = import_crypto.default.randomBytes(IV_LENGTH);
95
+ const key = getKey(salt, passphrase);
96
+ const cipher = import_crypto.default.createCipheriv(ALGORITHM, key, iv);
97
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
98
+ const tag = cipher.getAuthTag();
99
+ return Buffer.concat([salt, iv, tag, encrypted]).toString("base64");
100
+ }
101
+ __name(encrypt, "encrypt");
102
+ function decrypt(ciphertext, passphrase) {
103
+ if (!passphrase) return ciphertext;
104
+ try {
105
+ const raw = Buffer.from(ciphertext, "base64");
106
+ const salt = raw.subarray(0, SALT_LENGTH);
107
+ const iv = raw.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
108
+ const tag = raw.subarray(SALT_LENGTH + IV_LENGTH, SALT_LENGTH + IV_LENGTH + TAG_LENGTH);
109
+ const encrypted = raw.subarray(SALT_LENGTH + IV_LENGTH + TAG_LENGTH);
110
+ const key = getKey(salt, passphrase);
111
+ const decipher = import_crypto.default.createDecipheriv(ALGORITHM, key, iv);
112
+ decipher.setAuthTag(tag);
113
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
114
+ return decrypted.toString("utf8");
115
+ } catch {
116
+ return null;
117
+ }
118
+ }
119
+ __name(decrypt, "decrypt");
120
+
121
+ // src/commands/set.ts
122
+ function getReplyAt(session) {
123
+ return session.user?.valReplyAt || "";
124
+ }
125
+ __name(getReplyAt, "getReplyAt");
126
+ function applySet(ctx, config) {
127
+ ctx.command("val.set [cookie:string]", "设置认证信息").action(async (argv) => {
128
+ const { session, args } = argv;
129
+ const prefix = getReplyAt(session);
130
+ const cookie = args[0];
131
+ if (!cookie) {
132
+ return `${prefix} 请提供cookie参数`;
133
+ }
134
+ if (!config.Key) {
135
+ return `${prefix} 请先配置加密密钥`;
136
+ }
137
+ try {
138
+ const db = getDb();
139
+ if (!db) return `${prefix} 数据库未连接`;
140
+ const encryptedCookie = config.Key ? encrypt(cookie, config.Key) : cookie;
141
+ await db.collection("val_cookies").updateOne(
142
+ { userId: session.userId },
143
+ { $set: { cookie: encryptedCookie, updatedAt: /* @__PURE__ */ new Date() } },
144
+ { upsert: true }
145
+ );
146
+ return `${prefix} Cookie设置成功`;
147
+ } catch (e) {
148
+ return `${prefix} Cookie设置失败: ${e.message}`;
149
+ }
150
+ });
151
+ }
152
+ __name(applySet, "applySet");
153
+
154
+ // src/commands/shop.ts
155
+ var import_koishi2 = require("koishi");
156
+
157
+ // src/utils/shop.ts
158
+ var import_axios = __toESM(require("axios"));
159
+ function cleanUrl(url) {
160
+ return url.replace(/`/g, "").trim();
161
+ }
162
+ __name(cleanUrl, "cleanUrl");
163
+ async function fetchShop(cookie) {
164
+ const url = "https://app.mval.qq.com/go/mlol_store/agame/user_store";
165
+ const headers = {
166
+ "Cookie": cookie,
167
+ "Content-Type": "application/json",
168
+ "Referer": "https://mval.qq.com/"
169
+ };
170
+ const body = {
171
+ scene: "",
172
+ source_game_zone: "agame",
173
+ game_zone: "agame"
174
+ };
175
+ const response = await import_axios.default.post(url, body, { headers });
176
+ const data = response.data;
177
+ data.data.forEach((store) => {
178
+ store.list.forEach((item) => {
179
+ item.goods_pic = cleanUrl(item.goods_pic);
180
+ if (item.bg_image) {
181
+ item.bg_image = cleanUrl(item.bg_image);
182
+ }
183
+ });
184
+ });
185
+ return data;
186
+ }
187
+ __name(fetchShop, "fetchShop");
188
+ async function fetchUserProfile(cookie) {
189
+ try {
190
+ const userIdMatch = cookie.match(/userId=([^;]+)/);
191
+ if (!userIdMatch) return null;
192
+ const userId = userIdMatch[1];
193
+ const url = "https://app.mval.qq.com/go/user_profile/query/user";
194
+ const headers = {
195
+ "Cookie": cookie,
196
+ "Content-Type": "application/json",
197
+ "Referer": "https://mval.qq.com/"
198
+ };
199
+ const body = {
200
+ opUuid: userId,
201
+ isNeedGameInfo: 1,
202
+ isNeedMedal: 1,
203
+ isNeedDress: 1,
204
+ clientType: 10,
205
+ isNeedCommunityInfo: 1,
206
+ isNeedMainInfo: 1,
207
+ source_game_zone: "agame",
208
+ game_zone: "agame",
209
+ uuidSceneList: [{ uuid: userId }],
210
+ isNeedRemark: 1
211
+ };
212
+ const response = await import_axios.default.post(url, body, { headers, timeout: 15e3 });
213
+ const data = response.data;
214
+ if (data.result !== 0 || !data.data || data.data.length === 0) {
215
+ return null;
216
+ }
217
+ const userData = data.data[0];
218
+ if (!userData.gameInfoList || !Array.isArray(userData.gameInfoList) || userData.gameInfoList.length === 0) {
219
+ return null;
220
+ }
221
+ const valGame = userData.gameInfoList.find((g) => {
222
+ const gameId = String(g.gameId || "").trim().toLowerCase();
223
+ return gameId === "agame";
224
+ });
225
+ if (!valGame) {
226
+ const firstGame = userData.gameInfoList[0];
227
+ if (firstGame) {
228
+ return {
229
+ gameId: firstGame.gameId || "",
230
+ roleName: firstGame.roleName || "",
231
+ tier: firstGame.tier || "",
232
+ level: firstGame.level || 0,
233
+ gameHeadUrl: firstGame.gameHeadUrl || ""
234
+ };
235
+ }
236
+ return null;
237
+ }
238
+ return {
239
+ gameId: valGame.gameId || "",
240
+ roleName: valGame.roleName || "",
241
+ tier: valGame.tier || "",
242
+ level: valGame.level || 0,
243
+ gameHeadUrl: cleanUrl(valGame.gameHeadUrl || "")
244
+ };
245
+ } catch (e) {
246
+ return null;
247
+ }
248
+ }
249
+ __name(fetchUserProfile, "fetchUserProfile");
250
+
251
+ // src/utils/render.ts
252
+ var import_sharp = __toESM(require("sharp"));
253
+ var import_axios2 = __toESM(require("axios"));
254
+ function cleanUrl2(url) {
255
+ if (!url) return "";
256
+ return url.replace(/`/g, "").trim();
257
+ }
258
+ __name(cleanUrl2, "cleanUrl");
259
+ async function downloadImage(url) {
260
+ try {
261
+ if (!url || !url.startsWith("http")) return null;
262
+ const response = await import_axios2.default.get(url, { responseType: "arraybuffer", timeout: 1e4 });
263
+ return Buffer.from(response.data);
264
+ } catch (e) {
265
+ return null;
266
+ }
267
+ }
268
+ __name(downloadImage, "downloadImage");
269
+ async function composeShopImage(items, type = "m", userName = "", subtitleText = "", gameHeadUrl = "") {
270
+ const width = 400;
271
+ const height = 146;
272
+ const headerHeight = 122;
273
+ const bottomPadding = 100;
274
+ const padding = 8;
275
+ const paddingX = 30;
276
+ const contentWidth = width;
277
+ const totalHeight = headerHeight + items.length * (height + padding) + padding + bottomPadding;
278
+ const bgRgb = { r: 235, g: 235, b: 226 };
279
+ const itemRgb = { r: 0, g: 0, b: 0, alpha: 0 };
280
+ const shadowOpacity = type === "g" ? 0.1 : 0.4;
281
+ for (let attempt = 0; attempt < 2; attempt++) {
282
+ try {
283
+ const composites = [];
284
+ const shadowComposites = [];
285
+ const picComposites = [];
286
+ for (let i = 0; i < items.length; i++) {
287
+ const item = items[i];
288
+ const y = headerHeight + i * (height + padding) + padding;
289
+ let itemBuffer = null;
290
+ const bgUrl = cleanUrl2(item.bg_image || "");
291
+ if (bgUrl) {
292
+ const bgBuffer = await downloadImage(bgUrl);
293
+ if (bgBuffer) {
294
+ try {
295
+ const metadata = await (0, import_sharp.default)(bgBuffer).metadata();
296
+ const originalWidth = metadata.width || contentWidth;
297
+ const originalHeight = metadata.height || height;
298
+ const ratio = Math.min(contentWidth / originalWidth, height / originalHeight);
299
+ const newWidth = Math.round(originalWidth * ratio);
300
+ const newHeight = Math.round(originalHeight * ratio);
301
+ const resizedBuffer = await (0, import_sharp.default)(bgBuffer).resize(newWidth, newHeight, { fit: "fill" }).flatten({ background: { r: 255, g: 255, b: 255, alpha: 1 } }).png({ force: true }).toBuffer();
302
+ const shadowOffset = 24;
303
+ const cornerRadius2 = 12;
304
+ const canvasWidth = newWidth + shadowOffset * 2;
305
+ const canvasHeight = newHeight + shadowOffset * 2;
306
+ const shadowSvg = Buffer.from(
307
+ `<svg width="${canvasWidth}" height="${canvasHeight}">
308
+ <rect x="${shadowOffset}" y="${shadowOffset + 4}" width="${newWidth}" height="${newHeight}" rx="${cornerRadius2}" ry="${cornerRadius2}" fill="black" fill-opacity="${shadowOpacity}"/>
309
+ </svg>`
310
+ );
311
+ const maskSvg = Buffer.from(
312
+ `<svg width="${newWidth}" height="${newHeight}">
313
+ <rect width="${newWidth}" height="${newHeight}" rx="${cornerRadius2}" ry="${cornerRadius2}" fill="white"/>
314
+ </svg>`
315
+ );
316
+ const blurredShadow = await (0, import_sharp.default)(shadowSvg).blur(18).png().toBuffer();
317
+ const roundedImage = await (0, import_sharp.default)(resizedBuffer).composite([{
318
+ input: maskSvg,
319
+ top: 0,
320
+ left: 0,
321
+ blend: "dest-in"
322
+ }]).png().toBuffer();
323
+ itemBuffer = await (0, import_sharp.default)(blurredShadow).composite([{
324
+ input: roundedImage,
325
+ top: shadowOffset,
326
+ left: shadowOffset
327
+ }]).png({ force: true }).toBuffer();
328
+ const itemNameText = item.goods_name || "";
329
+ const itemPrice = item.rmb_price ? `${item.rmb_price} VP` : "";
330
+ if (itemNameText || itemPrice) {
331
+ const textColor = type === "g" ? "#000000" : "#ffffff";
332
+ const priceColor = type === "g" ? "#000000b7" : "#ffffffb7";
333
+ let textElements = "";
334
+ if (itemPrice) {
335
+ textElements += `<text x="${width - shadowOffset - 10}" y="${canvasHeight - 34}" text-anchor="end" font-family="思源黑体, Source Han Sans, Noto Sans CJK, Arial, sans-serif" font-size="16" fill="${priceColor}" font-weight="bold">${itemPrice}</text>`;
336
+ }
337
+ if (itemNameText) {
338
+ textElements += `<text x="${shadowOffset + 10}" y="${canvasHeight - 34}" font-family="思源黑体, Source Han Sans, Noto Sans CJK, Arial, sans-serif" font-size="16" fill="${textColor}">${itemNameText}</text>`;
339
+ }
340
+ const textSvg = Buffer.from(
341
+ `<svg width="${canvasWidth}" height="${canvasHeight}">
342
+ ${textElements}
343
+ </svg>`
344
+ );
345
+ const textBuffer = await (0, import_sharp.default)(textSvg).png().toBuffer();
346
+ itemBuffer = await (0, import_sharp.default)(itemBuffer).composite([{
347
+ input: textBuffer,
348
+ top: 0,
349
+ left: 0
350
+ }]).png({ force: true }).toBuffer();
351
+ }
352
+ } catch (e) {
353
+ itemBuffer = null;
354
+ }
355
+ }
356
+ }
357
+ if (!itemBuffer) {
358
+ try {
359
+ itemBuffer = await (0, import_sharp.default)({
360
+ create: {
361
+ width: contentWidth,
362
+ height,
363
+ channels: 4,
364
+ background: { r: itemRgb.r, g: itemRgb.g, b: itemRgb.b, alpha: 1 }
365
+ }
366
+ }).png({ force: true }).toBuffer();
367
+ } catch (e) {
368
+ return null;
369
+ }
370
+ }
371
+ composites.push({ input: itemBuffer, top: y, left: 0 });
372
+ const picBuffer = await downloadImage(cleanUrl2(item.goods_pic));
373
+ if (picBuffer) {
374
+ try {
375
+ const picMetadata = await (0, import_sharp.default)(picBuffer).metadata();
376
+ const picOriginalWidth = picMetadata.width || 80;
377
+ const picOriginalHeight = picMetadata.height || 80;
378
+ const picRatio = Math.min(contentWidth * 0.7 / picOriginalWidth, height * 0.7 / picOriginalHeight);
379
+ const picNewWidth = Math.round(picOriginalWidth * picRatio);
380
+ const picNewHeight = Math.round(picOriginalHeight * picRatio);
381
+ const picResized = await (0, import_sharp.default)(picBuffer).resize(picNewWidth, picNewHeight, { fit: "fill" }).ensureAlpha(0.01).png({ force: true }).toBuffer();
382
+ const picLeft = (contentWidth - picNewWidth) / 2;
383
+ const picTop = y + 20 + (height - picNewHeight) / 2;
384
+ picComposites.push({ input: picResized, top: Math.round(picTop), left: Math.round(picLeft) });
385
+ } catch (e) {
386
+ }
387
+ }
388
+ }
389
+ const titleText = type === "g" ? "王国商店" : "每日商店";
390
+ const headerSvg = Buffer.from(
391
+ `<svg width="${width}" height="${headerHeight}">
392
+ <text x="${paddingX}" y="80" font-family="思源黑体, Source Han Sans, Noto Sans CJK, Arial, sans-serif" font-size="46" font-weight="bold" fill="#333">${userName}</text>
393
+ <text x="${paddingX}" y="118" font-family="思源黑体, Source Han Sans, Noto Sans CJK, Arial, sans-serif" font-size="26" font-weight="bold" fill="#333">的${titleText}</text>
394
+ </svg>`
395
+ );
396
+ const headerBuffer = await (0, import_sharp.default)(headerSvg).png().toBuffer();
397
+ const baseImage = (0, import_sharp.default)({
398
+ create: {
399
+ width,
400
+ height: totalHeight,
401
+ channels: 4,
402
+ background: { r: bgRgb.r, g: bgRgb.g, b: bgRgb.b, alpha: 1 }
403
+ }
404
+ });
405
+ let finalComposites = [...composites];
406
+ if (shadowComposites.length > 0) {
407
+ finalComposites = [...finalComposites, ...shadowComposites];
408
+ }
409
+ if (picComposites.length > 0) {
410
+ finalComposites = [...finalComposites, ...picComposites];
411
+ }
412
+ if (subtitleText) {
413
+ const footerHeight = 50;
414
+ const footerY = totalHeight - footerHeight;
415
+ const avatarX = paddingX;
416
+ const avatarY = footerY - 20;
417
+ const avatarSize = 25;
418
+ const textX = paddingX + 35;
419
+ if (gameHeadUrl && gameHeadUrl.startsWith("http")) {
420
+ const avatarBuffer = await downloadImage(gameHeadUrl);
421
+ if (avatarBuffer) {
422
+ try {
423
+ const avatarResized = await (0, import_sharp.default)(avatarBuffer).resize(avatarSize, avatarSize, { fit: "cover" }).png().toBuffer();
424
+ const avatarMaskSvg = Buffer.from(
425
+ `<svg width="${avatarSize}" height="${avatarSize}">
426
+ <circle cx="${avatarSize / 2}" cy="${avatarSize / 2}" r="${avatarSize / 2}" fill="white"/>
427
+ </svg>`
428
+ );
429
+ const avatarRounded = await (0, import_sharp.default)(avatarResized).composite([{
430
+ input: avatarMaskSvg,
431
+ top: 0,
432
+ left: 0,
433
+ blend: "dest-in"
434
+ }]).png().toBuffer();
435
+ finalComposites.push({ input: avatarRounded, top: avatarY, left: avatarX });
436
+ } catch (e) {
437
+ }
438
+ }
439
+ }
440
+ const textSvg = Buffer.from(
441
+ `<svg width="${width}" height="${footerHeight + 20}">
442
+ <text x="${textX}" y="30" font-family="思源黑体, Source Han Sans, Noto Sans CJK, Arial, sans-serif" font-size="18" fill="#666">${subtitleText}</text>
443
+ <text x="${width / 2}" y="65" text-anchor="middle" font-family="思源黑体, Source Han Sans, Noto Sans CJK, Arial, sans-serif" font-size="16" font-weight="bold" fill="#15151553">By Xiaoming</text>
444
+ </svg>`
445
+ );
446
+ const textBuffer = await (0, import_sharp.default)(textSvg).png().toBuffer();
447
+ finalComposites.push({ input: textBuffer, top: footerY - 30, left: 0 });
448
+ }
449
+ finalComposites.push({ input: headerBuffer, top: 0, left: 0 });
450
+ const imageBuffer = await baseImage.composite(finalComposites).png({ force: true }).toBuffer();
451
+ const cornerRadius = 20;
452
+ const roundedSvg = Buffer.from(
453
+ `<svg width="${width}" height="${totalHeight}">
454
+ <rect x="0" y="0" width="${width}" height="${totalHeight}" rx="${cornerRadius}" ry="${cornerRadius}" fill="white"/>
455
+ </svg>`
456
+ );
457
+ return await (0, import_sharp.default)(imageBuffer).composite([{
458
+ input: roundedSvg,
459
+ blend: "dest-in"
460
+ }]).png({ force: true }).toBuffer();
461
+ } catch (e) {
462
+ if (attempt === 0) {
463
+ await new Promise((resolve) => setTimeout(resolve, 100));
464
+ continue;
465
+ }
466
+ return null;
467
+ }
468
+ }
469
+ return null;
470
+ }
471
+ __name(composeShopImage, "composeShopImage");
472
+
473
+ // src/commands/shop.ts
474
+ function getReplyAt2(session) {
475
+ return session.user?.valReplyAt || "";
476
+ }
477
+ __name(getReplyAt2, "getReplyAt");
478
+ function applySd(ctx, config) {
479
+ ctx.command("val.sd [type]", "获取商店信息").action(async (argv) => {
480
+ const { session, args } = argv;
481
+ const prefix = getReplyAt2(session);
482
+ const type = args[0] || "m";
483
+ if (type !== "m" && type !== "g") {
484
+ return `${prefix} v: 参数无效`;
485
+ }
486
+ try {
487
+ const db = getDb();
488
+ if (!db) return `${prefix} 数据库未连接`;
489
+ if (!config.Key) return `${prefix} 加密密钥未配置`;
490
+ const userData = await db.collection("val_cookies").findOne({ userId: session.userId });
491
+ if (!userData || !userData.cookie) {
492
+ return `${prefix} 请先设置Cookie,使用 val set <cookie>`;
493
+ }
494
+ const cookie = decrypt(userData.cookie, config.Key);
495
+ if (!cookie) {
496
+ return `${prefix} Cookie解密失败,请检查加密密钥`;
497
+ }
498
+ const shopData = await fetchShop(cookie);
499
+ if (shopData.result !== 0) {
500
+ return `${prefix} 获取商店失败: ${shopData.msg || shopData.err_msg}`;
501
+ }
502
+ const storeKey = type === "m" ? "dailystore" : "kingdomstore";
503
+ const storeInfo = shopData.data.find((s) => s.key === storeKey);
504
+ if (!storeInfo) {
505
+ return `${prefix} ${type === "m" ? "每日商店" : "王国商店"}暂无可用商品`;
506
+ }
507
+ try {
508
+ const userName = session.author?.name || session.user?.name || "";
509
+ const gameInfo = await fetchUserProfile(cookie);
510
+ let roleText = "";
511
+ let gameHeadUrl = "";
512
+ if (gameInfo) {
513
+ roleText = `${gameInfo.roleName} | Lv${gameInfo.level} | ${gameInfo.tier}`;
514
+ gameHeadUrl = gameInfo.gameHeadUrl;
515
+ }
516
+ const imageBuffer = await composeShopImage(storeInfo.list, type, userName, roleText, gameHeadUrl);
517
+ if (!imageBuffer) {
518
+ return `${prefix} 图片生成失败,请稍后再试`;
519
+ }
520
+ return prefix + "\n" + import_koishi2.h.image(imageBuffer, "image/png");
521
+ } catch (e) {
522
+ return `${prefix} 图片生成失败: ${e.message}`;
523
+ }
524
+ } catch (e) {
525
+ return `${prefix} 获取失败: ${e.message}`;
526
+ }
527
+ });
528
+ }
529
+ __name(applySd, "applySd");
530
+
531
+ // src/index.ts
532
+ var name = "val-cunapp";
533
+ var Config = import_koishi3.Schema.object({
534
+ mongodbUrl: import_koishi3.Schema.string().description("MongoDB 连接地址").default(""),
535
+ Key: import_koishi3.Schema.string().description("存储信息加密密钥,请勿随意修改").default(""),
536
+ mongodbDb: import_koishi3.Schema.string().description("数据库名称").default("mongodb_botuser")
537
+ });
538
+ var logger2 = new import_koishi3.Logger("val-cunapp");
539
+ function checkAt(session) {
540
+ if (session.isDirect) return "";
541
+ const message = session.content || "";
542
+ if (!message.startsWith("@")) return null;
543
+ const atElement = session.elements?.filter((element) => element.type == "at")?.[0];
544
+ const atId = atElement?.attrs?.id;
545
+ if (!atId || atId !== session.selfId) return null;
546
+ return import_koishi3.h.at(session.userId).toString();
547
+ }
548
+ __name(checkAt, "checkAt");
549
+ function getReplyAt3(session) {
550
+ if (session.isDirect) return "";
551
+ return import_koishi3.h.at(session.userId).toString();
552
+ }
553
+ __name(getReplyAt3, "getReplyAt");
554
+ function apply(ctx, config) {
555
+ ctx.middleware(async (session, next) => {
556
+ const message = session.content || "";
557
+ const strippedMessage = session.stripped?.content || "";
558
+ logger2.info(`消息内容: "${message}", 去掉@后内容: "${strippedMessage}", 是否群聊: ${!session.isDirect}`);
559
+ const isValCommand = strippedMessage.includes("val.set") || strippedMessage.includes("val.sd") || strippedMessage.trim().startsWith("val");
560
+ if (isValCommand) {
561
+ const atResult = checkAt(session);
562
+ session.valAt = atResult;
563
+ session.valReplyAt = getReplyAt3(session);
564
+ }
565
+ return next();
566
+ });
567
+ const cmd = ctx.command("val", "瓦罗兰特相关功能");
568
+ delete cmd["_options"].help;
569
+ cmd.action(async ({ session, args, options }) => {
570
+ logger2.info(`命令处理函数被调用: 用户=${session.userId}, 消息="${session.content}"`);
571
+ if (!session.isDirect) {
572
+ const message = session.content || "";
573
+ logger2.info(`群聊消息检查: 消息="${message}", 包含@机器人: ${message.includes("@" + session.selfId)}`);
574
+ if (!message.includes("@" + session.selfId)) {
575
+ logger2.warn(`群聊命令执行被阻止: 用户=${session.userId}, 群组=${session.guildId}, 消息="${message}"`);
576
+ return;
577
+ }
578
+ logger2.info(`群聊命令开始执行: 用户=${session.userId}, 群组=${session.guildId}, 参数=${JSON.stringify(args)}`);
579
+ }
580
+ const atResult = session.valAt || "";
581
+ const replyAt = session.valReplyAt || "";
582
+ const prefix = replyAt || atResult || "";
583
+ logger2.info(`回复前缀: "${prefix}"`);
584
+ if (options?.help) {
585
+ logger2.info(`显示帮助信息: 用户=${session.userId}`);
586
+ return `${prefix} val ~ 瓦罗兰特指令模板
587
+
588
+ val set 设置认证信息
589
+ val sd 获取商店信息
590
+
591
+ 选项:
592
+ -h, --help 显示帮助信息`;
593
+ }
594
+ if (args[0]) {
595
+ logger2.warn(`无效子指令: 用户=${session.userId}, 指令="${args[0]}"`);
596
+ return `${prefix} 无效的子指令:${args[0]}
597
+ 请使用 val -h 查看帮助`;
598
+ }
599
+ logger2.info(`提示使用帮助: 用户=${session.userId}`);
600
+ return `${prefix} 请使用 val -h 查看命令帮助`;
601
+ });
602
+ applySet(ctx, config);
603
+ applySd(ctx, config);
604
+ ctx.on("ready", async () => {
605
+ try {
606
+ await connectMongo(config);
607
+ logger2.info("MongoDB 连接就绪");
608
+ } catch (e) {
609
+ logger2.error("MongoDB 连接失败:", e.message);
610
+ }
611
+ });
612
+ ctx.on("dispose", async () => {
613
+ await closeMongo();
614
+ });
615
+ }
616
+ __name(apply, "apply");
617
+ // Annotate the CommonJS export names for ESM import in node:
618
+ 0 && (module.exports = {
619
+ Config,
620
+ apply,
621
+ name
622
+ });
@@ -0,0 +1,2 @@
1
+ export declare function encrypt(plaintext: string, passphrase: string): string;
2
+ export declare function decrypt(ciphertext: string, passphrase: string): string | null;
@@ -0,0 +1,11 @@
1
+ export interface ShopItem {
2
+ goods_name: string;
3
+ goods_pic: string;
4
+ goods_id: string;
5
+ rmb_price: string;
6
+ quality: string;
7
+ like_num: string;
8
+ tips: string;
9
+ bg_image?: string;
10
+ }
11
+ export declare function composeShopImage(items: ShopItem[], type?: 'm' | 'g', userName?: string, subtitleText?: string, gameHeadUrl?: string): Promise<Buffer | null>;
@@ -0,0 +1,40 @@
1
+ export interface ShopItem {
2
+ goods_name: string;
3
+ goods_pic: string;
4
+ goods_id: string;
5
+ rmb_price: string;
6
+ quality: string;
7
+ like_num: string;
8
+ tips: string;
9
+ bg_image?: string;
10
+ }
11
+ export interface ShopData {
12
+ key: string;
13
+ title: string;
14
+ time: number;
15
+ end_ts: number;
16
+ cur_ts: number;
17
+ list: ShopItem[];
18
+ }
19
+ export interface ShopResponse {
20
+ result: number;
21
+ msg: string;
22
+ err_msg: string;
23
+ req_id: string;
24
+ data: ShopData[];
25
+ }
26
+ export interface GameInfo {
27
+ gameId: string;
28
+ roleName: string;
29
+ tier: string;
30
+ level: number;
31
+ gameHeadUrl: string;
32
+ }
33
+ export interface UserProfileResponse {
34
+ result: number;
35
+ msg: string;
36
+ err_msg: string;
37
+ data: any[];
38
+ }
39
+ export declare function fetchShop(cookie: string): Promise<ShopResponse>;
40
+ export declare function fetchUserProfile(cookie: string): Promise<GameInfo | null>;
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "koishi-plugin-cunapp-val",
3
+ "version": "0.0.1",
4
+ "main": "lib/index.js",
5
+ "typings": "lib/index.d.ts",
6
+ "files": [
7
+ "lib",
8
+ "dist"
9
+ ],
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "chatbot",
13
+ "koishi",
14
+ "plugin",
15
+ "valorant"
16
+ ],
17
+ "contributors": [
18
+ "cunbot"
19
+ ],
20
+ "repository": {
21
+ "type": "git",
22
+ "url": ""
23
+ },
24
+ "homepage": "",
25
+ "peerDependencies": {
26
+ "koishi": "^4.18.7",
27
+ "mongodb": "*"
28
+ },
29
+ "devDependencies": {
30
+ "axios": "^1.6.2",
31
+ "mongodb": "^6.3.0"
32
+ },
33
+ "dependencies": {
34
+ "axios": "^1.6.2",
35
+ "follow-redirects": "^1.15.6",
36
+ "form-data": "^4.0.0",
37
+ "proxy-from-env": "^1.1.0",
38
+ "sharp": "^0.34.5"
39
+ },
40
+ "koishi": {
41
+ "description": {
42
+ "en": "Valorant plugin for viewing shop and player info",
43
+ "zh": "瓦罗兰特商店查询和玩家信息插件(自用)"
44
+ },
45
+ "service": {
46
+ "required": []
47
+ }
48
+ }
49
+ }
package/readme.md ADDED
@@ -0,0 +1,5 @@
1
+ # koishi-plugin-val-cunapp
2
+
3
+ [![npm](https://img.shields.io/npm/v/koishi-plugin-val-cunapp?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-val-cunapp)
4
+
5
+ 获取瓦罗兰特玩家信息,需要登录账户