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
|
@@ -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 推送
|