koishi-plugin-apple-xingshuxing 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.
package/lib/index.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ import { Context, Schema } from 'koishi';
2
+ interface Attributes {
3
+ looks: number;
4
+ acting: number;
5
+ singing: number;
6
+ dancing: number;
7
+ fitness: number;
8
+ fashion: number;
9
+ talent: number;
10
+ wisdom: number;
11
+ eq: number;
12
+ reputation: number;
13
+ ambition: number;
14
+ starCoins: number;
15
+ points: number;
16
+ rank: string;
17
+ popularity: number;
18
+ }
19
+ interface PersonaRecord {
20
+ id: string;
21
+ qq: string;
22
+ name: string;
23
+ gender: string;
24
+ attributes: Attributes;
25
+ createdAt: number;
26
+ }
27
+ declare module 'koishi' {
28
+ interface Tables {
29
+ xing_persona: PersonaRecord;
30
+ }
31
+ }
32
+ interface PluginConfig {
33
+ whiteGroups: string[];
34
+ adminQQ: string[];
35
+ }
36
+ export declare const name = "xing-persona-puppeteer";
37
+ export declare const using: string[];
38
+ export declare function apply(ctx: Context, config: PluginConfig): void;
39
+ export declare const Config: Schema<Schemastery.ObjectS<{
40
+ whiteGroups: Schema<string[], string[]>;
41
+ adminQQ: Schema<string[], string[]>;
42
+ }>, Schemastery.ObjectT<{
43
+ whiteGroups: Schema<string[], string[]>;
44
+ adminQQ: Schema<string[], string[]>;
45
+ }>>;
46
+ export {};
package/lib/index.js ADDED
@@ -0,0 +1,559 @@
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
+ using: () => using
37
+ });
38
+ module.exports = __toCommonJS(src_exports);
39
+ var import_koishi = require("koishi");
40
+ var puppeteer = __toESM(require("puppeteer"));
41
+ var DEFAULT_ATTR = {
42
+ looks: 0,
43
+ acting: 0,
44
+ singing: 0,
45
+ dancing: 0,
46
+ fitness: 0,
47
+ fashion: 0,
48
+ talent: 0,
49
+ wisdom: 0,
50
+ eq: 0,
51
+ reputation: 0,
52
+ ambition: 0,
53
+ starCoins: 1e3,
54
+ points: 0,
55
+ rank: "素人",
56
+ popularity: 0
57
+ };
58
+ function randomAttributes() {
59
+ const attr = { ...DEFAULT_ATTR };
60
+ attr.looks = Math.floor(Math.random() * 1001);
61
+ attr.acting = Math.floor(Math.random() * 1001);
62
+ attr.singing = Math.floor(Math.random() * 1001);
63
+ attr.dancing = Math.floor(Math.random() * 1001);
64
+ attr.fitness = Math.floor(Math.random() * 1001);
65
+ attr.fashion = Math.floor(Math.random() * 1001);
66
+ attr.talent = Math.floor(Math.random() * 1001);
67
+ attr.wisdom = Math.floor(Math.random() * 1001);
68
+ attr.eq = Math.floor(Math.random() * 1001);
69
+ attr.ambition = Math.floor(Math.random() * 101);
70
+ return attr;
71
+ }
72
+ __name(randomAttributes, "randomAttributes");
73
+ function generateHTML(userId, name2, gender, attr) {
74
+ const getProgressColor = /* @__PURE__ */ __name((value) => {
75
+ if (value >= 800) return "#ffd700";
76
+ if (value >= 600) return "#ff6b6b";
77
+ if (value >= 400) return "#ffa94d";
78
+ if (value >= 200) return "#74c0fc";
79
+ return "#adb5bd";
80
+ }, "getProgressColor");
81
+ const attrList = [
82
+ { label: "容貌", key: "looks" },
83
+ { label: "演技", key: "acting" },
84
+ { label: "唱功", key: "singing" },
85
+ { label: "舞蹈", key: "dancing" },
86
+ { label: "体能", key: "fitness" },
87
+ { label: "时尚", key: "fashion" },
88
+ { label: "才艺", key: "talent" },
89
+ { label: "智慧", key: "wisdom" },
90
+ { label: "情商", key: "eq" },
91
+ { label: "上进", key: "ambition" }
92
+ ];
93
+ let attrItemsHTML = "";
94
+ attrList.forEach(({ label, key }) => {
95
+ const value = attr[key] || 0;
96
+ const color = getProgressColor(value);
97
+ const percent = Math.min(value / 1e3 * 100, 100);
98
+ attrItemsHTML += `
99
+ <div class="attr-item">
100
+ <div class="attr-label">${label}</div>
101
+ <div class="attr-bar-bg">
102
+ <div class="attr-bar" style="width: ${percent}%; background: ${color};"></div>
103
+ </div>
104
+ <div class="attr-value">${value}</div>
105
+ </div>
106
+ `;
107
+ });
108
+ const fixedAttrs = [
109
+ { label: "口碑", value: attr.reputation || 0 },
110
+ { label: "星光币", value: attr.starCoins || 1e3 },
111
+ { label: "积分", value: attr.points || 0 },
112
+ { label: "咖位", value: attr.rank || "素人" },
113
+ { label: "人气", value: attr.popularity || 0 }
114
+ ];
115
+ let fixedHTML = "";
116
+ fixedAttrs.forEach(({ label, value }) => {
117
+ fixedHTML += `
118
+ <div class="fixed-item">
119
+ <span class="fixed-label">${label}</span>
120
+ <span class="fixed-value">${value}</span>
121
+ </div>
122
+ `;
123
+ });
124
+ return `<!DOCTYPE html>
125
+ <html>
126
+ <head>
127
+ <meta charset="UTF-8">
128
+ <style>
129
+ * { margin: 0; padding: 0; box-sizing: border-box; }
130
+ body {
131
+ display: flex;
132
+ justify-content: center;
133
+ align-items: center;
134
+ min-height: 100vh;
135
+ background: #0a0a12;
136
+ font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
137
+ padding: 20px;
138
+ }
139
+ .card {
140
+ width: 640px;
141
+ background: linear-gradient(145deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
142
+ border-radius: 24px;
143
+ padding: 30px 35px 25px;
144
+ box-shadow: 0 20px 60px rgba(0,0,0,0.8), 0 0 80px rgba(255,215,0,0.06);
145
+ border: 1px solid rgba(255,215,0,0.15);
146
+ position: relative;
147
+ overflow: hidden;
148
+ }
149
+ .card::before {
150
+ content: '';
151
+ position: absolute;
152
+ top: -50%;
153
+ left: -50%;
154
+ width: 200%;
155
+ height: 200%;
156
+ background: radial-gradient(ellipse at 30% 20%, rgba(255,215,0,0.03) 0%, transparent 60%);
157
+ pointer-events: none;
158
+ }
159
+ /* 顶部装饰线 */
160
+ .card-header {
161
+ text-align: center;
162
+ padding-bottom: 18px;
163
+ border-bottom: 2px solid rgba(255,215,0,0.2);
164
+ position: relative;
165
+ }
166
+ .card-header::after {
167
+ content: '◆';
168
+ position: absolute;
169
+ bottom: -12px;
170
+ left: 50%;
171
+ transform: translateX(-50%);
172
+ color: #ffd700;
173
+ font-size: 14px;
174
+ background: #0f3460;
175
+ padding: 0 12px;
176
+ }
177
+ .title {
178
+ font-size: 32px;
179
+ font-weight: 700;
180
+ background: linear-gradient(135deg, #ffd700 0%, #ffb347 50%, #ff8c00 100%);
181
+ -webkit-background-clip: text;
182
+ -webkit-text-fill-color: transparent;
183
+ background-clip: text;
184
+ letter-spacing: 6px;
185
+ text-shadow: 0 0 40px rgba(255,215,0,0.15);
186
+ }
187
+ .subtitle {
188
+ font-size: 12px;
189
+ color: rgba(255,215,0,0.4);
190
+ letter-spacing: 8px;
191
+ margin-top: 4px;
192
+ }
193
+ /* 基础信息 */
194
+ .info-area {
195
+ display: flex;
196
+ justify-content: space-between;
197
+ background: rgba(255,255,255,0.04);
198
+ border-radius: 12px;
199
+ padding: 12px 20px;
200
+ margin: 18px 0 20px;
201
+ border: 1px solid rgba(255,255,255,0.06);
202
+ }
203
+ .info-item {
204
+ color: #c8d0e0;
205
+ font-size: 15px;
206
+ }
207
+ .info-item span {
208
+ color: #ffd700;
209
+ font-weight: 600;
210
+ }
211
+ /* 属性列表 */
212
+ .attr-grid {
213
+ display: grid;
214
+ grid-template-columns: 1fr 1fr;
215
+ gap: 6px 20px;
216
+ margin: 16px 0 18px;
217
+ }
218
+ .attr-item {
219
+ display: flex;
220
+ align-items: center;
221
+ gap: 8px;
222
+ padding: 4px 0;
223
+ }
224
+ .attr-label {
225
+ color: #a8b2d0;
226
+ font-size: 13px;
227
+ width: 36px;
228
+ flex-shrink: 0;
229
+ text-align: right;
230
+ font-weight: 500;
231
+ }
232
+ .attr-bar-bg {
233
+ flex: 1;
234
+ height: 6px;
235
+ background: rgba(255,255,255,0.08);
236
+ border-radius: 4px;
237
+ overflow: hidden;
238
+ min-width: 60px;
239
+ }
240
+ .attr-bar {
241
+ height: 100%;
242
+ border-radius: 4px;
243
+ transition: width 0.3s ease;
244
+ box-shadow: 0 0 8px rgba(255,215,0,0.15);
245
+ }
246
+ .attr-value {
247
+ color: #e8ecf4;
248
+ font-size: 12px;
249
+ font-weight: 600;
250
+ width: 32px;
251
+ text-align: right;
252
+ font-variant-numeric: tabular-nums;
253
+ }
254
+ /* 固定属性区域 */
255
+ .fixed-area {
256
+ display: grid;
257
+ grid-template-columns: repeat(5, 1fr);
258
+ gap: 8px;
259
+ background: rgba(255,255,255,0.03);
260
+ border-radius: 12px;
261
+ padding: 12px 16px;
262
+ margin-top: 8px;
263
+ border: 1px solid rgba(255,215,0,0.06);
264
+ }
265
+ .fixed-item {
266
+ text-align: center;
267
+ display: flex;
268
+ flex-direction: column;
269
+ align-items: center;
270
+ gap: 2px;
271
+ }
272
+ .fixed-label {
273
+ color: #8892b0;
274
+ font-size: 11px;
275
+ letter-spacing: 1px;
276
+ }
277
+ .fixed-value {
278
+ color: #ffd700;
279
+ font-size: 18px;
280
+ font-weight: 700;
281
+ text-shadow: 0 0 20px rgba(255,215,0,0.1);
282
+ }
283
+ .fixed-value.rank {
284
+ font-size: 20px;
285
+ background: linear-gradient(135deg, #ffd700, #ff6b35);
286
+ -webkit-background-clip: text;
287
+ -webkit-text-fill-color: transparent;
288
+ background-clip: text;
289
+ }
290
+ /* 底部 */
291
+ .card-footer {
292
+ display: flex;
293
+ justify-content: space-between;
294
+ margin-top: 16px;
295
+ padding-top: 14px;
296
+ border-top: 1px solid rgba(255,255,255,0.05);
297
+ color: #4a5478;
298
+ font-size: 11px;
299
+ letter-spacing: 1px;
300
+ }
301
+ .card-footer .id {
302
+ color: #5a6a8a;
303
+ font-family: monospace;
304
+ }
305
+ .card-footer .power {
306
+ color: rgba(255,215,0,0.25);
307
+ }
308
+ /* 评分圆环 - 装饰 */
309
+ .score-ring {
310
+ position: absolute;
311
+ top: 20px;
312
+ right: 25px;
313
+ width: 56px;
314
+ height: 56px;
315
+ border-radius: 50%;
316
+ border: 2px solid rgba(255,215,0,0.1);
317
+ display: flex;
318
+ align-items: center;
319
+ justify-content: center;
320
+ flex-direction: column;
321
+ background: rgba(0,0,0,0.3);
322
+ }
323
+ .score-ring .num {
324
+ color: #ffd700;
325
+ font-size: 20px;
326
+ font-weight: 700;
327
+ line-height: 1;
328
+ }
329
+ .score-ring .lbl {
330
+ color: #6a7a9a;
331
+ font-size: 8px;
332
+ letter-spacing: 1px;
333
+ }
334
+ </style>
335
+ </head>
336
+ <body>
337
+ <div class="card">
338
+ <div class="card-header">
339
+ <div class="title">✦ 星 途 人 物 卡 ✦</div>
340
+ <div class="subtitle">— S T A R R O A D —</div>
341
+ </div>
342
+
343
+ <div class="info-area">
344
+ <div class="info-item">🆔 <span>${userId}</span></div>
345
+ <div class="info-item">👤 <span>${name2}</span></div>
346
+ <div class="info-item">⚥ <span>${gender}</span></div>
347
+ </div>
348
+
349
+ <div class="attr-grid">
350
+ ${attrItemsHTML}
351
+ </div>
352
+
353
+ <div class="fixed-area">
354
+ ${fixedHTML}
355
+ </div>
356
+
357
+ <div class="card-footer">
358
+ <span class="id">#${userId.slice(-6)}</span>
359
+ <span class="power">✦ 星途无限 ✦</span>
360
+ <span class="id">${(/* @__PURE__ */ new Date()).toLocaleDateString()}</span>
361
+ </div>
362
+ </div>
363
+ </body>
364
+ </html>`;
365
+ }
366
+ __name(generateHTML, "generateHTML");
367
+ var browserInstance = null;
368
+ async function getBrowser() {
369
+ if (!browserInstance) {
370
+ browserInstance = await puppeteer.launch({
371
+ headless: true,
372
+ args: [
373
+ "--no-sandbox",
374
+ "--disable-setuid-sandbox",
375
+ "--disable-dev-shm-usage",
376
+ "--disable-gpu",
377
+ "--font-render-hinting=none"
378
+ ]
379
+ });
380
+ }
381
+ return browserInstance;
382
+ }
383
+ __name(getBrowser, "getBrowser");
384
+ async function generateProfileImage(userId, name2, gender, attr) {
385
+ const html = generateHTML(userId, name2, gender, attr);
386
+ const browser = await getBrowser();
387
+ const page = await browser.newPage();
388
+ try {
389
+ await page.setViewport({
390
+ width: 680,
391
+ height: 780,
392
+ deviceScaleFactor: 2
393
+ // 高清输出
394
+ });
395
+ await page.setContent(html, {
396
+ waitUntil: "load"
397
+ });
398
+ await page.evaluate(() => document.fonts.ready);
399
+ const cardHeight = await page.evaluate(() => {
400
+ const card = document.querySelector(".card");
401
+ return card ? card.offsetHeight + 40 : 780;
402
+ });
403
+ const screenshot = await page.screenshot({
404
+ type: "png",
405
+ clip: {
406
+ x: 0,
407
+ y: 0,
408
+ width: 680,
409
+ height: Math.max(cardHeight, 780)
410
+ },
411
+ omitBackground: false
412
+ });
413
+ return screenshot;
414
+ } finally {
415
+ await page.close();
416
+ }
417
+ }
418
+ __name(generateProfileImage, "generateProfileImage");
419
+ async function closeBrowser() {
420
+ if (browserInstance) {
421
+ await browserInstance.close();
422
+ browserInstance = null;
423
+ }
424
+ }
425
+ __name(closeBrowser, "closeBrowser");
426
+ var name = "xing-persona-puppeteer";
427
+ var using = ["database"];
428
+ function apply(ctx, config) {
429
+ const whiteGroups = config.whiteGroups || [];
430
+ const adminQQ = config.adminQQ || [];
431
+ if (!ctx.model.tables["xing_persona"]) {
432
+ ctx.model.extend("xing_persona", {
433
+ id: "string",
434
+ qq: "string",
435
+ name: "string",
436
+ gender: "string",
437
+ attributes: "json",
438
+ createdAt: "integer"
439
+ });
440
+ }
441
+ ctx.middleware((session, next) => {
442
+ if (session.type === "message") {
443
+ const groupId = session.event?.guild?.id?.toString();
444
+ if (groupId && whiteGroups.includes(groupId)) {
445
+ return next();
446
+ } else if (!groupId) {
447
+ return next();
448
+ } else {
449
+ return;
450
+ }
451
+ }
452
+ return next();
453
+ });
454
+ ctx.command("创建人物 <name:string> <gender:string>", "创建星途人物").action(async ({ session }, name2, gender) => {
455
+ if (!session) {
456
+ return "会话不存在,请重试。";
457
+ }
458
+ const groupId = session.event?.guild?.id?.toString();
459
+ if (!groupId) {
460
+ return "请在群聊中使用此指令。";
461
+ }
462
+ if (!whiteGroups.includes(groupId)) {
463
+ return "本群未开启星途人物功能。";
464
+ }
465
+ const qq = session.userId || "";
466
+ if (!qq) {
467
+ return "无法获取用户信息,请重试。";
468
+ }
469
+ const exist = await ctx.database.get("xing_persona", { qq });
470
+ if (exist.length) return "您已创建过人物,每人仅限一个。";
471
+ if (!name2 || !gender) return "请正确输入: 创建人物 姓名 性别 (如: 创建人物 小明 男)";
472
+ if (!["男", "女", "其他"].includes(gender)) return "性别请填写 男/女/其他";
473
+ const attr = randomAttributes();
474
+ const id = `xing_${Date.now()}_${qq}`;
475
+ await ctx.database.create("xing_persona", {
476
+ id,
477
+ qq,
478
+ name: name2,
479
+ gender,
480
+ attributes: attr,
481
+ createdAt: Date.now()
482
+ });
483
+ try {
484
+ const imgBuffer = await generateProfileImage(qq, name2, gender, attr);
485
+ return import_koishi.segment.image(imgBuffer, "image/png");
486
+ } catch (e) {
487
+ ctx.logger.error("图片生成失败", e);
488
+ return `人物创建成功!
489
+ 姓名:${name2}
490
+ 性别:${gender}
491
+ 属性:${JSON.stringify(attr)}`;
492
+ }
493
+ });
494
+ ctx.command("我的人物", "查看自己的人物属性").action(async ({ session }) => {
495
+ if (!session) {
496
+ return "会话不存在,请重试。";
497
+ }
498
+ const qq = session.userId || "";
499
+ if (!qq) {
500
+ return "无法获取用户信息,请重试。";
501
+ }
502
+ const data = await ctx.database.get("xing_persona", { qq });
503
+ if (!data.length) return '您尚未创建人物,请使用 "创建人物 姓名 性别" 创建。';
504
+ const record = data[0];
505
+ try {
506
+ const imgBuffer = await generateProfileImage(qq, record.name, record.gender, record.attributes);
507
+ return import_koishi.segment.image(imgBuffer, "image/png");
508
+ } catch (e) {
509
+ return `人物信息:
510
+ 姓名:${record.name}
511
+ 性别:${record.gender}
512
+ 属性:${JSON.stringify(record.attributes)}`;
513
+ }
514
+ });
515
+ ctx.command("admin/reset-all", "重置所有人数据 (仅管理员)").action(async ({ session }) => {
516
+ if (!session) {
517
+ return "会话不存在,请重试。";
518
+ }
519
+ const userId = session.userId || "";
520
+ if (!userId) {
521
+ return "无法获取用户信息,请重试。";
522
+ }
523
+ if (!adminQQ.includes(userId)) return "您不是管理员。";
524
+ await ctx.database.remove("xing_persona", {});
525
+ return "✅ 已重置所有人数据。";
526
+ });
527
+ ctx.command("admin/delete-user <qq:string>", "删除指定QQ人物数据 (仅管理员)").action(async ({ session }, qq) => {
528
+ if (!session) {
529
+ return "会话不存在,请重试。";
530
+ }
531
+ const userId = session.userId || "";
532
+ if (!userId) {
533
+ return "无法获取用户信息,请重试。";
534
+ }
535
+ if (!adminQQ.includes(userId)) return "您不是管理员。";
536
+ if (!qq) return "请提供要删除的QQ号。";
537
+ const exist = await ctx.database.get("xing_persona", { qq });
538
+ if (!exist.length) {
539
+ return `❌ 未找到QQ ${qq} 的人物数据。`;
540
+ }
541
+ await ctx.database.remove("xing_persona", { qq });
542
+ return `✅ 已删除QQ ${qq} 的人物数据。`;
543
+ });
544
+ ctx.on("dispose", () => {
545
+ closeBrowser().catch(console.error);
546
+ });
547
+ }
548
+ __name(apply, "apply");
549
+ var Config = import_koishi.Schema.object({
550
+ whiteGroups: import_koishi.Schema.array(String).description("白名单群号列表").default([]),
551
+ adminQQ: import_koishi.Schema.array(String).description("管理员QQ号列表").default([])
552
+ });
553
+ // Annotate the CommonJS export names for ESM import in node:
554
+ 0 && (module.exports = {
555
+ Config,
556
+ apply,
557
+ name,
558
+ using
559
+ });
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "koishi-plugin-apple-xingshuxing",
3
+ "description": "自用辅助",
4
+ "version": "0.0.1",
5
+ "main": "lib/index.js",
6
+ "typings": "lib/index.d.ts",
7
+ "files": [
8
+ "lib",
9
+ "dist"
10
+ ],
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "chatbot",
14
+ "koishi",
15
+ "plugin"
16
+ ],
17
+ "peerDependencies": {
18
+ "koishi": "^4.18.7"
19
+ },
20
+ "dependencies": {
21
+ "@koishijs/plugin-puppeteer": "^3.2.0",
22
+ "puppeteer": "^25.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/puppeteer": "^7.0.4"
26
+ }
27
+ }
package/readme.md ADDED
@@ -0,0 +1,5 @@
1
+ # koishi-plugin-apple-xingshuxing
2
+
3
+ [![npm](https://img.shields.io/npm/v/koishi-plugin-apple-xingshuxing?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-apple-xingshuxing)
4
+
5
+ 自用辅助