mioku-plugin-help 3.0.4 → 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 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 || bot?.bot_id;
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(/^[#/\s]+/, "")
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
@@ -65,19 +65,20 @@ export async function resolveViewerRole(
65
65
  return "admin";
66
66
  }
67
67
 
68
- if (event?.group_id != null && event?.user_id != null) {
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
- // fall through to member
81
+ return "admin";
81
82
  }
82
83
  }
83
84
  }
package/index.ts CHANGED
@@ -87,7 +87,7 @@ const helpPlugin = definePlugin({
87
87
  return;
88
88
  }
89
89
  try {
90
- const { botNickname, botAvatarUrl } = resolveHelpBotProfile(
90
+ const { botNickname, botAvatarUrl } = await resolveHelpBotProfile(
91
91
  ctx,
92
92
  event,
93
93
  );
@@ -129,7 +129,7 @@ const helpPlugin = definePlugin({
129
129
  }
130
130
 
131
131
  try {
132
- const { botNickname, botAvatarUrl } = resolveHelpBotProfile(ctx, event);
132
+ const { botNickname, botAvatarUrl } = await resolveHelpBotProfile(ctx, event);
133
133
  const viewerRole = await resolveViewerRole(ctx, event);
134
134
  const imagePath = await generateHelpImage({
135
135
  helpService,
@@ -160,7 +160,7 @@ const helpPlugin = definePlugin({
160
160
  name: "help",
161
161
  aliases: ["帮助", "菜单"],
162
162
  match: /^(?:help|帮助|菜单)(?:\s|$)/i,
163
- prefixes: ["#", "/", ""],
163
+ prefixes: [".", "#", "/", ""],
164
164
  description: "生成帮助图片",
165
165
  handler: ({ event }) => handleHelpMessage(event),
166
166
  });
@@ -169,7 +169,7 @@ const helpPlugin = definePlugin({
169
169
  name: "status",
170
170
  aliases: ["状态", "zt"],
171
171
  match: /^(?:status|状态|zt)(?:\s|$)/i,
172
- prefixes: ["#", "/", ""],
172
+ prefixes: [".", "#", "/", ""],
173
173
  description: "生成系统状态图片",
174
174
  handler: ({ event }) => handleHelpMessage(event),
175
175
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mioku-plugin-help",
3
- "version": "3.0.4",
3
+ "version": "3.1.0",
4
4
  "description": "帮助插件,使用截图服务生成美观的帮助图片,并提供 #状态 指令",
5
5
  "main": "index.ts",
6
6
  "type": "module",
package/skills/help.ts CHANGED
@@ -57,7 +57,7 @@ export function createHelpSkill(): AISkill {
57
57
  }
58
58
 
59
59
  try {
60
- const { botNickname, botAvatarUrl } = resolveHelpBotProfile(
60
+ const { botNickname, botAvatarUrl } = await resolveHelpBotProfile(
61
61
  ctx,
62
62
  event,
63
63
  );
package/skills/status.ts CHANGED
@@ -32,7 +32,7 @@ export function createStatusSkill(): AISkill {
32
32
  return "screenshot 服务未加载";
33
33
  }
34
34
  try {
35
- const { botNickname, botAvatarUrl } = resolveHelpBotProfile(
35
+ const { botNickname, botAvatarUrl } = await resolveHelpBotProfile(
36
36
  ctx,
37
37
  event,
38
38
  );
@@ -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
- : `https://q1.qlogo.cn/g?b=qq&nk=${uin}&s=160`,
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: `#状态`, `/状态`, `状态`, `菜单 状态` (also the
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(/^[#/\s]+/, "")
17
+ .replace(/^[#/.\s]+/, "")
18
18
  .replace(/[。.!!??,,::;);]+$/g, "");
19
19
  }
20
20