koishi-plugin-dwrgbwiki 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/index.js +111 -17
  2. package/lib.js +82 -1
  3. package/package.json +3 -3
  4. package/readme.md +7 -1
package/index.js CHANGED
@@ -5,13 +5,31 @@ const lib = require('./lib')
5
5
 
6
6
  const HELP = lib.buildHelp()
7
7
 
8
+ const MAIN_HELP = [
9
+ '第五人格BWIKI数据查询机器人',
10
+ '',
11
+ '支持指令:',
12
+ ' 胜率 <角色名> [赛季] [周数] [排序要素] [段位]',
13
+ ' 查询角色胜率,例:胜率 小女孩',
14
+ ' 地图 <地图名> [赛季] [周数] [段位] [阵营]',
15
+ ' 查询地图胜率,例:地图 军工厂',
16
+ ' 模仿者 <角色名> [日期] [模式]',
17
+ ' 查询模仿者游戏数据,例:模仿者 侦探',
18
+ ' 勋章 <角色名> [日期] [模式]',
19
+ ' 查询角色勋章分数线,例:勋章 小女孩',
20
+ '',
21
+ '发送“胜率”等单独指令可查看对应完整帮助',
22
+ '数据均根据第五人格官方小程序数据计算',
23
+ ].join('\n')
24
+
8
25
  module.exports = {
9
26
  name: 'dwrgbwiki',
10
27
  description: '第五人格BWIKI编辑组测试用组件,纯AI生成喵',
28
+ inject: ['puppeteer'],
11
29
 
12
30
  Config: Schema.object({
13
- dataSource: Schema.string()
14
- .description('数据源地址,可在两个镜像站点之间切换:' + lib.KNOWN_SOURCES.join(' / '))
31
+ dataSource: Schema.union(lib.KNOWN_SOURCES)
32
+ .description('数据源地址,可在两个镜像站点之间切换')
15
33
  .default('https://website.homeworkkun.top'),
16
34
  tryAlternate: Schema.boolean()
17
35
  .description('主数据源失败时自动尝试备用数据源')
@@ -29,25 +47,76 @@ module.exports = {
29
47
  .description('勋章日增长率天数')
30
48
  .default(7),
31
49
  width: Schema.number().min(400).max(4000)
32
- .description('图片宽度(px)')
33
- .default(1000),
50
+ .description('图片宽度(px),其他指令未单独设置时使用该值')
51
+ .default(1300),
52
+ mapWidth: Schema.number().min(0).max(4000)
53
+ .description('地图指令图片宽度(px),0=使用默认宽度')
54
+ .default(0),
55
+ mimicWidth: Schema.number().min(0).max(4000)
56
+ .description('模仿者指令图片宽度(px),0=使用默认宽度')
57
+ .default(0),
58
+ badgeWidth: Schema.number().min(0).max(4000)
59
+ .description('勋章指令图片宽度(px),0=使用默认宽度')
60
+ .default(0),
34
61
  scale: Schema.number().min(1).max(4)
35
62
  .description('截图倍率(越大越清晰)')
36
63
  .default(2),
37
64
  timeout: Schema.number().min(3000).max(60000)
38
65
  .description('网络请求超时(毫秒)')
39
66
  .default(15000),
67
+ heartbeatEnable: Schema.boolean()
68
+ .description('启用心跳包定时发送功能')
69
+ .default(false),
70
+ heartbeatServer: Schema.string()
71
+ .description('心跳服务端地址(如 http://192.168.1.10:8080)')
72
+ .default('http://localhost:8080'),
73
+ heartbeatUid: Schema.string()
74
+ .description('心跳 uid(可在指令中修改)')
75
+ .default(''),
76
+ heartbeatInterval: Schema.number().min(5).max(86400)
77
+ .description('心跳发送间隔(秒)')
78
+ .default(60),
40
79
  }),
41
80
 
42
81
  apply(ctx, config) {
82
+ ctx.command('BWIKI机器人', '显示当前支持的指令列表')
83
+ .action(async ({ session }) => {
84
+ return helpImage(ctx, config, MAIN_HELP)
85
+ })
86
+
87
+ const heartbeat = {
88
+ uid: config.heartbeatUid || '',
89
+ timer: null,
90
+ }
91
+ const sendHeartbeat = async () => {
92
+ if (!heartbeat.uid || !config.heartbeatServer) return
93
+ const url = config.heartbeatServer.replace(/\/+$/, '') + '/api/heartbeat?uid=' + encodeURIComponent(heartbeat.uid)
94
+ const ctrl = new AbortController()
95
+ const timer = setTimeout(() => ctrl.abort(), 10000)
96
+ try {
97
+ await fetch(url, { signal: ctrl.signal })
98
+ } catch (e) {
99
+ ctx.logger('dwrgbwiki').warn('[heartbeat] 发送失败: ' + (e && e.message ? e.message : e))
100
+ } finally {
101
+ clearTimeout(timer)
102
+ }
103
+ }
104
+ const startHeartbeat = () => {
105
+ if (heartbeat.timer) return
106
+ if (!config.heartbeatInterval || config.heartbeatInterval < 5) config.heartbeatInterval = 60
107
+ heartbeat.timer = ctx.setInterval(() => sendHeartbeat(), config.heartbeatInterval * 1000)
108
+ sendHeartbeat()
109
+ }
110
+ if (config.heartbeatEnable) startHeartbeat()
111
+
43
112
  ctx.command('胜率 <角色名> [条件:text]', '查询角色胜率并生成图片')
44
113
  .usage(HELP)
45
114
  .example('胜率 小女孩')
46
115
  .example('胜率 小女孩 第43赛季 第3周 使用率')
47
116
  .example('胜率 小女孩 43 3 胜率')
48
117
  .action(async ({ session }, name, cond) => {
49
- if (!name || !name.trim()) return HELP
50
- return run(ctx, config, lib.collect, lib.buildHtml, name.trim(), cond)
118
+ if (!name || !name.trim()) return helpImage(ctx, config, HELP)
119
+ return run(ctx, config, lib.collect, lib.buildHtml, name.trim(), cond, 'width')
51
120
  })
52
121
 
53
122
  ctx.command('地图 <地图名> [条件:text]', '查询地图胜率并生成图片')
@@ -56,8 +125,8 @@ module.exports = {
56
125
  .example('地图 红教堂 S44 3 求生者')
57
126
  .example('地图 月亮河公园 全赛季')
58
127
  .action(async ({ session }, name, cond) => {
59
- if (!name || !name.trim()) return MAP_HELP
60
- return run(ctx, config, lib.collectMap, lib.buildMapHtml, name.trim(), cond)
128
+ if (!name || !name.trim()) return helpImage(ctx, config, MAP_HELP)
129
+ return run(ctx, config, lib.collectMap, lib.buildMapHtml, name.trim(), cond, 'mapWidth')
61
130
  })
62
131
 
63
132
  ctx.command('模仿者 <角色名> [条件:text]', '查询模仿者游戏角色数据并生成图片')
@@ -65,8 +134,8 @@ module.exports = {
65
134
  .example('模仿者 侦探')
66
135
  .example('模仿者 药剂师 260531 低级场')
67
136
  .action(async ({ session }, name, cond) => {
68
- if (!name || !name.trim()) return MIMIC_HELP
69
- return run(ctx, config, lib.collectMimic, lib.buildMimicHtml, name.trim(), cond)
137
+ if (!name || !name.trim()) return helpImage(ctx, config, MIMIC_HELP)
138
+ return run(ctx, config, lib.collectMimic, lib.buildMimicHtml, name.trim(), cond, 'mimicWidth')
70
139
  })
71
140
 
72
141
  ctx.command('勋章 <角色名> [条件:text]', '查询角色勋章分数线并生成图片')
@@ -75,25 +144,50 @@ module.exports = {
75
144
  .example('勋章 小女孩 20250917 7日增长率')
76
145
  .example('勋章 小女孩 赛季同比增长率')
77
146
  .action(async ({ session }, name, cond) => {
78
- if (!name || !name.trim()) return BADGE_HELP
79
- return run(ctx, config, lib.collectBadge, lib.buildBadgeHtml, name.trim(), cond)
147
+ if (!name || !name.trim()) return helpImage(ctx, config, BADGE_HELP)
148
+ return run(ctx, config, lib.collectBadge, lib.buildBadgeHtml, name.trim(), cond, 'badgeWidth')
80
149
  })
81
150
  },
82
151
  }
83
152
 
84
- async function run(ctx, config, collectFn, buildFn, name, cond) {
153
+ async function run(ctx, config, collectFn, buildFn, name, cond, widthKey) {
85
154
  if (!ctx.puppeteer) {
86
155
  return '未检测到 puppeteer 服务,请先安装并启用 koishi-plugin-puppeteer'
87
156
  }
88
157
  try {
89
158
  const result = await collectFn(config, name, cond)
90
- if (!result.ok) return result.error
91
- const html = buildFn(result, { ...config, avatarBase: result.base })
159
+ if (!result.ok) return errorImage(ctx, config, result.error)
160
+ const width = (config[widthKey] && config[widthKey] > 0) ? config[widthKey] : config.width
161
+ const html = buildFn(result, { ...config, avatarBase: result.base, width })
162
+ const image = await capture(ctx, html, { ...config, width })
163
+ return [h.image(image, 'image/png')]
164
+ } catch (e) {
165
+ ctx.logger('dwrgbwiki').warn(e)
166
+ return errorImage(ctx, config, '查询失败:' + (e && e.message ? e.message : e))
167
+ }
168
+ }
169
+
170
+ async function helpImage(ctx, config, text) {
171
+ if (!ctx.puppeteer) return text
172
+ try {
173
+ const html = lib.buildHelpHtml(text, { ...config, width: config.width })
174
+ const image = await capture(ctx, html, config)
175
+ return [h.image(image, 'image/png')]
176
+ } catch (e) {
177
+ ctx.logger('dwrgbwiki').warn(e)
178
+ return text
179
+ }
180
+ }
181
+
182
+ async function errorImage(ctx, config, text) {
183
+ if (!ctx.puppeteer || !text) return text
184
+ try {
185
+ const html = lib.buildHelpHtml(text, { ...config, width: config.width })
92
186
  const image = await capture(ctx, html, config)
93
- return [h.image(image)]
187
+ return [h.image(image, 'image/png')]
94
188
  } catch (e) {
95
189
  ctx.logger('dwrgbwiki').warn(e)
96
- return '查询失败:' + (e && e.message ? e.message : e)
190
+ return text
97
191
  }
98
192
  }
99
193
 
package/lib.js CHANGED
@@ -940,7 +940,8 @@ function buildHtml(data, config) {
940
940
  const zoneLeft = '<div class="stat-block">' + sixRows + '</div>' + changeBlock +
941
941
  '<div class="zone-sep"></div>' +
942
942
  '<div class="stat-block"><div class="comp-title" style="color:var(--accent);margin-bottom:4px">各段位去平胜率(距平均)</div>' +
943
- '<div class="allrank-grid">' + partsItems + '</div></div>'
943
+ '<div class="allrank-grid">' + partsItems + '</div></div>' +
944
+ '<div class="credit">@夜莺小姐档案馆</div>'
944
945
 
945
946
  let zoneRight
946
947
  if (badge) {
@@ -1036,6 +1037,7 @@ body { background:var(--bg); color:var(--text); font-family:-apple-system,BlinkM
1036
1037
  .rk-name { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
1037
1038
  .rk-val { text-align:right; font-variant-numeric:tabular-nums; font-weight:600; font-size:16px; }
1038
1039
  .rank-header .rk-pos, .rank-header .rk-val { color:var(--text-secondary); font-weight:600; }
1040
+ .credit { text-align:center; color:var(--text-secondary); font-size:12px; margin-top:14px; opacity:.7; }
1039
1041
  .stat-block { display:flex; flex-direction:column; gap:6px; }
1040
1042
  .stat-block + .stat-block { margin-top:12px; }
1041
1043
  .s-row { display:flex; align-items:center; justify-content:space-between; padding:8px 12px; background:rgba(255,255,255,.02); border:1px solid rgba(255,255,255,.04); border-radius:6px; gap:10px; }
@@ -1337,6 +1339,84 @@ function buildHelp() {
1337
1339
  ].join('\n')
1338
1340
  }
1339
1341
 
1342
+ function buildHelpHtml(text, config) {
1343
+ const width = config.width || 1300
1344
+ const cardW = Math.min(width, 880)
1345
+
1346
+ let hero = '', body = '', titleTaken = false
1347
+ String(text).split('\n').forEach(line => {
1348
+ const raw = String(line).replace(/\r$/, '')
1349
+ const trimmed = raw.trim()
1350
+ if (!titleTaken) {
1351
+ titleTaken = true
1352
+ hero =
1353
+ '<div class="hero-icon">🤖</div>' +
1354
+ '<div class="hero-title">' + esc(trimmed) + '</div>' +
1355
+ '<div class="hero-sub">第五人格 BWIKI 数据查询 · 数据均根据官方小程序数据计算</div>'
1356
+ return
1357
+ }
1358
+ if (!trimmed) { body += '<div class="gap"></div>'; return }
1359
+ if (/^(支持指令|用法|示例):/.test(trimmed)) {
1360
+ body += '<div class="sec">' + esc(trimmed) + '</div>'
1361
+ return
1362
+ }
1363
+ if (/^[ ]{4,}/.test(raw)) {
1364
+ body += '<div class="desc">' + esc(trimmed) + '</div>'
1365
+ return
1366
+ }
1367
+ if (/^[ ]{2}/.test(raw)) {
1368
+ body += '<div class="code">' + esc(trimmed) + '</div>'
1369
+ return
1370
+ }
1371
+ if (/^(排序要素|段位|日期|模式|阵营|赛季)/.test(trimmed) && trimmed.includes(':')) {
1372
+ body += '<div class="param"><span class="param-key">' + esc(trimmed.split(':')[0] + ':') + '</span>' + esc(trimmed.slice(trimmed.indexOf(':') + 1)) + '</div>'
1373
+ return
1374
+ }
1375
+ body += '<div class="line">' + esc(trimmed) + '</div>'
1376
+ })
1377
+
1378
+ return `<!DOCTYPE html>
1379
+ <html lang="zh-CN">
1380
+ <head>
1381
+ <meta charset="UTF-8">
1382
+ <style>
1383
+ * { margin:0; padding:0; box-sizing:border-box; }
1384
+ body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Microsoft YaHei',sans-serif; padding:28px 16px; color:#c9d1d9;
1385
+ background:radial-gradient(1200px 500px at 50% -10%, #1b2a4a 0%, #10182b 45%, #0a0e17 100%); }
1386
+ .page { max-width:${cardW}px; margin:0 auto; }
1387
+ .hero { display:flex; flex-direction:column; align-items:center; gap:10px; text-align:center; margin-bottom:22px; }
1388
+ .hero-icon { width:74px; height:74px; border-radius:22px; display:flex; align-items:center; justify-content:center; font-size:40px;
1389
+ background:linear-gradient(135deg, rgba(88,166,255,.18), rgba(210,153,29,.16)); border:1px solid rgba(88,166,255,.35);
1390
+ box-shadow:0 0 36px rgba(88,166,255,.18), inset 0 1px 0 rgba(255,255,255,.08); }
1391
+ .hero-title { font-size:30px; font-weight:800; letter-spacing:1px;
1392
+ background:linear-gradient(90deg, #79c0ff, #ffd76d); -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent; }
1393
+ .hero-sub { font-size:13px; color:#8b949e; }
1394
+ .card { background:rgba(22,27,34,.72); border:1px solid rgba(255,255,255,.09); border-radius:22px; padding:26px 30px;
1395
+ backdrop-filter:blur(8px); box-shadow:0 24px 64px rgba(0,0,0,.45); }
1396
+ .gap { height:8px; }
1397
+ .sec { font-size:16px; font-weight:700; color:#ffd76d; margin:14px 0 8px; display:flex; align-items:center; gap:10px; }
1398
+ .sec::before { content:''; width:4px; height:16px; border-radius:2px; background:linear-gradient(#ffd76d, #ff8c00); flex-shrink:0; }
1399
+ .sec:first-child { margin-top:0; }
1400
+ .line { font-size:14px; line-height:1.9; color:#c9d1d9; }
1401
+ .param { font-size:13.5px; line-height:1.9; color:#c9d1d9; padding:5px 14px; background:rgba(255,255,255,.03); border:1px solid rgba(255,255,255,.06); border-radius:8px; margin:5px 0; }
1402
+ .param-key { color:#ffd76d; font-weight:600; }
1403
+ .desc { font-size:13px; line-height:1.8; color:#8b949e; padding:2px 0 2px 18px; }
1404
+ .code { font-family:'SF Mono',Consolas,'Courier New',monospace; font-size:13.5px; line-height:1.7; color:#79c0ff;
1405
+ background:rgba(88,166,255,.08); border:1px solid rgba(88,166,255,.2); border-radius:8px; padding:6px 14px; margin:5px 0;
1406
+ word-break:break-all; }
1407
+ .foot { text-align:center; color:#8b949e; font-size:12px; margin-top:14px; opacity:.8; }
1408
+ </style>
1409
+ </head>
1410
+ <body>
1411
+ <div class="page">
1412
+ <div class="hero">${hero}</div>
1413
+ <div class="card">${body}</div>
1414
+ <div class="foot">@夜莺小姐档案馆 · 数据均根据第五人格官方小程序数据计算</div>
1415
+ </div>
1416
+ </body>
1417
+ </html>`
1418
+ }
1419
+
1340
1420
  module.exports = {
1341
1421
  KNOWN_SOURCES,
1342
1422
  METRICS,
@@ -1360,5 +1440,6 @@ module.exports = {
1360
1440
  buildMimicHtml,
1361
1441
  buildBadgeHtml,
1362
1442
  buildHelp,
1443
+ buildHelpHtml,
1363
1444
  findSimilar,
1364
1445
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-dwrgbwiki",
3
- "version": "1.1.0",
4
- "description": "第五人格BWIKI编辑组测试用组件,纯AI生成喵",
3
+ "version": "1.3.0",
4
+ "description": "第五人格BWIKI编辑组测试用插件,所有代码由DeepSeek V4 Flash编写",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  "index.js",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "koishi": {
23
23
  "description": {
24
- "zh": "第五人格BWIKI编辑组测试用组件,纯AI生成喵"
24
+ "zh": "第五人格BWIKI编辑组测试用插件,所有代码由DeepSeek V4 Flash编写"
25
25
  }
26
26
  }
27
27
  }
package/readme.md CHANGED
@@ -9,12 +9,15 @@
9
9
  ## 用法
10
10
 
11
11
  ```
12
+ BWIKI机器人
12
13
  胜率 <角色名> [赛季] [周数] [排序要素] [段位]
13
14
  地图 <地图名> [赛季] [周数] [段位] [阵营]
14
15
  模仿者 <角色名> [日期] [模式]
15
16
  勋章 <角色名> [日期] [模式]
16
17
  ```
17
18
 
19
+ 发送 `BWIKI机器人` 可查看当前支持的指令列表图片;发送 `胜率` 等单独指令可查看对应完整帮助。
20
+
18
21
  ### 胜率(角色胜率)
19
22
 
20
23
  示例:
@@ -83,7 +86,10 @@
83
86
  | `mapDefaultCamp` | 地图查询默认阵营(1=监管者,2=求生者) | `1` |
84
87
  | `showBadge` | 显示勋章分数据 | `true` |
85
88
  | `badgeDays` | 勋章日增长率天数 | `7` |
86
- | `width` | 图片宽度(px) | `1000` |
89
+ | `width` | 图片宽度(px),其他指令未单独设置时使用该值 | `1300` |
90
+ | `mapWidth` | 地图指令图片宽度(px),0=使用默认宽度 | `0` |
91
+ | `mimicWidth` | 模仿者指令图片宽度(px),0=使用默认宽度 | `0` |
92
+ | `badgeWidth` | 勋章指令图片宽度(px),0=使用默认宽度 | `0` |
87
93
  | `scale` | 截图倍率 | `2` |
88
94
  | `timeout` | 网络请求超时(毫秒) | `15000` |
89
95