mioku-plugin-help 2.3.3 → 3.0.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/config.md +18 -0
- package/help/html-generator.ts +1 -1
- package/help/image.ts +7 -17
- package/help/intent.ts +1 -1
- package/help/role.ts +3 -8
- package/index.ts +8 -2
- package/package.json +6 -5
- package/status/config.ts +17 -0
- package/status/data-collector.ts +77 -66
- package/status/html-generator.ts +6 -3
- package/status/image.ts +2 -2
- package/status/intent.ts +1 -1
- package/status/network-sampler.ts +1 -1
- package/status/types.ts +5 -2
- package/utils.ts +3 -3
package/config.md
CHANGED
|
@@ -8,6 +8,12 @@ fields:
|
|
|
8
8
|
description: 最基础的单行文本输入。
|
|
9
9
|
placeholder: 输入任意文本
|
|
10
10
|
|
|
11
|
+
- key: status.stdinAvatar
|
|
12
|
+
label: stdin 头像
|
|
13
|
+
type: text
|
|
14
|
+
description: stdin 适配器在状态图中使用的头像,支持 http(s):// 或 file:// URL,也支持本地绝对路径(自动转 file://)。默认使用 uapis.cn 随机表情接口。
|
|
15
|
+
placeholder: https://uapis.cn/api/v1/random/image?category=bq&type=eciyuan
|
|
16
|
+
|
|
11
17
|
- key: demo.secretValue
|
|
12
18
|
label: secret 密文输入
|
|
13
19
|
type: secret
|
|
@@ -192,4 +198,16 @@ config: demo
|
|
|
192
198
|
|
|
193
199
|
用法:`mioku-file` 会直接把某个配置文件完整显示为 JSON 编辑器,适合调试或保底兜底。
|
|
194
200
|
|
|
201
|
+
## 7. `status` 状态图配置
|
|
202
|
+
|
|
203
|
+
```mioku-field
|
|
204
|
+
key: status.stdinAvatar
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`status` 配置文件(`config/help/status.json`)目前支持:
|
|
208
|
+
|
|
209
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
210
|
+
| --- | --- | --- | --- |
|
|
211
|
+
| `stdinAvatar` | string | `https://uapis.cn/api/v1/random/image?category=bq&type=eciyuan` | stdin 适配器在状态图中的头像,支持 http(s):// / file:// URL 或本地绝对路径(自动转 file://) |
|
|
212
|
+
|
|
195
213
|
---
|
package/help/html-generator.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { CommandRole, PluginHelp } from "mioku";
|
|
15
|
-
import { botConfig } from "
|
|
15
|
+
import { botConfig } from "mioku";
|
|
16
16
|
import { escapeHtml } from "../utils";
|
|
17
17
|
import { getHelpTheme, HELP_BACKGROUND_IMAGE_URL } from "../theme";
|
|
18
18
|
import { getRenderableEntries } from "./intent";
|
package/help/image.ts
CHANGED
|
@@ -115,11 +115,9 @@ export function resolveHelpBotProfile(
|
|
|
115
115
|
const fallbackNickname = "Mioku Bot";
|
|
116
116
|
const selfId = event?.self_id;
|
|
117
117
|
const bot =
|
|
118
|
-
|
|
119
|
-
? ctx.pickBot(selfId)
|
|
120
|
-
: null) ||
|
|
118
|
+
event?.bot ??
|
|
121
119
|
(ctx?.bots instanceof Map ? Array.from(ctx.bots.values())[0] : null);
|
|
122
|
-
const botId = selfId || bot?.
|
|
120
|
+
const botId = selfId || bot?.bot_id;
|
|
123
121
|
const botNickname = bot?.nickname || bot?.name || fallbackNickname;
|
|
124
122
|
const botAvatarUrl = botId
|
|
125
123
|
? `https://q1.qlogo.cn/g?b=qq&nk=${botId}&s=640`
|
|
@@ -160,8 +158,8 @@ export async function replyWithImage(
|
|
|
160
158
|
}
|
|
161
159
|
|
|
162
160
|
/**
|
|
163
|
-
* Send an image from inside an AI skill handler.
|
|
164
|
-
* `
|
|
161
|
+
* Send an image from inside an AI skill handler. Uses the event-bound bot
|
|
162
|
+
* (`event.bot`), supports an optional quote-reply, and falls
|
|
165
163
|
* back to base64 when the adapter refuses a raw path.
|
|
166
164
|
*/
|
|
167
165
|
export async function sendImageFromSkillContext(options: {
|
|
@@ -171,28 +169,20 @@ export async function sendImageFromSkillContext(options: {
|
|
|
171
169
|
quoteReply?: boolean;
|
|
172
170
|
}): Promise<void> {
|
|
173
171
|
const { ctx, event, imagePath, quoteReply = false } = options;
|
|
174
|
-
const
|
|
175
|
-
const bot =
|
|
176
|
-
selfId != null && typeof ctx?.pickBot === "function"
|
|
177
|
-
? ctx.pickBot(selfId)
|
|
178
|
-
: undefined;
|
|
172
|
+
const bot = event?.bot;
|
|
179
173
|
|
|
180
174
|
if (!bot) {
|
|
181
175
|
throw new Error("当前上下文不支持发送图片");
|
|
182
176
|
}
|
|
183
177
|
|
|
184
178
|
const buildImageSegment = (file: string) => {
|
|
185
|
-
|
|
186
|
-
if (ctx?.segment?.image) {
|
|
187
|
-
return ctx.segment.image(normalizedFile);
|
|
188
|
-
}
|
|
189
|
-
return { type: "image", file: normalizedFile };
|
|
179
|
+
return ctx.segment.image(normalizeImageSource(file));
|
|
190
180
|
};
|
|
191
181
|
|
|
192
182
|
const sendPayload = async (file: string) => {
|
|
193
183
|
const payload: any[] = [];
|
|
194
184
|
if (quoteReply && event?.message_id != null) {
|
|
195
|
-
payload.push(
|
|
185
|
+
payload.push(ctx.segment.reply(event.message_id));
|
|
196
186
|
}
|
|
197
187
|
payload.push(buildImageSegment(file));
|
|
198
188
|
|
package/help/intent.ts
CHANGED
|
@@ -24,7 +24,7 @@ function sanitizeKeyword(value: string): string {
|
|
|
24
24
|
return String(value || "")
|
|
25
25
|
.trim()
|
|
26
26
|
.replace(/^[#/\s]+/, "")
|
|
27
|
-
.replace(/[
|
|
27
|
+
.replace(/[。.!!??,,::;);]+$/g, "");
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/** Lowercase + strip everything that isn't a-z, 0-9, or CJK. */
|
package/help/role.ts
CHANGED
|
@@ -66,15 +66,10 @@ export async function resolveViewerRole(
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
if (event?.group_id != null && event?.user_id != null) {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
const bot =
|
|
72
|
-
selfId != null && typeof ctx?.pickBot === "function"
|
|
73
|
-
? ctx.pickBot(selfId)
|
|
74
|
-
: undefined;
|
|
75
|
-
if (bot && typeof bot.getGroupMemberInfo === "function") {
|
|
69
|
+
const bot = event?.bot;
|
|
70
|
+
if (bot) {
|
|
76
71
|
try {
|
|
77
|
-
const info = await bot.
|
|
72
|
+
const info = await bot.getMemberInfo(
|
|
78
73
|
event.group_id,
|
|
79
74
|
event.user_id,
|
|
80
75
|
);
|
package/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { definePlugin, type
|
|
1
|
+
import { definePlugin, type MiokuContext } from "mioku";
|
|
2
2
|
import { getService, Services } from "mioku";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import { HELP_DEMO_CONFIG } from "./demo-config";
|
|
5
|
+
import { HELP_STATUS_DEFAULT_CONFIG } from "./status/config";
|
|
5
6
|
import {
|
|
6
7
|
generateHelpImage,
|
|
7
8
|
replyWithImage,
|
|
@@ -23,7 +24,7 @@ const helpPlugin = definePlugin({
|
|
|
23
24
|
version: "2.1.0",
|
|
24
25
|
description: "帮助插件,生成帮助图片,并提供 #状态 指令",
|
|
25
26
|
|
|
26
|
-
async setup(ctx:
|
|
27
|
+
async setup(ctx: MiokuContext) {
|
|
27
28
|
// 启动后台采样器
|
|
28
29
|
networkSampler.start();
|
|
29
30
|
perfMonitor.start();
|
|
@@ -48,6 +49,11 @@ const helpPlugin = definePlugin({
|
|
|
48
49
|
|
|
49
50
|
if (configService) {
|
|
50
51
|
await configService.registerConfig("help", "demo", HELP_DEMO_CONFIG.demo);
|
|
52
|
+
await configService.registerConfig(
|
|
53
|
+
"help",
|
|
54
|
+
"status",
|
|
55
|
+
HELP_STATUS_DEFAULT_CONFIG,
|
|
56
|
+
);
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
// 注册 help manifest,让 `#help 状态` 能命中
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mioku-plugin-help",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "帮助插件,使用截图服务生成美观的帮助图片,并提供 #状态 指令",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -32,11 +32,12 @@
|
|
|
32
32
|
]
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"mioku": "^0.
|
|
36
|
-
|
|
35
|
+
"mioku": "^1.0.0"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"systeminformation": "^5.31.6"
|
|
37
39
|
},
|
|
38
40
|
"devDependencies": {
|
|
39
|
-
"mioku": "workspace:*"
|
|
40
|
-
"mioki": "^0.16.0"
|
|
41
|
+
"mioku": "workspace:*"
|
|
41
42
|
}
|
|
42
43
|
}
|
package/status/config.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* help 插件状态图配置(config-service 注册名为 "help/status")。
|
|
3
|
+
* 配置文件位于 `<项目>/config/help/status.json`,可热重载。
|
|
4
|
+
*/
|
|
5
|
+
export interface HelpStatusConfig {
|
|
6
|
+
/**
|
|
7
|
+
* stdin 适配器在状态图中使用的头像。
|
|
8
|
+
* 支持 http(s):// 或 file:// URL,也支持本地绝对路径(自动转换为 file://)。
|
|
9
|
+
* 默认使用 uapis.cn 的随机表情接口。
|
|
10
|
+
*/
|
|
11
|
+
stdinAvatar?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const HELP_STATUS_DEFAULT_CONFIG: HelpStatusConfig = {
|
|
15
|
+
// 默认使用 uapis.cn 的二次元表情随机接口(每次刷新头像都不同)
|
|
16
|
+
stdinAvatar: "https://uapis.cn/api/v1/random/image?category=bq&type=eciyuan",
|
|
17
|
+
};
|
package/status/data-collector.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import systemInfo from "systeminformation";
|
|
3
4
|
import {
|
|
4
5
|
connectedBots,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "
|
|
10
|
-
import type { AIService, MiokiContext } from "mioku";
|
|
6
|
+
getPluginMetadataList,
|
|
7
|
+
getService,
|
|
8
|
+
Services,
|
|
9
|
+
} from "mioku";
|
|
10
|
+
import type { AIService, Bot, MiokuContext } from "mioku";
|
|
11
11
|
import { getRenderVersions } from "../utils";
|
|
12
12
|
import { perfMonitor } from "./performance-monitor";
|
|
13
13
|
import { networkSampler } from "./network-sampler";
|
|
14
|
+
import type { HelpStatusConfig } from "./config";
|
|
14
15
|
import type {
|
|
15
16
|
AIUsageStatsLite,
|
|
16
17
|
AIUsageSummary,
|
|
@@ -185,45 +186,47 @@ function formatDuration(ms: number): string {
|
|
|
185
186
|
return `${minutes}分${String(seconds).padStart(2, "0")}秒`;
|
|
186
187
|
}
|
|
187
188
|
|
|
188
|
-
async function collectBots(
|
|
189
|
+
async function collectBots(
|
|
190
|
+
ctx: MiokuContext,
|
|
191
|
+
stdinAvatar?: string,
|
|
192
|
+
): Promise<BotAccountStatus[]> {
|
|
189
193
|
const bots = Array.from(connectedBots.values());
|
|
190
194
|
if (bots.length === 0) {
|
|
191
195
|
return [];
|
|
192
196
|
}
|
|
193
|
-
|
|
197
|
+
// stdin 适配器恒排第一位(终端是本机主人通道,优先展示)
|
|
198
|
+
const sorted = [...bots].sort(
|
|
199
|
+
(a, b) =>
|
|
200
|
+
Number(b.adapter === "stdin") - Number(a.adapter === "stdin"),
|
|
201
|
+
);
|
|
202
|
+
return collectBotStatuses(sorted, ctx, stdinAvatar);
|
|
194
203
|
}
|
|
195
204
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const miokiStatus = await withTimeout(
|
|
202
|
-
Promise.resolve().then(() => getMiokiStatus(bots)),
|
|
203
|
-
);
|
|
204
|
-
if (Array.isArray(miokiStatus?.bots)) {
|
|
205
|
-
for (const b of miokiStatus.bots) {
|
|
206
|
-
const u = safeNumber(b?.uin);
|
|
207
|
-
if (u > 0) {
|
|
208
|
-
perBotCounts.set(u, {
|
|
209
|
-
send: safeNumber(b?.send),
|
|
210
|
-
receive: safeNumber(b?.receive),
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
} catch {
|
|
216
|
-
// ignore
|
|
205
|
+
/** 把本地绝对路径 / http(s) / file:// 统一成可被截图服务加载的图片地址 */
|
|
206
|
+
function toImageSrc(value: string): string {
|
|
207
|
+
if (/^(https?:|file:|data:)/i.test(value)) return value;
|
|
208
|
+
if (value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value)) {
|
|
209
|
+
return `file://${value}`;
|
|
217
210
|
}
|
|
211
|
+
return value;
|
|
212
|
+
}
|
|
218
213
|
|
|
214
|
+
async function collectBotStatuses(
|
|
215
|
+
bots: readonly Bot[],
|
|
216
|
+
ctx: MiokuContext,
|
|
217
|
+
stdinAvatar?: string,
|
|
218
|
+
): Promise<BotAccountStatus[]> {
|
|
219
219
|
const results: BotAccountStatus[] = [];
|
|
220
220
|
for (const bot of bots) {
|
|
221
|
-
const uin =
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
const
|
|
221
|
+
const uin = String(bot.bot_id);
|
|
222
|
+
const adapter = String(bot.adapter);
|
|
223
|
+
const isStdin = adapter === "stdin";
|
|
224
|
+
const nickname = String(bot.nickname || "Unknown Bot");
|
|
225
|
+
let framework = isStdin ? adapter : "unknown";
|
|
226
|
+
const avatarUrl = isStdin
|
|
227
|
+
? toImageSrc(stdinAvatar || "")
|
|
228
|
+
: `https://q1.qlogo.cn/g?b=qq&nk=${uin}&s=160`;
|
|
225
229
|
|
|
226
|
-
// 并行拿 OneBot API 的 status / group / friend / version
|
|
227
230
|
let online = true;
|
|
228
231
|
let groupCount = 0;
|
|
229
232
|
let friendCount = 0;
|
|
@@ -231,9 +234,9 @@ async function collectBotStatuses(
|
|
|
231
234
|
let protocolVersion = "unknown";
|
|
232
235
|
const [statusResult, groupsResult, friendsResult, versionResult] =
|
|
233
236
|
await Promise.allSettled([
|
|
234
|
-
withTimeout(
|
|
235
|
-
|
|
236
|
-
)
|
|
237
|
+
withTimeout(Promise.resolve().then(() => bot.sendApi<OneBotStatusData>("get_status"))).catch(
|
|
238
|
+
() => null,
|
|
239
|
+
),
|
|
237
240
|
withTimeout(Promise.resolve().then(() => bot.getGroupList())).catch(
|
|
238
241
|
() => [],
|
|
239
242
|
),
|
|
@@ -241,16 +244,18 @@ async function collectBotStatuses(
|
|
|
241
244
|
() => [],
|
|
242
245
|
),
|
|
243
246
|
withTimeout(
|
|
244
|
-
Promise.resolve().then(() =>
|
|
245
|
-
bot.api<OneBotVersionInfoData>("get_version_info"),
|
|
246
|
-
),
|
|
247
|
+
Promise.resolve().then(() => bot.sendApi<OneBotVersionInfoData>("get_version_info")),
|
|
247
248
|
).catch(() => null),
|
|
248
249
|
]);
|
|
249
250
|
if (statusResult.status === "fulfilled" && statusResult.value) {
|
|
250
|
-
online = statusResult.value.online;
|
|
251
|
+
online = Boolean(statusResult.value.online);
|
|
251
252
|
}
|
|
252
|
-
if (
|
|
253
|
+
if (isStdin) {
|
|
254
|
+
// 终端适配器没有 OneBot 版本信息,直接用适配器包版本
|
|
255
|
+
appVersion = String(ctx.getAdapter(adapter)?.version ?? "unknown");
|
|
256
|
+
} else if (versionResult.status === "fulfilled" && versionResult.value) {
|
|
253
257
|
const v = versionResult.value;
|
|
258
|
+
framework = String(v.app_name || "").trim() || framework;
|
|
254
259
|
if (v.app_version && v.app_version.trim()) {
|
|
255
260
|
appVersion = v.app_version.trim();
|
|
256
261
|
}
|
|
@@ -271,27 +276,26 @@ async function collectBotStatuses(
|
|
|
271
276
|
friendCount = friendsResult.value.length;
|
|
272
277
|
}
|
|
273
278
|
|
|
274
|
-
const counts = perBotCounts.get(uin) || { send: 0, receive: 0 };
|
|
275
|
-
|
|
276
279
|
results.push({
|
|
277
280
|
uin,
|
|
278
281
|
nickname,
|
|
279
282
|
avatarUrl,
|
|
283
|
+
adapter,
|
|
280
284
|
framework,
|
|
281
285
|
appVersion,
|
|
282
286
|
protocolVersion,
|
|
283
287
|
online,
|
|
284
288
|
groupCount,
|
|
285
289
|
friendCount,
|
|
286
|
-
send:
|
|
287
|
-
receive:
|
|
290
|
+
send: 0,
|
|
291
|
+
receive: 0,
|
|
288
292
|
});
|
|
289
293
|
}
|
|
290
294
|
return results;
|
|
291
295
|
}
|
|
292
296
|
|
|
293
297
|
async function collectFramework(
|
|
294
|
-
rawBots:
|
|
298
|
+
rawBots: readonly Bot[],
|
|
295
299
|
botStatuses: BotAccountStatus[],
|
|
296
300
|
): Promise<FrameworkStatus> {
|
|
297
301
|
const adapters = new Set<string>();
|
|
@@ -301,28 +305,22 @@ async function collectFramework(
|
|
|
301
305
|
}
|
|
302
306
|
}
|
|
303
307
|
|
|
304
|
-
// 从 mioki 内部服务读 plugins / versions
|
|
305
|
-
let miokiStatus: MiokiStatus | null = null;
|
|
306
|
-
try {
|
|
307
|
-
miokiStatus = await withTimeout(
|
|
308
|
-
Promise.resolve().then(() => getMiokiStatus(rawBots)),
|
|
309
|
-
);
|
|
310
|
-
} catch {
|
|
311
|
-
// ignore
|
|
312
|
-
}
|
|
313
|
-
|
|
314
308
|
const runtime = detectRuntime();
|
|
315
309
|
// Read both versions from the same `getRenderVersions` helper that the
|
|
316
|
-
// help panel uses
|
|
317
|
-
// when `mioki` is only installed under `mioku/node_modules`.
|
|
310
|
+
// help panel uses.
|
|
318
311
|
const { miokiVersion, miokuVersion } = await getRenderVersions();
|
|
312
|
+
const pluginCount = getPluginMetadataList().length;
|
|
313
|
+
// 优先取第一个非 stdin 的 bot 作为平台框架版本(stdin 是本地终端,无 NapCat)
|
|
314
|
+
const primaryFramework =
|
|
315
|
+
botStatuses.find((b) => b.adapter !== "stdin")?.framework ??
|
|
316
|
+
botStatuses[0]?.framework ??
|
|
317
|
+
"unknown";
|
|
319
318
|
return {
|
|
320
319
|
miokuVersion,
|
|
321
320
|
miokiVersion,
|
|
322
|
-
napcatVersion:
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
pluginEnabled: safeNumber(miokiStatus?.plugins?.enabled),
|
|
321
|
+
napcatVersion: primaryFramework,
|
|
322
|
+
pluginCount,
|
|
323
|
+
pluginEnabled: pluginCount,
|
|
326
324
|
adapterCount: adapters.size,
|
|
327
325
|
onlineBotCount: botStatuses.filter((b) => b.online).length,
|
|
328
326
|
uptimeMs: safeNumber(process.uptime()) * 1000,
|
|
@@ -632,7 +630,7 @@ async function collectSystem(): Promise<SystemInfo> {
|
|
|
632
630
|
};
|
|
633
631
|
}
|
|
634
632
|
|
|
635
|
-
async function collectAI(ctx:
|
|
633
|
+
async function collectAI(ctx: MiokuContext): Promise<AIUsageStatsLite> {
|
|
636
634
|
const ai = ctx?.services?.ai as AIService | undefined;
|
|
637
635
|
if (!ai || typeof ai.getUsageSummary !== "function") {
|
|
638
636
|
return {
|
|
@@ -691,16 +689,29 @@ async function collectAI(ctx: MiokiContext): Promise<AIUsageStatsLite> {
|
|
|
691
689
|
}
|
|
692
690
|
|
|
693
691
|
export async function collectSnapshot(
|
|
694
|
-
ctx:
|
|
692
|
+
ctx: MiokuContext,
|
|
695
693
|
options: { isNightMode: boolean } = { isNightMode: false },
|
|
696
694
|
): Promise<StatusSnapshot> {
|
|
697
695
|
if (cache && Date.now() - cache.at < TTL_MS) {
|
|
698
696
|
return { ...cache.snapshot, isNightMode: options.isNightMode };
|
|
699
697
|
}
|
|
700
698
|
|
|
699
|
+
// 读取 help/status 配置(stdin 头像等),失败时回退到默认值
|
|
700
|
+
let statusConfig: HelpStatusConfig = {};
|
|
701
|
+
try {
|
|
702
|
+
const configService = getService(ctx, Services.Config);
|
|
703
|
+
statusConfig =
|
|
704
|
+
((await configService?.getConfig("help", "status")) as
|
|
705
|
+
| HelpStatusConfig
|
|
706
|
+
| null
|
|
707
|
+
| undefined) ?? {};
|
|
708
|
+
} catch {
|
|
709
|
+
statusConfig = {};
|
|
710
|
+
}
|
|
711
|
+
|
|
701
712
|
const rawBots = Array.from(connectedBots.values());
|
|
702
713
|
const [bots, disk, system, ai, resources] = await Promise.all([
|
|
703
|
-
collectBots(),
|
|
714
|
+
collectBots(ctx, statusConfig.stdinAvatar),
|
|
704
715
|
collectDisk(),
|
|
705
716
|
collectSystem(),
|
|
706
717
|
collectAI(ctx),
|
package/status/html-generator.ts
CHANGED
|
@@ -134,10 +134,13 @@ function renderHero(
|
|
|
134
134
|
.map((bot) => {
|
|
135
135
|
const statusText = bot.online ? "在线" : "离线";
|
|
136
136
|
const statusColor = bot.online ? "#10b981" : "#ef4444";
|
|
137
|
+
// stdin 等本地适配器没有 OneBot 版本信息,统一显示为 `<适配器>@<包版本>`
|
|
137
138
|
const frameworkText =
|
|
138
|
-
bot.
|
|
139
|
-
? `${bot.
|
|
140
|
-
: bot.
|
|
139
|
+
bot.adapter === "stdin"
|
|
140
|
+
? `${bot.adapter}@${bot.appVersion}`
|
|
141
|
+
: bot.appVersion && bot.appVersion !== "unknown"
|
|
142
|
+
? `${bot.framework} ${bot.appVersion} · 协议 ${bot.protocolVersion}`
|
|
143
|
+
: bot.framework;
|
|
141
144
|
// Nickname is rendered as a bold large heading above the avatar
|
|
142
145
|
// row (not as a chip). Chips below only carry the metadata.
|
|
143
146
|
const tags = [
|
package/status/image.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MiokuContext, ScreenshotService } from "mioku";
|
|
2
2
|
import { checkNightMode } from "../utils";
|
|
3
3
|
import { collectSnapshot } from "./data-collector";
|
|
4
4
|
import { renderStatusHtml } from "./html-generator";
|
|
@@ -28,7 +28,7 @@ function withTimeout<T>(p: PromiseLike<T>, ms: number): Promise<T> {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface GenerateStatusImageOptions {
|
|
31
|
-
ctx:
|
|
31
|
+
ctx: MiokuContext;
|
|
32
32
|
event?: any;
|
|
33
33
|
intent: StatusIntent;
|
|
34
34
|
botNickname?: string;
|
package/status/intent.ts
CHANGED
package/status/types.ts
CHANGED
|
@@ -7,9 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
export interface BotAccountStatus {
|
|
10
|
-
|
|
10
|
+
/** 平台账号标识。QQ 机器人是数字字符串,stdin 等本地适配器是 "stdin"。 */
|
|
11
|
+
uin: string;
|
|
11
12
|
nickname: string;
|
|
12
13
|
avatarUrl: string;
|
|
14
|
+
/** 适配器名,如 "stdin" / "onebotv11" / "icqq"。 */
|
|
15
|
+
adapter: string;
|
|
13
16
|
/** Underlying bot framework identifier, e.g. "QQBot" / "NapCat" / "LLOneBot". */
|
|
14
17
|
framework: string;
|
|
15
18
|
/** Adapter app version, e.g. "5.0.6" — from OneBot `get_version_info.app_version`. */
|
|
@@ -25,7 +28,7 @@ export interface BotAccountStatus {
|
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
30
|
* OneBot v11 `get_version_info` payload (already unwrapped from the
|
|
28
|
-
* `{ status, retcode, data, ... }` envelope by
|
|
31
|
+
* `{ status, retcode, data, ... }` envelope by the adapter's `bot.sendApi`).
|
|
29
32
|
*/
|
|
30
33
|
export interface OneBotVersionInfoData {
|
|
31
34
|
app_name: string;
|
package/utils.ts
CHANGED
|
@@ -96,9 +96,9 @@ export async function getRenderVersions(): Promise<{
|
|
|
96
96
|
|
|
97
97
|
const miokiCandidates = [
|
|
98
98
|
// Nested under mioku (the typical workspace case).
|
|
99
|
-
path.join(process.cwd(), "node_modules", "mioku", "node_modules", "
|
|
99
|
+
path.join(process.cwd(), "node_modules", "mioku", "node_modules", "mioku", "package.json"),
|
|
100
100
|
// Hoisted top-level (a single-package install).
|
|
101
|
-
path.join(process.cwd(), "node_modules", "
|
|
101
|
+
path.join(process.cwd(), "node_modules", "mioku", "package.json"),
|
|
102
102
|
];
|
|
103
103
|
const miokuCandidates = [
|
|
104
104
|
path.join(process.cwd(), "node_modules", "mioku", "package.json"),
|
|
@@ -107,7 +107,7 @@ export async function getRenderVersions(): Promise<{
|
|
|
107
107
|
|
|
108
108
|
let miokiVersion = "unknown";
|
|
109
109
|
for (const candidate of miokiCandidates) {
|
|
110
|
-
const v = await readNamedPackageVersion(candidate, "
|
|
110
|
+
const v = await readNamedPackageVersion(candidate, "mioku");
|
|
111
111
|
if (v !== "unknown") {
|
|
112
112
|
miokiVersion = v;
|
|
113
113
|
break;
|