mioku-plugin-help 2.1.0 → 2.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mioku-plugin-help",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "帮助插件,使用截图服务生成美观的帮助图片,并提供 #状态 指令",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -17,6 +17,18 @@
17
17
  "screenshot",
18
18
  "config",
19
19
  "ai"
20
+ ],
21
+ "accessHooks": [
22
+ {
23
+ "id": "帮助菜单",
24
+ "match": "/(?:[#/]\\s*(?:帮助|菜单|help)|^(?:帮助|菜单|help)(?:\\s|$))/",
25
+ "description": "匹配 #help / 帮助 / 菜单 等帮助指令"
26
+ },
27
+ {
28
+ "id": "状态",
29
+ "match": "/(?:[#/]\\s*(?:状态|zt|status)|^(?:状态|zt|status)(?:\\s|$))/",
30
+ "description": "匹配 #状态 / 状态 / zt / status 等系统状态指令"
31
+ }
20
32
  ]
21
33
  },
22
34
  "peerDependencies": {
@@ -67,6 +67,71 @@ function safeNumber(value: unknown, fallback = 0): number {
67
67
  return Number.isFinite(n) ? n : fallback;
68
68
  }
69
69
 
70
+ /**
71
+ * Snapshot of the system's CPU tick counters at a point in time. The
72
+ * sum covers every core, so a 4-core box that's fully loaded registers
73
+ * `total = 4 * elapsed_in_ticks`. `idle` is the slice spent idle.
74
+ */
75
+ interface CpuTickSample {
76
+ idle: number;
77
+ total: number;
78
+ ts: number;
79
+ }
80
+
81
+ function sampleCpuTicks(): CpuTickSample {
82
+ let idle = 0;
83
+ let total = 0;
84
+ for (const cpu of os.cpus()) {
85
+ const t = cpu.times;
86
+ idle += t.idle;
87
+ total += t.user + t.nice + t.sys + t.idle + t.irq;
88
+ }
89
+ return { idle, total, ts: Date.now() };
90
+ }
91
+
92
+ /**
93
+ * Baseline sample taken at module load. We seed the cache eagerly so the
94
+ * first `#状态` call (whenever it happens) already has a comparison
95
+ * point — without this, the first call after a long idle period would
96
+ * show 0% even if the system is under load.
97
+ */
98
+ let prevCpuTicks: CpuTickSample = sampleCpuTicks();
99
+
100
+ /**
101
+ * If the gap between two samples is larger than this, the previous
102
+ * sample is too stale to produce a meaningful delta (e.g. the user
103
+ * called `#状态` once, then came back an hour later). Discard and
104
+ * start a fresh baseline.
105
+ */
106
+ const CPU_SAMPLE_MAX_GAP_MS = 30_000;
107
+
108
+ /**
109
+ * System-wide average CPU utilization between the previous sample and
110
+ * now. Returns 0 on the first call after a baseline reset, and a value
111
+ * in [0, 100] on subsequent calls.
112
+ *
113
+ * `os.cpus()` already sums ticks across all cores, so the result is
114
+ * the per-core average — no need to divide by `cpuCores` (the old
115
+ * `process.cpuUsage()` math did that because it summed its own usage
116
+ * across cores; same destination, different source).
117
+ */
118
+ function computeSystemCpuPercent(): number {
119
+ const current = sampleCpuTicks();
120
+ const gap = current.ts - prevCpuTicks.ts;
121
+ if (gap > CPU_SAMPLE_MAX_GAP_MS || gap <= 0) {
122
+ prevCpuTicks = current;
123
+ return 0;
124
+ }
125
+ const idleDiff = current.idle - prevCpuTicks.idle;
126
+ const totalDiff = current.total - prevCpuTicks.total;
127
+ prevCpuTicks = current;
128
+ if (totalDiff <= 0) {
129
+ return 0;
130
+ }
131
+ const percent = 100 - (idleDiff / totalDiff) * 100;
132
+ return Math.max(0, Math.min(100, percent));
133
+ }
134
+
70
135
  /** Detect the active JS runtime and its version. */
71
136
  function detectRuntime(): { name: string; version: string } {
72
137
  if (process.versions.bun) {
@@ -160,7 +225,6 @@ async function collectBotStatuses(
160
225
 
161
226
  // 并行拿 OneBot API 的 status / group / friend / version
162
227
  let online = true;
163
- let onlineDurationMs = 0;
164
228
  let groupCount = 0;
165
229
  let friendCount = 0;
166
230
  let appVersion = "unknown";
@@ -183,20 +247,7 @@ async function collectBotStatuses(
183
247
  ).catch(() => null),
184
248
  ]);
185
249
  if (statusResult.status === "fulfilled" && statusResult.value) {
186
- const status = statusResult.value;
187
- // napcat-sdk 的 api() 已经把 OneBot v11 响应的 data 字段解包出来,
188
- const startTs = safeNumber(status?.stat?.start_time);
189
- if (startTs > 0) {
190
- // OneBot 约定:start_time 是 unix 秒;> 1e12 视为毫秒
191
- const startMs = startTs > 1e12 ? startTs : startTs * 1000;
192
- onlineDurationMs = Math.max(0, Date.now() - startMs);
193
- }
194
- if (typeof status?.online === "boolean") {
195
- online = status.online;
196
- } else if (typeof status?.good === "boolean") {
197
- // go-cqhttp / LLOneBot 用 good 表示总体健康
198
- online = status.good;
199
- }
250
+ online = statusResult.value.online;
200
251
  }
201
252
  if (versionResult.status === "fulfilled" && versionResult.value) {
202
253
  const v = versionResult.value;
@@ -232,7 +283,6 @@ async function collectBotStatuses(
232
283
  online,
233
284
  groupCount,
234
285
  friendCount,
235
- onlineDurationMs,
236
286
  send: counts.send,
237
287
  receive: counts.receive,
238
288
  });
@@ -315,16 +365,11 @@ async function collectResources(): Promise<ResourceStatus> {
315
365
  speedMhz >= 1000
316
366
  ? `${(speedMhz / 1000).toFixed(1)} GHz`
317
367
  : `${Math.round(speedMhz)} MHz`;
318
- // CPU% requires a 2nd sample; we compute the delta from a previous tick.
319
- // Keep it simple here: estimate from process.cpuUsage() relative to
320
- // wall-clock since start. The webui module re-samples with sleep; for the
321
- // status panel we accept a less precise reading.
322
- const cpuUser = process.cpuUsage();
323
- const cpuTotalUsec = cpuUser.user + cpuUser.system;
324
- const cpuPercent = Math.min(
325
- 100,
326
- ((cpuTotalUsec / 1e6 / Math.max(1, process.uptime())) * 100) / cpuCores,
327
- );
368
+ // System-wide CPU% from a 2-sample delta. `collectSnapshot` has a 2s
369
+ // TTL, so under normal use we'll have a fresh reading each call. The
370
+ // first call after a baseline reset returns 0 (sentinel) — see
371
+ // `computeSystemCpuPercent` for the gap-discard policy.
372
+ const cpuPercent = computeSystemCpuPercent();
328
373
 
329
374
  // systeminformation.mem() gives buffcache and swap fields that os can't.
330
375
  // Wrap in withTimeout so a single slow call doesn't stall the snapshot.
@@ -71,23 +71,6 @@ function fmtBps(bps: number): string {
71
71
  return `${fmtBytes(bps)}/s`;
72
72
  }
73
73
 
74
- function fmtUptime(ms: number): string {
75
- if (!Number.isFinite(ms) || ms <= 0) {
76
- return "—";
77
- }
78
- const sec = Math.floor(ms / 1000);
79
- const days = Math.floor(sec / 86400);
80
- const hours = Math.floor((sec % 86400) / 3600);
81
- const minutes = Math.floor((sec % 3600) / 60);
82
- if (days > 0) {
83
- return `${days}天${String(hours).padStart(2, "0")}时${String(minutes).padStart(2, "0")}分`;
84
- }
85
- if (hours > 0) {
86
- return `${hours}时${String(minutes).padStart(2, "0")}分`;
87
- }
88
- return `${minutes}分`;
89
- }
90
-
91
74
  function progressBar(percent: number, color: string): string {
92
75
  const clamped = Math.max(0, Math.min(100, percent));
93
76
  return `<div class="status-bar"><div class="status-bar__fill" style="width:${clamped}%;background:${color};"></div></div>`;
@@ -162,7 +145,6 @@ function renderHero(
162
145
  { text: statusText, kind: bot.online ? "ok" : "danger" },
163
146
  { text: `好友 ${fmtNumber(bot.friendCount)}`, kind: "data" },
164
147
  { text: `群聊 ${fmtNumber(bot.groupCount)}`, kind: "data" },
165
- { text: `运行时长 ${fmtUptime(bot.onlineDurationMs)}`, kind: "data" },
166
148
  { text: `收 ${fmtNumber(bot.receive)}`, kind: "data" },
167
149
  { text: `发 ${fmtNumber(bot.send)}`, kind: "data" },
168
150
  { text: frameworkText, kind: "data" },
package/status/types.ts CHANGED
@@ -19,8 +19,6 @@ export interface BotAccountStatus {
19
19
  online: boolean;
20
20
  groupCount: number;
21
21
  friendCount: number;
22
- /** Computed from `bot.api("get_status").stat.start_time`. 0 if unavailable. */
23
- onlineDurationMs: number;
24
22
  send: number;
25
23
  receive: number;
26
24
  }
@@ -37,18 +35,13 @@ export interface OneBotVersionInfoData {
37
35
 
38
36
  /**
39
37
  * OneBot v11 `get_status` payload (already unwrapped by napcat-sdk).
40
- * Different implementations expose different subsets:
41
- * - NapCat / go-cqhttp: `online`, `good`, `stat.start_time`
42
- * - LLOneBot: same shape
38
+ * Per napcat 官方文档: `{ online, good, stat }` —— `stat` 是空对象,
39
+ * 不包含 `start_time` 或任何统计字段。本接口只声明文档承诺的字段。
43
40
  */
44
41
  export interface OneBotStatusData {
45
- online?: boolean;
46
- good?: boolean;
47
- stat?: {
48
- start_time?: number;
49
- [key: string]: unknown;
50
- };
51
- [key: string]: unknown;
42
+ online: boolean;
43
+ good: boolean;
44
+ stat: Record<string, never>;
52
45
  }
53
46
 
54
47
  /** Subset of mioku's `AIService.getUsageSummary` payload that we actually render. */