mioku-plugin-admin 2.3.2 → 2.3.4
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/commands/verify.ts +53 -33
- package/config.md +4 -4
- package/package.json +11 -1
- package/skills/group.ts +18 -11
- package/skills/personal.ts +3 -4
- package/verify/config.ts +15 -21
- package/verify/index.ts +4 -3
package/commands/verify.ts
CHANGED
|
@@ -29,6 +29,13 @@ const VERIFY_MODE_LABELS: Record<string, string> = {
|
|
|
29
29
|
chiral: "手性碳",
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
async function extractQuoteImageUrls(event: any): Promise<string[]> {
|
|
33
|
+
if (!event || typeof event.getQuoteMsg !== "function") return [];
|
|
34
|
+
const quoteMsg = await event.getQuoteMsg().catch(() => null);
|
|
35
|
+
if (!quoteMsg || !Array.isArray(quoteMsg.message)) return [];
|
|
36
|
+
return extractImageUrls(quoteMsg.message);
|
|
37
|
+
}
|
|
38
|
+
|
|
32
39
|
export function registerVerifyCommands(options: VerifyCommandOptions) {
|
|
33
40
|
const { ctx, getVerifyConfig, setVerifyConfig, verifyController } = options;
|
|
34
41
|
|
|
@@ -250,29 +257,32 @@ export function registerVerifyCommands(options: VerifyCommandOptions) {
|
|
|
250
257
|
|
|
251
258
|
// /入群提示 xxx
|
|
252
259
|
// /入群提示 关闭
|
|
253
|
-
//
|
|
260
|
+
// /入群提示(带图片,可来自当前消息或引用消息)— 仅图片也可
|
|
254
261
|
if (text.startsWith("/入群提示") || text.startsWith("#入群提示")) {
|
|
255
262
|
if (!(await ensureAdminPermission())) return;
|
|
256
263
|
|
|
257
264
|
const rawArg = text.replace(/^[/#]入群提示\s*/, "").trim();
|
|
258
265
|
const current = getVerifyConfig();
|
|
259
266
|
const groupCfg = getGroupVerifyConfig(current, groupId);
|
|
260
|
-
const
|
|
267
|
+
const directImageUrls = extractImageUrls(event.message);
|
|
268
|
+
const quoteImageUrls = await extractQuoteImageUrls(event).catch(() => []);
|
|
269
|
+
const imageUrls =
|
|
270
|
+
directImageUrls.length > 0 ? directImageUrls : quoteImageUrls;
|
|
261
271
|
|
|
262
272
|
if (rawArg === "关闭" || rawArg === "清空" || rawArg === "关") {
|
|
263
273
|
if (!hasCustomPrompt(groupCfg)) {
|
|
264
274
|
await event.reply("本群还没设置自定义入群提示哦~", true);
|
|
265
275
|
return;
|
|
266
276
|
}
|
|
267
|
-
const
|
|
277
|
+
const removedFile = groupCfg.promptImage;
|
|
268
278
|
const next = upsertGroupVerifyConfig(current, groupId, {
|
|
269
279
|
customPrompt: "",
|
|
270
|
-
|
|
280
|
+
promptImage: "",
|
|
271
281
|
});
|
|
272
282
|
await setVerifyConfig(next);
|
|
273
283
|
await pruneGroupPromptImages(groupId, []).catch(() => {});
|
|
274
284
|
ctx.logger.info(
|
|
275
|
-
`admin verify 关闭群 ${groupId} 自定义入群提示,清理图片 ${
|
|
285
|
+
`admin verify 关闭群 ${groupId} 自定义入群提示,清理图片 ${removedFile}`,
|
|
276
286
|
);
|
|
277
287
|
await event.reply("已关闭本群自定义入群提示~", true);
|
|
278
288
|
return;
|
|
@@ -281,23 +291,26 @@ export function registerVerifyCommands(options: VerifyCommandOptions) {
|
|
|
281
291
|
if (!rawArg && !imageUrls.length) {
|
|
282
292
|
if (!hasCustomPrompt(groupCfg)) {
|
|
283
293
|
await event.reply(
|
|
284
|
-
"用法:/入群提示 xxx(最多 50
|
|
294
|
+
"用法:/入群提示 xxx(最多 50 字),可附带图片(当前消息或引用消息中的图片)一起发送;/入群提示 关闭 关闭自定义提示~",
|
|
285
295
|
true,
|
|
286
296
|
);
|
|
287
297
|
return;
|
|
288
298
|
}
|
|
289
|
-
const
|
|
290
|
-
|
|
299
|
+
const lines = ["已开启本群自定义入群提示"];
|
|
300
|
+
lines.push(
|
|
291
301
|
groupCfg.customPrompt
|
|
292
302
|
? `文字:${groupCfg.customPrompt}`
|
|
293
303
|
: "文字:(未设置)",
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
304
|
+
);
|
|
305
|
+
lines.push(
|
|
306
|
+
groupCfg.promptImage
|
|
307
|
+
? `图片:已设置(filename=${groupCfg.promptImage})`
|
|
308
|
+
: "图片:(未设置)",
|
|
309
|
+
);
|
|
310
|
+
await event.reply(lines.join("\n"), true);
|
|
297
311
|
return;
|
|
298
312
|
}
|
|
299
313
|
|
|
300
|
-
let nextPrompt = groupCfg.customPrompt;
|
|
301
314
|
if (rawArg) {
|
|
302
315
|
const argLength = Array.from(rawArg).length;
|
|
303
316
|
if (argLength > MAX_CUSTOM_PROMPT_LENGTH) {
|
|
@@ -309,39 +322,46 @@ export function registerVerifyCommands(options: VerifyCommandOptions) {
|
|
|
309
322
|
});
|
|
310
323
|
return;
|
|
311
324
|
}
|
|
312
|
-
nextPrompt = normalizeCustomPrompt(rawArg);
|
|
313
325
|
}
|
|
326
|
+
const nextPrompt = rawArg
|
|
327
|
+
? normalizeCustomPrompt(rawArg)
|
|
328
|
+
: groupCfg.customPrompt;
|
|
314
329
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
|
|
330
|
+
let nextImage = groupCfg.promptImage;
|
|
331
|
+
let imageNote = "";
|
|
332
|
+
if (imageUrls.length) {
|
|
333
|
+
const targetUrl = imageUrls[0];
|
|
334
|
+
const savedImage = await saveRemoteImageAsPrompt(groupId, targetUrl);
|
|
335
|
+
if (savedImage) {
|
|
336
|
+
const previousImage = groupCfg.promptImage;
|
|
337
|
+
nextImage = savedImage.filename;
|
|
338
|
+
imageNote = previousImage
|
|
339
|
+
? `图片已替换(旧文件 ${previousImage} 已删除)`
|
|
340
|
+
: `图片已设置(${savedImage.filename})`;
|
|
341
|
+
if (previousImage) {
|
|
342
|
+
ctx.logger.info(
|
|
343
|
+
`admin verify 替换群 ${groupId} 入群提示图片:${previousImage} -> ${savedImage.filename}`,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
imageNote = "图片下载失败,请稍后重试";
|
|
348
|
+
}
|
|
320
349
|
}
|
|
321
350
|
|
|
322
|
-
const nextImages = imageUrls.length
|
|
323
|
-
? [...groupCfg.promptImages, ...saved]
|
|
324
|
-
: groupCfg.promptImages;
|
|
325
|
-
|
|
326
351
|
const next = upsertGroupVerifyConfig(current, groupId, {
|
|
327
352
|
customPrompt: nextPrompt,
|
|
328
|
-
|
|
353
|
+
promptImage: nextImage,
|
|
329
354
|
});
|
|
330
355
|
await setVerifyConfig(next);
|
|
331
|
-
await pruneGroupPromptImages(groupId,
|
|
356
|
+
await pruneGroupPromptImages(groupId, nextImage ? [nextImage] : []).catch(
|
|
357
|
+
() => {},
|
|
358
|
+
);
|
|
332
359
|
|
|
333
|
-
const lines
|
|
360
|
+
const lines = ["已更新本群自定义入群提示"];
|
|
334
361
|
if (rawArg) lines.push(`文字:${nextPrompt}`);
|
|
335
362
|
else if (groupCfg.customPrompt)
|
|
336
363
|
lines.push(`文字(保持):${groupCfg.customPrompt}`);
|
|
337
|
-
if (imageUrls.length)
|
|
338
|
-
lines.push(
|
|
339
|
-
saved.length
|
|
340
|
-
? `新增图片 ${saved.length} 张${failed > saved.length ? `,${failed - saved.length} 张保存失败` : ""}`
|
|
341
|
-
: `图片保存失败 ${failed} 张,请稍后重试`,
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
lines.push(`当前共 ${nextImages.length} 张图片`);
|
|
364
|
+
if (imageUrls.length && imageNote) lines.push(imageNote);
|
|
345
365
|
await event.reply(lines.join("\n"), true);
|
|
346
366
|
return;
|
|
347
367
|
}
|
package/config.md
CHANGED
|
@@ -106,11 +106,11 @@ fields:
|
|
|
106
106
|
description: 一旦设置该群将跳过 AI 生成欢迎,验证通过后直接发送这段文字(可附带本地图片一起)。上限 50 字,建议在群里直接用 /入群提示 xxx 设置或 /入群提示 关闭 关闭。
|
|
107
107
|
placeholder: 例:欢迎来到本群,请先看群公告~
|
|
108
108
|
|
|
109
|
-
- key:
|
|
109
|
+
- key: promptImage
|
|
110
110
|
label: 群自定义入群提示图片
|
|
111
|
-
type:
|
|
112
|
-
description:
|
|
113
|
-
placeholder: 例:
|
|
111
|
+
type: text
|
|
112
|
+
description: 单张图片文件名(保存在 data/admin/<群号>/prompt-images/),与文字一起发送。在群里发送 /入群提示 时附带图片(当前消息或引用消息中的图片均可)即可替换;/入群提示 关闭 会一并清空。
|
|
113
|
+
placeholder: 例:prompt-l8t9a-3f2c.png
|
|
114
114
|
|
|
115
115
|
- key: verify.reactionEmojiId
|
|
116
116
|
label: 回应模式表态表情ID
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mioku-plugin-admin",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.4",
|
|
4
4
|
"description": "管理插件,提供事件通知与群管/个人管理指令",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -137,6 +137,10 @@
|
|
|
137
137
|
{
|
|
138
138
|
"id": "/重新验证",
|
|
139
139
|
"match": "/^[/#]重新验证/"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"id": "/入群提示",
|
|
143
|
+
"match": "/^[/#]入群提示/"
|
|
140
144
|
}
|
|
141
145
|
],
|
|
142
146
|
"help": {
|
|
@@ -288,6 +292,12 @@
|
|
|
288
292
|
"desc": "让指定成员重新进行入群验证",
|
|
289
293
|
"usage": "/重新验证 @成员",
|
|
290
294
|
"role": "admin"
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
"cmd": "/入群提示",
|
|
298
|
+
"desc": "设置本群自定义入群提示,可附带文字+图片",
|
|
299
|
+
"usage": "/入群提示 xxx;/入群提示 关闭",
|
|
300
|
+
"role": "admin"
|
|
291
301
|
}
|
|
292
302
|
]
|
|
293
303
|
}
|
package/skills/group.ts
CHANGED
|
@@ -61,14 +61,14 @@ const groupAdminSkill: AISkill = {
|
|
|
61
61
|
tools: [
|
|
62
62
|
{
|
|
63
63
|
name: "manage_member",
|
|
64
|
-
description:
|
|
65
|
-
"管理群成员:踢人(kick)、禁言(mute)、解除禁言(unmute)、设为管理员(set_admin)、取消管理员(unset_admin)、设置某成员头衔(set_title)、设置自己头衔(set_self_title)。",
|
|
64
|
+
description: "管理群成员",
|
|
66
65
|
parameters: {
|
|
67
66
|
type: "object",
|
|
68
67
|
properties: {
|
|
69
68
|
action: {
|
|
70
69
|
type: "string",
|
|
71
|
-
description:
|
|
70
|
+
description:
|
|
71
|
+
"要执行的动作:踢人,禁言,解禁,设置/取消管理,设置头衔",
|
|
72
72
|
enum: [
|
|
73
73
|
"kick",
|
|
74
74
|
"mute",
|
|
@@ -81,8 +81,7 @@ const groupAdminSkill: AISkill = {
|
|
|
81
81
|
},
|
|
82
82
|
user_id: {
|
|
83
83
|
type: "number",
|
|
84
|
-
description:
|
|
85
|
-
"目标成员QQ号。set_self_title 不需要;其他动作必填。",
|
|
84
|
+
description: "目标成员QQ号。set_self_title 不需要;其他动作必填",
|
|
86
85
|
},
|
|
87
86
|
duration: {
|
|
88
87
|
type: "number",
|
|
@@ -192,7 +191,11 @@ const groupAdminSkill: AISkill = {
|
|
|
192
191
|
return { error: `未知的 action: ${action}` };
|
|
193
192
|
}
|
|
194
193
|
} catch (err) {
|
|
195
|
-
logAdminSkillError(
|
|
194
|
+
logAdminSkillError(
|
|
195
|
+
runtimeCtx,
|
|
196
|
+
`admin_group.manage_member.${action}`,
|
|
197
|
+
err,
|
|
198
|
+
);
|
|
196
199
|
return { error: `执行 ${action} 失败: ${err}` };
|
|
197
200
|
}
|
|
198
201
|
},
|
|
@@ -200,7 +203,7 @@ const groupAdminSkill: AISkill = {
|
|
|
200
203
|
{
|
|
201
204
|
name: "manage_group",
|
|
202
205
|
description:
|
|
203
|
-
"
|
|
206
|
+
"管理群本身或批量撤回消息:开启/关闭全体禁言、改群名、改Bot在群里的名片、改群头像、撤回一条或多条消息",
|
|
204
207
|
parameters: {
|
|
205
208
|
type: "object",
|
|
206
209
|
properties: {
|
|
@@ -226,8 +229,7 @@ const groupAdminSkill: AISkill = {
|
|
|
226
229
|
},
|
|
227
230
|
message_id: {
|
|
228
231
|
type: "number",
|
|
229
|
-
description:
|
|
230
|
-
"包含图片的消息 message_id,仅 set_group_avatar 需要",
|
|
232
|
+
description: "包含图片的消息 message_id,仅 set_group_avatar 需要",
|
|
231
233
|
},
|
|
232
234
|
message_ids: {
|
|
233
235
|
type: "array",
|
|
@@ -260,7 +262,8 @@ const groupAdminSkill: AISkill = {
|
|
|
260
262
|
return { success: true, message: "当前群已关闭全体禁言" };
|
|
261
263
|
case "set_group_name": {
|
|
262
264
|
const groupName = String(args?.group_name || "").trim();
|
|
263
|
-
if (!groupName)
|
|
265
|
+
if (!groupName)
|
|
266
|
+
return { error: "set_group_name 需要提供 group_name" };
|
|
264
267
|
await bot.api("set_group_name", {
|
|
265
268
|
group_id: groupId,
|
|
266
269
|
group_name: groupName,
|
|
@@ -331,7 +334,11 @@ const groupAdminSkill: AISkill = {
|
|
|
331
334
|
return { error: `未知的 action: ${action}` };
|
|
332
335
|
}
|
|
333
336
|
} catch (err) {
|
|
334
|
-
logAdminSkillError(
|
|
337
|
+
logAdminSkillError(
|
|
338
|
+
runtimeCtx,
|
|
339
|
+
`admin_group.manage_group.${action}`,
|
|
340
|
+
err,
|
|
341
|
+
);
|
|
335
342
|
return { error: `执行 ${action} 失败: ${err}` };
|
|
336
343
|
}
|
|
337
344
|
},
|
package/skills/personal.ts
CHANGED
|
@@ -21,13 +21,13 @@ function logAdminSkillError(runtimeCtx: any, toolName: string, err: unknown) {
|
|
|
21
21
|
const personalSkill: AISkill = {
|
|
22
22
|
name: "admin_personal",
|
|
23
23
|
description:
|
|
24
|
-
"Bot
|
|
24
|
+
"Bot个人账号与消息管理统一入口:修改Bot资料、发送消息、获取列表、管理关系",
|
|
25
25
|
permission: "owner",
|
|
26
26
|
tools: [
|
|
27
27
|
{
|
|
28
28
|
name: "manage_personal",
|
|
29
29
|
description:
|
|
30
|
-
"Bot
|
|
30
|
+
"Bot个人账号管理:修改头像/昵称/签名/性别、给指定用户或群发消息、查看好友/群列表、删除好友、退群",
|
|
31
31
|
parameters: {
|
|
32
32
|
type: "object",
|
|
33
33
|
properties: {
|
|
@@ -66,8 +66,7 @@ const personalSkill: AISkill = {
|
|
|
66
66
|
},
|
|
67
67
|
user_id: {
|
|
68
68
|
type: "number",
|
|
69
|
-
description:
|
|
70
|
-
"目标QQ号。send_private / delete_friend 需要。",
|
|
69
|
+
description: "目标QQ号。send_private / delete_friend 需要。",
|
|
71
70
|
},
|
|
72
71
|
group_id: {
|
|
73
72
|
type: "number",
|
package/verify/config.ts
CHANGED
|
@@ -7,7 +7,7 @@ export interface VerifyGroupConfig {
|
|
|
7
7
|
enabled: boolean;
|
|
8
8
|
mode: VerifyMode;
|
|
9
9
|
customPrompt: string;
|
|
10
|
-
|
|
10
|
+
promptImage: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export interface VerifyConfig {
|
|
@@ -50,25 +50,16 @@ export function normalizeCustomPrompt(value: unknown): string {
|
|
|
50
50
|
return Array.from(trimmed).slice(0, MAX_CUSTOM_PROMPT_LENGTH).join("");
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export function
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
const result: string[] = [];
|
|
57
|
-
for (const item of value) {
|
|
58
|
-
if (typeof item !== "string") continue;
|
|
59
|
-
const name = item.trim();
|
|
60
|
-
if (!name || seen.has(name)) continue;
|
|
61
|
-
seen.add(name);
|
|
62
|
-
result.push(name);
|
|
63
|
-
}
|
|
64
|
-
return result;
|
|
53
|
+
export function normalizePromptImage(value: unknown): string {
|
|
54
|
+
if (typeof value !== "string") return "";
|
|
55
|
+
return value.trim();
|
|
65
56
|
}
|
|
66
57
|
|
|
67
58
|
export function hasCustomPrompt(groupCfg: VerifyGroupConfig | undefined): boolean {
|
|
68
59
|
if (!groupCfg) return false;
|
|
69
60
|
return (
|
|
70
61
|
Boolean(groupCfg.customPrompt && groupCfg.customPrompt.trim()) ||
|
|
71
|
-
groupCfg.
|
|
62
|
+
Boolean(groupCfg.promptImage && groupCfg.promptImage.trim())
|
|
72
63
|
);
|
|
73
64
|
}
|
|
74
65
|
|
|
@@ -81,6 +72,11 @@ export function normalizeVerifyMode(value: unknown): VerifyMode {
|
|
|
81
72
|
|
|
82
73
|
function normalizeVerifyGroup(raw: any): VerifyGroupConfig {
|
|
83
74
|
const groupId = Number(raw?.groupId || raw?.group_id || 0);
|
|
75
|
+
const promptImageRaw =
|
|
76
|
+
raw?.promptImage ??
|
|
77
|
+
raw?.prompt_image ??
|
|
78
|
+
(Array.isArray(raw?.promptImages) ? raw.promptImages[0] : undefined) ??
|
|
79
|
+
(Array.isArray(raw?.images) ? raw.images[0] : undefined);
|
|
84
80
|
return {
|
|
85
81
|
groupId: groupId > 0 ? groupId : 0,
|
|
86
82
|
enabled: raw?.enabled === true,
|
|
@@ -88,9 +84,7 @@ function normalizeVerifyGroup(raw: any): VerifyGroupConfig {
|
|
|
88
84
|
customPrompt: normalizeCustomPrompt(
|
|
89
85
|
raw?.customPrompt ?? raw?.custom_prompt ?? raw?.extraPrompt,
|
|
90
86
|
),
|
|
91
|
-
|
|
92
|
-
raw?.promptImages ?? raw?.prompt_images ?? raw?.images,
|
|
93
|
-
),
|
|
87
|
+
promptImage: normalizePromptImage(promptImageRaw),
|
|
94
88
|
};
|
|
95
89
|
}
|
|
96
90
|
|
|
@@ -157,7 +151,7 @@ export function getGroupVerifyConfig(
|
|
|
157
151
|
enabled: false,
|
|
158
152
|
mode: "reaction",
|
|
159
153
|
customPrompt: "",
|
|
160
|
-
|
|
154
|
+
promptImage: "",
|
|
161
155
|
};
|
|
162
156
|
}
|
|
163
157
|
|
|
@@ -174,8 +168,8 @@ export function upsertGroupVerifyConfig(
|
|
|
174
168
|
if (patch.customPrompt !== undefined) {
|
|
175
169
|
normalizedPatch.customPrompt = normalizeCustomPrompt(patch.customPrompt);
|
|
176
170
|
}
|
|
177
|
-
if (patch.
|
|
178
|
-
normalizedPatch.
|
|
171
|
+
if (patch.promptImage !== undefined) {
|
|
172
|
+
normalizedPatch.promptImage = normalizePromptImage(patch.promptImage);
|
|
179
173
|
}
|
|
180
174
|
if (idx >= 0) {
|
|
181
175
|
next.groups = config.groups.map((g, i) =>
|
|
@@ -189,7 +183,7 @@ export function upsertGroupVerifyConfig(
|
|
|
189
183
|
enabled: false,
|
|
190
184
|
mode: "reaction",
|
|
191
185
|
customPrompt: "",
|
|
192
|
-
|
|
186
|
+
promptImage: "",
|
|
193
187
|
...normalizedPatch,
|
|
194
188
|
},
|
|
195
189
|
];
|
package/verify/index.ts
CHANGED
|
@@ -367,15 +367,16 @@ export function createVerifyController(
|
|
|
367
367
|
if (prompt) {
|
|
368
368
|
segments.push(ctx.segment.text(prompt));
|
|
369
369
|
}
|
|
370
|
-
|
|
370
|
+
const filename = String(groupCfg.promptImage || "").trim();
|
|
371
|
+
if (filename) {
|
|
371
372
|
const imagePath = getGroupPromptImagePath(info.groupId, filename);
|
|
372
373
|
if (!existsSync(imagePath)) {
|
|
373
374
|
ctx.logger.warn(
|
|
374
375
|
`admin verify 自定义入群提示图片缺失: ${imagePath}`,
|
|
375
376
|
);
|
|
376
|
-
|
|
377
|
+
} else {
|
|
378
|
+
segments.push(ctx.segment.image(`file://${imagePath}`));
|
|
377
379
|
}
|
|
378
|
-
segments.push(ctx.segment.image(`file://${imagePath}`));
|
|
379
380
|
}
|
|
380
381
|
if (!segments.length) return false;
|
|
381
382
|
try {
|