mioku-plugin-help 3.0.3 → 3.1.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/help/image.ts +22 -6
- package/help/intent.ts +4 -4
- package/help/role.ts +9 -8
- package/index.ts +22 -5
- package/package.json +1 -13
- package/skills/help.ts +1 -1
- package/skills/status.ts +1 -1
- package/status/data-collector.ts +9 -1
- package/status/intent.ts +2 -2
package/help/image.ts
CHANGED
|
@@ -108,24 +108,40 @@ export async function generateHelpImage(options: {
|
|
|
108
108
|
* Pick a bot's nickname + avatar URL. Used so the help card can greet
|
|
109
109
|
* the user with their bot's identity rather than a generic string.
|
|
110
110
|
*/
|
|
111
|
-
export function resolveHelpBotProfile(
|
|
111
|
+
export async function resolveHelpBotProfile(
|
|
112
112
|
ctx: any,
|
|
113
113
|
event?: any,
|
|
114
|
-
): { botNickname: string; botAvatarUrl?: string } {
|
|
114
|
+
): Promise<{ botNickname: string; botAvatarUrl?: string }> {
|
|
115
115
|
const fallbackNickname = "Mioku Bot";
|
|
116
116
|
const selfId = event?.self_id;
|
|
117
117
|
const bot =
|
|
118
118
|
event?.bot ??
|
|
119
119
|
(ctx?.bots instanceof Map ? Array.from(ctx.bots.values())[0] : null);
|
|
120
|
-
const botId = selfId
|
|
120
|
+
const botId = String(selfId ?? bot?.bot_id ?? "").trim();
|
|
121
121
|
const botNickname = bot?.nickname || bot?.name || fallbackNickname;
|
|
122
|
-
const botAvatarUrl = botId
|
|
123
|
-
? `https://q1.qlogo.cn/g?b=qq&nk=${botId}&s=640`
|
|
124
|
-
: undefined;
|
|
122
|
+
const botAvatarUrl = await resolveBotAvatar(bot, botId);
|
|
125
123
|
|
|
126
124
|
return { botNickname, botAvatarUrl };
|
|
127
125
|
}
|
|
128
126
|
|
|
127
|
+
/** QQ 系的 qlogo 头像服务只认数字 QQ 号,openid/AppID 一律走平台接口 */
|
|
128
|
+
export async function resolveBotAvatar(
|
|
129
|
+
bot: any,
|
|
130
|
+
botId: string,
|
|
131
|
+
): Promise<string | undefined> {
|
|
132
|
+
try {
|
|
133
|
+
const avatar = await bot?.getAvatar?.();
|
|
134
|
+
if (typeof avatar === "string" && avatar) return avatar;
|
|
135
|
+
} catch {
|
|
136
|
+
// 平台不支持时回退
|
|
137
|
+
}
|
|
138
|
+
const adapter = String(bot?.adapter ?? "");
|
|
139
|
+
if ((adapter === "onebotv11" || adapter === "icqq") && /^\d+$/.test(botId)) {
|
|
140
|
+
return `https://q1.qlogo.cn/g?b=qq&nk=${botId}&s=640`;
|
|
141
|
+
}
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
129
145
|
/**
|
|
130
146
|
* Reply to a chat event with a local image file. Falls back to base64
|
|
131
147
|
* encoding if the network adapter can't send a raw file path.
|
package/help/intent.ts
CHANGED
|
@@ -23,7 +23,7 @@ import type {
|
|
|
23
23
|
function sanitizeKeyword(value: string): string {
|
|
24
24
|
return String(value || "")
|
|
25
25
|
.trim()
|
|
26
|
-
.replace(/^[
|
|
26
|
+
.replace(/^[#/.\s]+/, "")
|
|
27
27
|
.replace(/[。.!!??,,::;);]+$/g, "");
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -55,15 +55,15 @@ function extractMatchTokens(text: string): string[] {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
* The first whitespace-separated token of a command, with the `#` /
|
|
59
|
-
* prefix removed. Used as an additional alias when matching plugin
|
|
58
|
+
* The first whitespace-separated token of a command, with the `.` / `#` /
|
|
59
|
+
* `/` prefix removed. Used as an additional alias when matching plugin
|
|
60
60
|
* commands. Returns null if the first token is empty, contains `<>` (a
|
|
61
61
|
* placeholder), or has no alphanumeric characters.
|
|
62
62
|
*/
|
|
63
63
|
function extractCommandAlias(command: string): string | null {
|
|
64
64
|
const value = String(command || "")
|
|
65
65
|
.trim()
|
|
66
|
-
.replace(/^[
|
|
66
|
+
.replace(/^[#/.]+/, "");
|
|
67
67
|
if (!value) {
|
|
68
68
|
return null;
|
|
69
69
|
}
|
package/help/role.ts
CHANGED
|
@@ -33,7 +33,7 @@ export function canInvokeCommand(
|
|
|
33
33
|
* Resolve the requesting user's effective role for help filtering.
|
|
34
34
|
*
|
|
35
35
|
* - `master` is honored everywhere (private or group) — it comes from
|
|
36
|
-
* mioki's `
|
|
36
|
+
* mioki's `isMaster` allowlist (the bot `owners` list).
|
|
37
37
|
* - Private chat skips the `isAdmin` check and defaults to `admin`,
|
|
38
38
|
* so anyone DM-ing the bot can see admin-level commands.
|
|
39
39
|
* - Group chat resolves bot admin (mioki `isAdmin` allowlist), group
|
|
@@ -49,7 +49,7 @@ export async function resolveViewerRole(
|
|
|
49
49
|
ctx: any,
|
|
50
50
|
event: any,
|
|
51
51
|
): Promise<CommandRole> {
|
|
52
|
-
if (ctx?.
|
|
52
|
+
if (ctx?.isMaster?.(event)) {
|
|
53
53
|
return "master";
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -65,19 +65,20 @@ export async function resolveViewerRole(
|
|
|
65
65
|
return "admin";
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
const groupId = String(event?.group_id ?? "").trim();
|
|
69
|
+
const userId = String(event?.user_id ?? "").trim();
|
|
70
|
+
if (groupId && userId) {
|
|
69
71
|
const bot = event?.bot;
|
|
70
72
|
if (bot) {
|
|
71
73
|
try {
|
|
72
|
-
const info = await bot.getMemberInfo(
|
|
73
|
-
event.group_id,
|
|
74
|
-
event.user_id,
|
|
75
|
-
);
|
|
74
|
+
const info = await bot.getMemberInfo(groupId, userId);
|
|
76
75
|
if (info?.role === "owner" || info?.role === "admin") {
|
|
77
76
|
return "admin";
|
|
78
77
|
}
|
|
78
|
+
// 拿不到成员信息(null/空)时不能断言对方是普通成员,保守给 admin
|
|
79
|
+
if (!info?.role) return "admin";
|
|
79
80
|
} catch {
|
|
80
|
-
|
|
81
|
+
return "admin";
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
}
|
package/index.ts
CHANGED
|
@@ -21,8 +21,6 @@ import { createHelpSkills } from "./skills";
|
|
|
21
21
|
|
|
22
22
|
const helpPlugin = definePlugin({
|
|
23
23
|
name: "help",
|
|
24
|
-
version: "2.1.0",
|
|
25
|
-
description: "帮助插件,生成帮助图片,并提供 #状态 指令",
|
|
26
24
|
|
|
27
25
|
async setup(ctx: MiokuContext) {
|
|
28
26
|
// 启动后台采样器
|
|
@@ -75,7 +73,7 @@ const helpPlugin = definePlugin({
|
|
|
75
73
|
for (const skill of createHelpSkills()) aiService.registerSkill(skill);
|
|
76
74
|
}
|
|
77
75
|
|
|
78
|
-
|
|
76
|
+
const handleHelpMessage = async (event: import("mioku").MessageEvent) => {
|
|
79
77
|
const text = ctx.text(event);
|
|
80
78
|
if (!text) {
|
|
81
79
|
return;
|
|
@@ -89,7 +87,7 @@ const helpPlugin = definePlugin({
|
|
|
89
87
|
return;
|
|
90
88
|
}
|
|
91
89
|
try {
|
|
92
|
-
const { botNickname, botAvatarUrl } = resolveHelpBotProfile(
|
|
90
|
+
const { botNickname, botAvatarUrl } = await resolveHelpBotProfile(
|
|
93
91
|
ctx,
|
|
94
92
|
event,
|
|
95
93
|
);
|
|
@@ -131,7 +129,7 @@ const helpPlugin = definePlugin({
|
|
|
131
129
|
}
|
|
132
130
|
|
|
133
131
|
try {
|
|
134
|
-
const { botNickname, botAvatarUrl } = resolveHelpBotProfile(ctx, event);
|
|
132
|
+
const { botNickname, botAvatarUrl } = await resolveHelpBotProfile(ctx, event);
|
|
135
133
|
const viewerRole = await resolveViewerRole(ctx, event);
|
|
136
134
|
const imagePath = await generateHelpImage({
|
|
137
135
|
helpService,
|
|
@@ -155,6 +153,25 @@ const helpPlugin = definePlugin({
|
|
|
155
153
|
ctx.logger.error(`生成帮助图片失败: ${error}`);
|
|
156
154
|
await event.reply(`生成帮助图片失败: ${error}`);
|
|
157
155
|
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
ctx.command({
|
|
159
|
+
id: "帮助菜单",
|
|
160
|
+
name: "help",
|
|
161
|
+
aliases: ["帮助", "菜单"],
|
|
162
|
+
match: /^(?:help|帮助|菜单)(?:\s|$)/i,
|
|
163
|
+
prefixes: [".", "#", "/", ""],
|
|
164
|
+
description: "生成帮助图片",
|
|
165
|
+
handler: ({ event }) => handleHelpMessage(event),
|
|
166
|
+
});
|
|
167
|
+
ctx.command({
|
|
168
|
+
id: "status",
|
|
169
|
+
name: "status",
|
|
170
|
+
aliases: ["状态", "zt"],
|
|
171
|
+
match: /^(?:status|状态|zt)(?:\s|$)/i,
|
|
172
|
+
prefixes: [".", "#", "/", ""],
|
|
173
|
+
description: "生成系统状态图片",
|
|
174
|
+
handler: ({ event }) => handleHelpMessage(event),
|
|
158
175
|
});
|
|
159
176
|
|
|
160
177
|
return () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mioku-plugin-help",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "帮助插件,使用截图服务生成美观的帮助图片,并提供 #状态 指令",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -17,18 +17,6 @@
|
|
|
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
|
-
}
|
|
32
20
|
]
|
|
33
21
|
},
|
|
34
22
|
"peerDependencies": {
|
package/skills/help.ts
CHANGED
package/skills/status.ts
CHANGED
package/status/data-collector.ts
CHANGED
|
@@ -205,6 +205,14 @@ async function collectBots(
|
|
|
205
205
|
return collectBotStatuses(sorted, ctx, stdinAvatar);
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
/** QQ 系适配器的 bot_id 才是数字 QQ 号,qlogo 服务对 openid/AppID 无效 */
|
|
209
|
+
function qlogoAvatarUrl(adapter: string, botId: string): string {
|
|
210
|
+
if ((adapter === "onebotv11" || adapter === "icqq") && /^\d+$/.test(botId)) {
|
|
211
|
+
return `https://q1.qlogo.cn/g?b=qq&nk=${botId}&s=160`;
|
|
212
|
+
}
|
|
213
|
+
return "";
|
|
214
|
+
}
|
|
215
|
+
|
|
208
216
|
/** 把本地绝对路径 / http(s) / file:// 统一成可被截图服务加载的图片地址 */
|
|
209
217
|
function toImageSrc(value: string): string {
|
|
210
218
|
if (/^(https?:|file:|data:)/i.test(value)) return value;
|
|
@@ -274,7 +282,7 @@ async function collectBotStatuses(
|
|
|
274
282
|
? toImageSrc(stdinAvatar || "")
|
|
275
283
|
: avatarFromBot
|
|
276
284
|
? toImageSrc(avatarFromBot)
|
|
277
|
-
:
|
|
285
|
+
: qlogoAvatarUrl(adapter, uin),
|
|
278
286
|
adapter,
|
|
279
287
|
implLabel,
|
|
280
288
|
framework: isStdin ? adapter : framework,
|
package/status/intent.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { StatusIntent } from "./types";
|
|
|
3
3
|
/**
|
|
4
4
|
* Parse a user message into a `StatusIntent`.
|
|
5
5
|
*
|
|
6
|
-
* Recognized prefixes:
|
|
6
|
+
* Recognized prefixes: `.状态` / `/状态` / `#状态` / 裸 `状态` (also the
|
|
7
7
|
* Latin aliases `zt` / `status`). Anything trailing the leading token is
|
|
8
8
|
* ignored — the panel always renders the full sheet.
|
|
9
9
|
*
|
|
@@ -14,7 +14,7 @@ import type { StatusIntent } from "./types";
|
|
|
14
14
|
function stripStopword(input: string): string {
|
|
15
15
|
return String(input || "")
|
|
16
16
|
.trim()
|
|
17
|
-
.replace(/^[
|
|
17
|
+
.replace(/^[#/.\s]+/, "")
|
|
18
18
|
.replace(/[。.!!??,,::;);]+$/g, "");
|
|
19
19
|
}
|
|
20
20
|
|