koishi-plugin-maichuni-scorehelper 0.0.17-test → 0.0.19-test

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.
@@ -7,6 +7,7 @@ export interface Config {
7
7
  mainServiceName: string;
8
8
  statusPageUrl: string;
9
9
  heartbeatApiUrl: string;
10
+ debug?: boolean;
10
11
  }
11
12
  export declare const Config: Schema<Config>;
12
13
  declare module 'koishi' {
@@ -23,6 +24,7 @@ export declare class MaimaiStatus extends Service {
23
24
  private readonly CACHE_FILE;
24
25
  private readonly UA;
25
26
  constructor(ctx: Context, config: Config);
27
+ private logDebug;
26
28
  protected start(): Promise<void>;
27
29
  protected stop(): Promise<void>;
28
30
  /**
@@ -16,7 +16,8 @@ exports.Config = koishi_1.Schema.object({
16
16
  showRecovery: koishi_1.Schema.boolean().default(true).description('是否在服务恢复时也发送通知'),
17
17
  mainServiceName: koishi_1.Schema.string().default('舞萌DX服务').description('主服务名称(去除了后缀的),当该服务下线时,屏蔽其他服务的通知'),
18
18
  statusPageUrl: koishi_1.Schema.string().default(DEFAULT_STATUS_PAGE_URL).description('状态页 URL,默认使用内置反代,可自定义'),
19
- heartbeatApiUrl: koishi_1.Schema.string().default(DEFAULT_HEARTBEAT_API_URL).description('心跳 API URL,默认使用内置反代,可自定义')
19
+ heartbeatApiUrl: koishi_1.Schema.string().default(DEFAULT_HEARTBEAT_API_URL).description('心跳 API URL,默认使用内置反代,可自定义'),
20
+ debug: koishi_1.Schema.boolean().default(false).description('开启后输出详细调试日志')
20
21
  });
21
22
  // 常量配置
22
23
  const FLAP_WINDOW = 10 * 60 * 1000; // 10分钟
@@ -35,6 +36,12 @@ class MaimaiStatus extends koishi_1.Service {
35
36
  this.UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
36
37
  this.clientLogger = ctx.logger('maimai-status');
37
38
  }
39
+ logDebug(...args) {
40
+ if (!this.config.debug)
41
+ return;
42
+ // 当 debug 开启时,使用 info 级别输出,确保默认日志等级也能看到
43
+ this.clientLogger.info.apply(this.clientLogger, ['[debug]', ...args]);
44
+ }
38
45
  async start() {
39
46
  this.clientLogger.info('Maimai status monitor started.');
40
47
  // 立即执行一次
@@ -202,6 +209,8 @@ class MaimaiStatus extends koishi_1.Service {
202
209
  catch (_) { /* ignore timeout */ }
203
210
  // 直接从页面上下文获取数据
204
211
  data = await page.evaluate(() => window.preloadData);
212
+ const bodySnippet = await page.evaluate(() => (document?.body?.innerText || '').slice(0, 50));
213
+ this.logDebug(`[monitor] puppeteer body head: ${bodySnippet}`);
205
214
  }
206
215
  finally {
207
216
  await page.close();
@@ -218,6 +227,7 @@ class MaimaiStatus extends koishi_1.Service {
218
227
  responseType: 'text',
219
228
  headers: { 'User-Agent': this.UA }
220
229
  });
230
+ this.logDebug(`[monitor] http body head: ${String(html).slice(0, 50)}`);
221
231
  const regex = /window\.preloadData\s*=\s*(\{.*?\});/s;
222
232
  const match = html.match(regex);
223
233
  if (match && match[1]) {
@@ -231,6 +241,7 @@ class MaimaiStatus extends koishi_1.Service {
231
241
  }
232
242
  if (data) {
233
243
  const monitorList = [];
244
+ this.logDebug('[monitor] parsed preloadData keys:', Object.keys(data || {}));
234
245
  if (data.config && Array.isArray(data.publicGroupList)) {
235
246
  for (const group of data.publicGroupList) {
236
247
  if (Array.isArray(group.monitorList)) {
@@ -238,6 +249,7 @@ class MaimaiStatus extends koishi_1.Service {
238
249
  }
239
250
  }
240
251
  }
252
+ this.logDebug(`[monitor] monitorList length=${monitorList.length}`);
241
253
  return monitorList;
242
254
  }
243
255
  return [];
@@ -308,6 +320,7 @@ class MaimaiStatus extends koishi_1.Service {
308
320
  await page.goto(url, { waitUntil: 'networkidle0', timeout: 20000 });
309
321
  // 直接读取正文文本(可能被 JS 动态生成)
310
322
  const body = await page.evaluate(() => document.body?.innerText || document.body?.textContent || '');
323
+ this.logDebug(`[heartbeat] puppeteer body head: ${body.slice(0, 50)}`);
311
324
  if (!body)
312
325
  return null;
313
326
  try {
@@ -336,6 +349,7 @@ class MaimaiStatus extends koishi_1.Service {
336
349
  headers: { 'User-Agent': this.UA },
337
350
  timeout: 15000
338
351
  });
352
+ this.logDebug(`[heartbeat] http body head: ${typeof data === 'string' ? data.slice(0, 50) : '[object]'}`);
339
353
  // 如果不是对象,尝试解析 JSON (因为 Object.keys 打印出了索引,说明是字符串)
340
354
  if (typeof data === 'string') {
341
355
  try {
@@ -354,6 +368,7 @@ class MaimaiStatus extends koishi_1.Service {
354
368
  }
355
369
  }
356
370
  // 返回原始对象,包含 heartbeatList 和 uptimeList
371
+ this.logDebug('[heartbeat] parsed keys:', Array.isArray(data) ? `array length ${data.length}` : Object.keys(data || {}));
357
372
  return data || {};
358
373
  }
359
374
  catch (e) {
@@ -417,21 +432,12 @@ class MaimaiStatus extends koishi_1.Service {
417
432
  const isMainServiceOffline = mainServiceCurrentStatus === 'OFFLINE';
418
433
  for (const { group, currentStatus } of updates) {
419
434
  const { groupName, lastStatus, isMuted, muteUntil } = group;
435
+ this.logDebug(`[state] ${groupName}: last=${lastStatus} current=${currentStatus}`);
420
436
  // 检查静音过期
421
437
  if (isMuted && now > muteUntil) {
422
438
  group.isMuted = false;
423
439
  this.clientLogger.info(`[${groupName}] 静音结束,恢复监控通知。`);
424
440
  }
425
- // 0. 初始化或状态无变更的处理
426
- if (lastStatus === 'UNKNOWN') {
427
- // 首次初始化,静默更新
428
- group.lastStatus = currentStatus;
429
- group.consecutiveFailures = 0;
430
- continue;
431
- }
432
- if (currentStatus === lastStatus) {
433
- continue;
434
- }
435
441
  // 如果处于静音状态,只更新状态,不进行通知推送判定
436
442
  if (group.isMuted) {
437
443
  group.lastStatus = currentStatus;
@@ -444,9 +450,11 @@ class MaimaiStatus extends koishi_1.Service {
444
450
  // 静默更新状态,重置连续失败计数以防恢复瞬间误报
445
451
  group.consecutiveFailures = 0;
446
452
  group.lastStatus = currentStatus;
453
+ this.clientLogger.debug(`[${groupName}] 跳过通知:主服务离线,当前状态 ${currentStatus}`);
447
454
  continue;
448
455
  }
449
- // 正常的报警逻辑 (含 3 次确认)
456
+ // 正常的报警逻辑 (含 3 次确认)
457
+ // 注意:即便状态未变化,也需要累计 consecutiveFailures,以便长时间离线也能触发阈值。
450
458
  await this.processNotificationLogic(group, currentStatus);
451
459
  // 更新最后状态
452
460
  group.lastStatus = currentStatus;
@@ -464,12 +472,14 @@ class MaimaiStatus extends koishi_1.Service {
464
472
  const groupName = group.groupName || 'Unknown Service'; // Fallback
465
473
  const isAbnormal = currentStatus === 'OFFLINE' || currentStatus === 'PARTIAL_OFFLINE';
466
474
  const wasAbnormal = group.lastStatus === 'OFFLINE' || group.lastStatus === 'PARTIAL_OFFLINE';
475
+ const isInit = group.lastStatus === 'UNKNOWN';
467
476
  if (isAbnormal) {
468
- // 异常计数 +1
477
+ // 异常计数 +1(初次发现也要记一次)
469
478
  group.consecutiveFailures++;
470
479
  // 只有达到阈值(第3次)时才报警,且之前没有报过(或者这是一个新的连续序列)
471
480
  // 注意:如果已经是第 4, 5 次,保持当前状态不变,不再重复报
472
481
  if (group.consecutiveFailures === FAILURE_THRESHOLD) {
482
+ this.logDebug(`[notify] threshold reached for ${groupName}: last=${group.lastStatus} current=${currentStatus}`);
473
483
  await this.handleStatusChange(groupName, group.lastStatus, currentStatus);
474
484
  }
475
485
  else if (group.consecutiveFailures < FAILURE_THRESHOLD) {
@@ -480,11 +490,16 @@ class MaimaiStatus extends koishi_1.Service {
480
490
  // 当前是 ONLINE
481
491
  if (wasAbnormal && group.consecutiveFailures >= FAILURE_THRESHOLD) {
482
492
  // 之前是异常,且已经报警过了(超过阈值),现在恢复 -> 发送恢复通知
493
+ this.logDebug(`[notify] recovery for ${groupName}: last=${group.lastStatus} -> ONLINE`);
483
494
  await this.handleStatusChange(groupName, group.lastStatus, currentStatus);
484
495
  }
485
496
  // 重置计数
486
497
  group.consecutiveFailures = 0;
487
498
  }
499
+ // 首次初始化状态也要记录,防止后续一直停留在 UNKNOWN 导致不推送
500
+ if (isInit && group.lastStatus === 'UNKNOWN') {
501
+ group.lastStatus = currentStatus;
502
+ }
488
503
  }
489
504
  /**
490
505
  * 状态变更处理接口
@@ -519,6 +534,7 @@ class MaimaiStatus extends koishi_1.Service {
519
534
  }
520
535
  }
521
536
  if (shouldNotify) {
537
+ this.logDebug(`[notify] will push: ${name} ${from} -> ${to}`);
522
538
  // 防抖检测:仅在真正决定推送时记录
523
539
  const group = this.groups.get(name);
524
540
  if (group) {
@@ -549,6 +565,7 @@ class MaimaiStatus extends koishi_1.Service {
549
565
  // 获取 bot 实例发送 (需要确保 context 中有 bot)
550
566
  const bot = this.ctx.bots[0];
551
567
  if (bot) {
568
+ this.logDebug(`[push] send to ${this.config.targetChannelId} level=${level} msgHead=${message.slice(0, 50)}`);
552
569
  await bot.sendMessage(this.config.targetChannelId, message);
553
570
  }
554
571
  else {
@@ -561,6 +578,7 @@ class MaimaiStatus extends koishi_1.Service {
561
578
  }
562
579
  else {
563
580
  this.clientLogger.debug('Notification skipped: targetChannelId not configured.');
581
+ this.logDebug(`[push] skipped push, no target. level=${level} msgHead=${message.slice(0, 50)}`);
564
582
  }
565
583
  // 示例:您也可以在这里添加 HTTP webhook 推送
566
584
  // await this.ctx.http.post('YOUR_WEBHOOK_URL', { content: message })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-maichuni-scorehelper",
3
- "version": "0.0.17-test",
3
+ "version": "0.0.19-test",
4
4
  "description": "【未完成】一些舞萌中二节奏功能",
5
5
  "keywords": [
6
6
  "koishi",
@@ -14,6 +14,7 @@ export interface Config {
14
14
  mainServiceName: string
15
15
  statusPageUrl: string
16
16
  heartbeatApiUrl: string
17
+ debug?: boolean
17
18
  }
18
19
 
19
20
  export const Config: Schema<Config> = Schema.object({
@@ -22,7 +23,8 @@ export const Config: Schema<Config> = Schema.object({
22
23
  showRecovery: Schema.boolean().default(true).description('是否在服务恢复时也发送通知'),
23
24
  mainServiceName: Schema.string().default('舞萌DX服务').description('主服务名称(去除了后缀的),当该服务下线时,屏蔽其他服务的通知'),
24
25
  statusPageUrl: Schema.string().default(DEFAULT_STATUS_PAGE_URL).description('状态页 URL,默认使用内置反代,可自定义'),
25
- heartbeatApiUrl: Schema.string().default(DEFAULT_HEARTBEAT_API_URL).description('心跳 API URL,默认使用内置反代,可自定义')
26
+ heartbeatApiUrl: Schema.string().default(DEFAULT_HEARTBEAT_API_URL).description('心跳 API URL,默认使用内置反代,可自定义'),
27
+ debug: Schema.boolean().default(false).description('开启后输出详细调试日志')
26
28
  })
27
29
 
28
30
  // 定义服务状态枚举
@@ -76,6 +78,12 @@ export class MaimaiStatus extends Service {
76
78
  this.clientLogger = ctx.logger('maimai-status')
77
79
  }
78
80
 
81
+ private logDebug(...args: any[]) {
82
+ if (!this.config.debug) return
83
+ // 当 debug 开启时,使用 info 级别输出,确保默认日志等级也能看到
84
+ this.clientLogger.info.apply(this.clientLogger, ['[debug]', ...args] as any)
85
+ }
86
+
79
87
  protected async start() {
80
88
  this.clientLogger.info('Maimai status monitor started.')
81
89
  // 立即执行一次
@@ -257,6 +265,8 @@ export class MaimaiStatus extends Service {
257
265
 
258
266
  // 直接从页面上下文获取数据
259
267
  data = await page.evaluate(() => (window as any).preloadData)
268
+ const bodySnippet = await page.evaluate(() => (document?.body?.innerText || '').slice(0, 50))
269
+ this.logDebug(`[monitor] puppeteer body head: ${bodySnippet}`)
260
270
  } finally {
261
271
  await page.close()
262
272
  }
@@ -272,6 +282,7 @@ export class MaimaiStatus extends Service {
272
282
  responseType: 'text',
273
283
  headers: { 'User-Agent': this.UA }
274
284
  })
285
+ this.logDebug(`[monitor] http body head: ${String(html).slice(0, 50)}`)
275
286
  const regex = /window\.preloadData\s*=\s*(\{.*?\});/s
276
287
  const match = html.match(regex)
277
288
 
@@ -286,6 +297,7 @@ export class MaimaiStatus extends Service {
286
297
 
287
298
  if (data) {
288
299
  const monitorList: MonitorItem[] = []
300
+ this.logDebug('[monitor] parsed preloadData keys:', Object.keys(data || {}))
289
301
  if (data.config && Array.isArray(data.publicGroupList)) {
290
302
  for (const group of data.publicGroupList) {
291
303
  if (Array.isArray(group.monitorList)) {
@@ -293,6 +305,7 @@ export class MaimaiStatus extends Service {
293
305
  }
294
306
  }
295
307
  }
308
+ this.logDebug(`[monitor] monitorList length=${monitorList.length}`)
296
309
  return monitorList
297
310
  }
298
311
 
@@ -369,6 +382,7 @@ export class MaimaiStatus extends Service {
369
382
  await page.goto(url, { waitUntil: 'networkidle0', timeout: 20000 })
370
383
  // 直接读取正文文本(可能被 JS 动态生成)
371
384
  const body = await page.evaluate(() => document.body?.innerText || document.body?.textContent || '')
385
+ this.logDebug(`[heartbeat] puppeteer body head: ${body.slice(0, 50)}`)
372
386
  if (!body) return null
373
387
  try {
374
388
  return JSON.parse(body)
@@ -391,6 +405,7 @@ export class MaimaiStatus extends Service {
391
405
  headers: { 'User-Agent': this.UA },
392
406
  timeout: 15000
393
407
  })
408
+ this.logDebug(`[heartbeat] http body head: ${typeof data === 'string' ? data.slice(0, 50) : '[object]'}`)
394
409
 
395
410
  // 如果不是对象,尝试解析 JSON (因为 Object.keys 打印出了索引,说明是字符串)
396
411
  if (typeof data === 'string') {
@@ -409,6 +424,7 @@ export class MaimaiStatus extends Service {
409
424
  }
410
425
 
411
426
  // 返回原始对象,包含 heartbeatList 和 uptimeList
427
+ this.logDebug('[heartbeat] parsed keys:', Array.isArray(data) ? `array length ${data.length}` : Object.keys(data || {}))
412
428
  return data || {}
413
429
  } catch (e) {
414
430
  // HTTP 失败也尝试 Puppeteer(例如超时/质询)
@@ -478,6 +494,7 @@ export class MaimaiStatus extends Service {
478
494
 
479
495
  for (const { group, currentStatus } of updates) {
480
496
  const { groupName, lastStatus, isMuted, muteUntil } = group
497
+ this.logDebug(`[state] ${groupName}: last=${lastStatus} current=${currentStatus}`)
481
498
 
482
499
  // 检查静音过期
483
500
  if (isMuted && now > muteUntil) {
@@ -485,18 +502,6 @@ export class MaimaiStatus extends Service {
485
502
  this.clientLogger.info(`[${groupName}] 静音结束,恢复监控通知。`)
486
503
  }
487
504
 
488
- // 0. 初始化或状态无变更的处理
489
- if (lastStatus === 'UNKNOWN') {
490
- // 首次初始化,静默更新
491
- group.lastStatus = currentStatus
492
- group.consecutiveFailures = 0
493
- continue
494
- }
495
-
496
- if (currentStatus === lastStatus) {
497
- continue
498
- }
499
-
500
505
  // 如果处于静音状态,只更新状态,不进行通知推送判定
501
506
  if (group.isMuted) {
502
507
  group.lastStatus = currentStatus
@@ -511,10 +516,12 @@ export class MaimaiStatus extends Service {
511
516
  // 静默更新状态,重置连续失败计数以防恢复瞬间误报
512
517
  group.consecutiveFailures = 0
513
518
  group.lastStatus = currentStatus
519
+ this.clientLogger.debug(`[${groupName}] 跳过通知:主服务离线,当前状态 ${currentStatus}`)
514
520
  continue
515
521
  }
516
522
 
517
- // 正常的报警逻辑 (含 3 次确认)
523
+ // 正常的报警逻辑 (含 3 次确认)
524
+ // 注意:即便状态未变化,也需要累计 consecutiveFailures,以便长时间离线也能触发阈值。
518
525
  await this.processNotificationLogic(group, currentStatus)
519
526
 
520
527
  // 更新最后状态
@@ -537,14 +544,16 @@ export class MaimaiStatus extends Service {
537
544
 
538
545
  const isAbnormal = currentStatus === 'OFFLINE' || currentStatus === 'PARTIAL_OFFLINE'
539
546
  const wasAbnormal = group.lastStatus === 'OFFLINE' || group.lastStatus === 'PARTIAL_OFFLINE'
547
+ const isInit = group.lastStatus === 'UNKNOWN'
540
548
 
541
549
  if (isAbnormal) {
542
- // 异常计数 +1
550
+ // 异常计数 +1(初次发现也要记一次)
543
551
  group.consecutiveFailures++
544
552
 
545
553
  // 只有达到阈值(第3次)时才报警,且之前没有报过(或者这是一个新的连续序列)
546
554
  // 注意:如果已经是第 4, 5 次,保持当前状态不变,不再重复报
547
555
  if (group.consecutiveFailures === FAILURE_THRESHOLD) {
556
+ this.logDebug(`[notify] threshold reached for ${groupName}: last=${group.lastStatus} current=${currentStatus}`)
548
557
  await this.handleStatusChange(groupName, group.lastStatus, currentStatus)
549
558
  } else if (group.consecutiveFailures < FAILURE_THRESHOLD) {
550
559
  this.clientLogger.debug(`[${groupName}] 异常计数 ${group.consecutiveFailures}/${FAILURE_THRESHOLD},暂不推送。`)
@@ -553,11 +562,17 @@ export class MaimaiStatus extends Service {
553
562
  // 当前是 ONLINE
554
563
  if (wasAbnormal && group.consecutiveFailures >= FAILURE_THRESHOLD) {
555
564
  // 之前是异常,且已经报警过了(超过阈值),现在恢复 -> 发送恢复通知
565
+ this.logDebug(`[notify] recovery for ${groupName}: last=${group.lastStatus} -> ONLINE`)
556
566
  await this.handleStatusChange(groupName, group.lastStatus, currentStatus)
557
567
  }
558
568
  // 重置计数
559
569
  group.consecutiveFailures = 0
560
570
  }
571
+
572
+ // 首次初始化状态也要记录,防止后续一直停留在 UNKNOWN 导致不推送
573
+ if (isInit && group.lastStatus === 'UNKNOWN') {
574
+ group.lastStatus = currentStatus
575
+ }
561
576
  }
562
577
 
563
578
  /**
@@ -594,6 +609,7 @@ export class MaimaiStatus extends Service {
594
609
  }
595
610
 
596
611
  if (shouldNotify) {
612
+ this.logDebug(`[notify] will push: ${name} ${from} -> ${to}`)
597
613
  // 防抖检测:仅在真正决定推送时记录
598
614
  const group = this.groups.get(name)
599
615
  if (group) {
@@ -627,6 +643,7 @@ export class MaimaiStatus extends Service {
627
643
  // 获取 bot 实例发送 (需要确保 context 中有 bot)
628
644
  const bot = this.ctx.bots[0]
629
645
  if (bot) {
646
+ this.logDebug(`[push] send to ${this.config.targetChannelId} level=${level} msgHead=${message.slice(0,50)}`)
630
647
  await bot.sendMessage(this.config.targetChannelId, message)
631
648
  } else {
632
649
  this.clientLogger.warn('No bot available to send notification.')
@@ -636,6 +653,7 @@ export class MaimaiStatus extends Service {
636
653
  }
637
654
  } else {
638
655
  this.clientLogger.debug('Notification skipped: targetChannelId not configured.')
656
+ this.logDebug(`[push] skipped push, no target. level=${level} msgHead=${message.slice(0,50)}`)
639
657
  }
640
658
 
641
659
  // 示例:您也可以在这里添加 HTTP webhook 推送