mioku-plugin-admin 2.3.5 → 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/commands/group.ts +34 -54
- package/commands/notice.ts +3 -3
- package/commands/personal.ts +112 -184
- package/commands/verify.ts +43 -35
- package/config.ts +29 -17
- package/index.ts +5 -5
- package/notify/index.ts +108 -138
- package/notify/welcome.ts +52 -44
- package/package.json +3 -5
- package/skills/group.ts +87 -143
- package/skills/message-image.ts +8 -9
- package/skills/personal.ts +72 -98
- package/verify/chiral.ts +4 -4
- package/verify/index.ts +59 -45
- package/verify/number.ts +4 -4
- package/verify/reaction.ts +62 -14
- package/verify/state.ts +1 -1
- package/verify/types.ts +18 -8
package/notify/welcome.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type MiokuContext, wait } from "mioku";
|
|
2
2
|
import { getPluginRuntimeState, type AIService } from "mioku";
|
|
3
3
|
import type { AdminConfig } from "../config";
|
|
4
4
|
|
|
5
5
|
export async function resolveMemberName(
|
|
6
|
-
ctx:
|
|
6
|
+
ctx: MiokuContext,
|
|
7
|
+
bot: import("mioku").Bot | undefined,
|
|
7
8
|
groupId: number,
|
|
8
9
|
userId: number,
|
|
9
|
-
selfId: number,
|
|
10
10
|
): Promise<string> {
|
|
11
11
|
try {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
.getGroupMemberInfo(groupId, userId);
|
|
12
|
+
if (!bot) return String(userId);
|
|
13
|
+
const member = await bot.getMemberInfo(groupId, userId);
|
|
15
14
|
return (
|
|
16
15
|
String(member?.card || "").trim() ||
|
|
17
16
|
String(member?.nickname || "").trim() ||
|
|
@@ -43,7 +42,7 @@ function getBatchMap(): Map<string, BatchState> {
|
|
|
43
42
|
return state[RUNTIME_KEY] as Map<string, BatchState>;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
function batchKey(selfId: number, groupId: number): string {
|
|
45
|
+
function batchKey(selfId: string | number, groupId: number): string {
|
|
47
46
|
return `${selfId}:${groupId}`;
|
|
48
47
|
}
|
|
49
48
|
|
|
@@ -66,20 +65,21 @@ function normalizeGeneratedText(value: string): string {
|
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
async function flushBatch(options: {
|
|
69
|
-
ctx:
|
|
68
|
+
ctx: MiokuContext;
|
|
70
69
|
aiService?: AIService;
|
|
71
70
|
config: AdminConfig;
|
|
72
|
-
selfId: number;
|
|
71
|
+
selfId: string | number;
|
|
73
72
|
groupId: number;
|
|
74
73
|
groupName: string;
|
|
75
74
|
members: PendingMember[];
|
|
76
75
|
promptInjections?: { content: string; title?: string }[];
|
|
76
|
+
bot?: import("mioku").Bot;
|
|
77
77
|
tryCustomPrompt?: (info: {
|
|
78
|
-
selfId: number;
|
|
78
|
+
selfId: string | number;
|
|
79
79
|
groupId: number;
|
|
80
80
|
userId: number;
|
|
81
81
|
groupName: string;
|
|
82
|
-
}) => Promise<boolean>;
|
|
82
|
+
}, bot?: import("mioku").Bot) => Promise<boolean>;
|
|
83
83
|
}): Promise<string> {
|
|
84
84
|
const {
|
|
85
85
|
ctx,
|
|
@@ -90,6 +90,7 @@ async function flushBatch(options: {
|
|
|
90
90
|
groupName,
|
|
91
91
|
members,
|
|
92
92
|
promptInjections,
|
|
93
|
+
bot,
|
|
93
94
|
tryCustomPrompt,
|
|
94
95
|
} = options;
|
|
95
96
|
if (!members.length) return "";
|
|
@@ -97,12 +98,15 @@ async function flushBatch(options: {
|
|
|
97
98
|
if (tryCustomPrompt) {
|
|
98
99
|
let anyCustom = false;
|
|
99
100
|
for (const m of members) {
|
|
100
|
-
const handled = await tryCustomPrompt(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
const handled = await tryCustomPrompt(
|
|
102
|
+
{
|
|
103
|
+
selfId,
|
|
104
|
+
groupId,
|
|
105
|
+
userId: m.userId,
|
|
106
|
+
groupName,
|
|
107
|
+
},
|
|
108
|
+
bot,
|
|
109
|
+
);
|
|
106
110
|
if (handled) anyCustom = true;
|
|
107
111
|
}
|
|
108
112
|
if (anyCustom) return "";
|
|
@@ -152,21 +156,22 @@ async function flushBatch(options: {
|
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
async function sendSingleWelcome(options: {
|
|
155
|
-
ctx:
|
|
159
|
+
ctx: MiokuContext;
|
|
156
160
|
aiService?: AIService;
|
|
157
161
|
config: AdminConfig;
|
|
158
|
-
selfId: number;
|
|
162
|
+
selfId: string | number;
|
|
159
163
|
groupId: number;
|
|
160
164
|
groupName: string;
|
|
161
165
|
userId: number;
|
|
162
166
|
memberName: string;
|
|
163
167
|
promptInjections?: { content: string; title?: string }[];
|
|
168
|
+
bot?: import("mioku").Bot;
|
|
164
169
|
tryCustomPrompt?: (info: {
|
|
165
|
-
selfId: number;
|
|
170
|
+
selfId: string | number;
|
|
166
171
|
groupId: number;
|
|
167
172
|
userId: number;
|
|
168
173
|
groupName: string;
|
|
169
|
-
}) => Promise<boolean>;
|
|
174
|
+
}, bot?: import("mioku").Bot) => Promise<boolean>;
|
|
170
175
|
}): Promise<void> {
|
|
171
176
|
const {
|
|
172
177
|
ctx,
|
|
@@ -178,6 +183,7 @@ async function sendSingleWelcome(options: {
|
|
|
178
183
|
userId,
|
|
179
184
|
memberName,
|
|
180
185
|
promptInjections,
|
|
186
|
+
bot,
|
|
181
187
|
tryCustomPrompt,
|
|
182
188
|
} = options;
|
|
183
189
|
const welcomeMessage = await flushBatch({
|
|
@@ -189,42 +195,42 @@ async function sendSingleWelcome(options: {
|
|
|
189
195
|
groupName,
|
|
190
196
|
members: [{ userId, memberName }],
|
|
191
197
|
promptInjections,
|
|
198
|
+
bot,
|
|
192
199
|
tryCustomPrompt,
|
|
193
200
|
});
|
|
194
201
|
if (!welcomeMessage) return;
|
|
195
|
-
const bot = ctx.pickBot(selfId);
|
|
196
202
|
if (!bot) return;
|
|
197
203
|
try {
|
|
198
|
-
await bot.
|
|
204
|
+
await bot.sendMessage({ type: "group", group_id: groupId}, [ctx.segment.text(welcomeMessage)]);
|
|
199
205
|
} catch (error) {
|
|
200
206
|
ctx.logger.warn(`发送入群欢迎失败: ${error}`);
|
|
201
207
|
}
|
|
202
208
|
}
|
|
203
209
|
|
|
204
210
|
export async function triggerSingleWelcome(options: {
|
|
205
|
-
ctx:
|
|
211
|
+
ctx: MiokuContext;
|
|
206
212
|
aiService?: AIService;
|
|
207
213
|
getConfig: () => AdminConfig;
|
|
208
|
-
selfId: number;
|
|
214
|
+
selfId: string | number;
|
|
209
215
|
groupId: number;
|
|
210
216
|
groupName: string;
|
|
211
217
|
userId: number;
|
|
212
218
|
memberName?: string;
|
|
213
219
|
promptInjections?: { content: string; title?: string }[];
|
|
214
220
|
tryCustomPrompt?: (info: {
|
|
215
|
-
selfId: number;
|
|
221
|
+
selfId: string | number;
|
|
216
222
|
groupId: number;
|
|
217
223
|
userId: number;
|
|
218
224
|
groupName: string;
|
|
219
|
-
}) => Promise<boolean>;
|
|
220
|
-
}): Promise<void> {
|
|
225
|
+
}, bot?: import("mioku").Bot) => Promise<boolean>;
|
|
226
|
+
}, bot?: import("mioku").Bot): Promise<void> {
|
|
221
227
|
const memberName =
|
|
222
228
|
options.memberName ||
|
|
223
229
|
(await resolveMemberName(
|
|
224
230
|
options.ctx,
|
|
231
|
+
bot,
|
|
225
232
|
options.groupId,
|
|
226
233
|
options.userId,
|
|
227
|
-
options.selfId,
|
|
228
234
|
));
|
|
229
235
|
await sendSingleWelcome({
|
|
230
236
|
ctx: options.ctx,
|
|
@@ -236,26 +242,27 @@ export async function triggerSingleWelcome(options: {
|
|
|
236
242
|
userId: options.userId,
|
|
237
243
|
memberName,
|
|
238
244
|
promptInjections: options.promptInjections,
|
|
245
|
+
bot,
|
|
239
246
|
tryCustomPrompt: options.tryCustomPrompt,
|
|
240
247
|
});
|
|
241
248
|
}
|
|
242
249
|
|
|
243
250
|
export function registerWelcomeHandler(
|
|
244
|
-
ctx:
|
|
251
|
+
ctx: MiokuContext,
|
|
245
252
|
aiService: AIService | undefined,
|
|
246
253
|
getConfig: () => AdminConfig,
|
|
247
254
|
shouldSuppress?: (info: {
|
|
248
|
-
selfId: number;
|
|
255
|
+
selfId: string | number;
|
|
249
256
|
groupId: number;
|
|
250
257
|
userId: number;
|
|
251
258
|
groupName: string;
|
|
252
|
-
}) => Promise<boolean> | boolean,
|
|
259
|
+
}, bot?: import("mioku").Bot) => Promise<boolean> | boolean,
|
|
253
260
|
tryCustomPrompt?: (info: {
|
|
254
|
-
selfId: number;
|
|
261
|
+
selfId: string | number;
|
|
255
262
|
groupId: number;
|
|
256
263
|
userId: number;
|
|
257
264
|
groupName: string;
|
|
258
|
-
}) => Promise<boolean>,
|
|
265
|
+
}, bot?: import("mioku").Bot) => Promise<boolean>,
|
|
259
266
|
): () => void {
|
|
260
267
|
const batches = getBatchMap();
|
|
261
268
|
|
|
@@ -263,18 +270,19 @@ export function registerWelcomeHandler(
|
|
|
263
270
|
"notice.group.increase",
|
|
264
271
|
async (event) => {
|
|
265
272
|
const cfg = getConfig();
|
|
266
|
-
const
|
|
273
|
+
const bot = event.bot;
|
|
274
|
+
const selfId = event?.self_id || ctx.self_id || "";
|
|
267
275
|
const groupId = Number(event?.group_id || 0);
|
|
268
276
|
const userId = Number(event?.user_id || 0);
|
|
269
277
|
if (!groupId || !userId) return;
|
|
270
|
-
if (userId === selfId) return;
|
|
278
|
+
if (selfId != null && String(userId) === String(selfId)) return;
|
|
271
279
|
|
|
272
280
|
const groupName =
|
|
273
|
-
String(event?.
|
|
281
|
+
String((event.raw as { group_name?: string } | undefined)?.group_name || "").trim() || String(groupId);
|
|
274
282
|
|
|
275
283
|
if (
|
|
276
284
|
shouldSuppress &&
|
|
277
|
-
(await shouldSuppress({ selfId, groupId, userId, groupName }))
|
|
285
|
+
(await shouldSuppress({ selfId, groupId, userId, groupName }, bot))
|
|
278
286
|
) {
|
|
279
287
|
return;
|
|
280
288
|
}
|
|
@@ -286,9 +294,9 @@ export function registerWelcomeHandler(
|
|
|
286
294
|
if (batchWindowMs === 0) {
|
|
287
295
|
const memberName = await resolveMemberName(
|
|
288
296
|
ctx,
|
|
297
|
+
bot,
|
|
289
298
|
groupId,
|
|
290
299
|
userId,
|
|
291
|
-
selfId,
|
|
292
300
|
);
|
|
293
301
|
const welcomeMessage = await flushBatch({
|
|
294
302
|
ctx,
|
|
@@ -298,12 +306,12 @@ export function registerWelcomeHandler(
|
|
|
298
306
|
groupId,
|
|
299
307
|
groupName,
|
|
300
308
|
members: [{ userId, memberName }],
|
|
309
|
+
bot,
|
|
301
310
|
});
|
|
302
311
|
if (!welcomeMessage) return;
|
|
303
|
-
const bot = ctx.pickBot(selfId);
|
|
304
312
|
if (!bot) return;
|
|
305
313
|
try {
|
|
306
|
-
await bot.
|
|
314
|
+
await bot.sendMessage({ type: "group", group_id: groupId}, [ctx.segment.text(welcomeMessage)]);
|
|
307
315
|
} catch (error) {
|
|
308
316
|
ctx.logger.warn(`发送入群欢迎失败: ${error}`);
|
|
309
317
|
}
|
|
@@ -320,7 +328,7 @@ export function registerWelcomeHandler(
|
|
|
320
328
|
state.groupName = groupName;
|
|
321
329
|
}
|
|
322
330
|
|
|
323
|
-
const memberName = await resolveMemberName(ctx, groupId, userId
|
|
331
|
+
const memberName = await resolveMemberName(ctx, bot, groupId, userId);
|
|
324
332
|
if (!state.members.some((m) => m.userId === userId)) {
|
|
325
333
|
state.members.push({ userId, memberName });
|
|
326
334
|
}
|
|
@@ -344,14 +352,14 @@ export function registerWelcomeHandler(
|
|
|
344
352
|
groupId,
|
|
345
353
|
groupName: pending.groupName,
|
|
346
354
|
members: pending.members,
|
|
355
|
+
bot,
|
|
347
356
|
tryCustomPrompt,
|
|
348
357
|
});
|
|
349
358
|
if (!welcomeMessage) return;
|
|
350
359
|
|
|
351
|
-
const bot = ctx.pickBot(selfId);
|
|
352
360
|
if (!bot) return;
|
|
353
361
|
try {
|
|
354
|
-
await bot.
|
|
362
|
+
await bot.sendMessage({ type: "group", group_id: groupId}, [ctx.segment.text(welcomeMessage)]);
|
|
355
363
|
} catch (error) {
|
|
356
364
|
ctx.logger.warn(`发送入群欢迎失败: ${error}`);
|
|
357
365
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mioku-plugin-admin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "管理插件,提供事件通知与群管/个人管理指令",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -12,12 +12,10 @@
|
|
|
12
12
|
"url": "https://github.com/mioku-lab/mioku-plugin-admin.git"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"mioku": "^0.
|
|
16
|
-
"mioki": "^0.16.0"
|
|
15
|
+
"mioku": "^1.0.0"
|
|
17
16
|
},
|
|
18
17
|
"devDependencies": {
|
|
19
|
-
"mioku": "workspace:*"
|
|
20
|
-
"mioki": "^0.16.0"
|
|
18
|
+
"mioku": "workspace:*"
|
|
21
19
|
},
|
|
22
20
|
"mioku": {
|
|
23
21
|
"services": [
|
package/skills/group.ts
CHANGED
|
@@ -1,25 +1,42 @@
|
|
|
1
|
-
import type { AISkill } from "mioku";
|
|
1
|
+
import type { AISkill, Bot, MessageEvent } from "mioku";
|
|
2
|
+
|
|
2
3
|
import { getMemberRole } from "../config";
|
|
3
4
|
import { getImageUrlByMessageId } from "./message-image";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
interface GroupRuntime {
|
|
7
|
+
ctx: { logger?: { error?: (...args: unknown[]) => void } };
|
|
8
|
+
event: MessageEvent;
|
|
9
|
+
bot: Bot;
|
|
10
|
+
groupId: number;
|
|
11
|
+
selfId: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface SkillRuntimeContext {
|
|
15
|
+
ctx?: GroupRuntime["ctx"];
|
|
16
|
+
event?: MessageEvent;
|
|
17
|
+
rawEvent?: MessageEvent;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resolveGroupRuntime(runtimeCtx: SkillRuntimeContext | undefined):
|
|
21
|
+
| GroupRuntime
|
|
22
|
+
| { error: string } {
|
|
6
23
|
const ctx = runtimeCtx?.ctx;
|
|
7
24
|
if (!ctx) return { error: "无法获取上下文" };
|
|
8
25
|
|
|
9
|
-
const event = runtimeCtx?.event
|
|
10
|
-
const groupId = Number(event?.group_id
|
|
26
|
+
const event = runtimeCtx?.event ?? runtimeCtx?.rawEvent;
|
|
27
|
+
const groupId = Number(event?.group_id ?? 0);
|
|
11
28
|
if (!groupId) return { error: "这个工具只能在群聊中使用" };
|
|
12
29
|
|
|
13
|
-
const selfId =
|
|
30
|
+
const selfId = Number(event?.self_id ?? 0);
|
|
14
31
|
if (!selfId) return { error: "无法获取Bot ID" };
|
|
15
32
|
|
|
16
|
-
const bot =
|
|
33
|
+
const bot = event?.bot;
|
|
17
34
|
if (!bot) return { error: "Bot不可用" };
|
|
18
35
|
|
|
19
|
-
return { ctx,
|
|
36
|
+
return { ctx, event: event!, bot, groupId, selfId };
|
|
20
37
|
}
|
|
21
38
|
|
|
22
|
-
function logAdminSkillError(runtimeCtx:
|
|
39
|
+
function logAdminSkillError(runtimeCtx: SkillRuntimeContext | undefined, toolName: string, err: unknown) {
|
|
23
40
|
runtimeCtx?.ctx?.logger?.error?.(
|
|
24
41
|
`[admin skills] ${toolName} failed: ${String(err)}`,
|
|
25
42
|
);
|
|
@@ -32,22 +49,16 @@ function formatGroupRole(role: string): string {
|
|
|
32
49
|
}
|
|
33
50
|
|
|
34
51
|
async function checkDangerousTargetPermission(
|
|
35
|
-
runtime:
|
|
52
|
+
runtime: GroupRuntime,
|
|
36
53
|
targetUserId: number,
|
|
37
54
|
): Promise<string | null> {
|
|
38
|
-
const operatorUserId = Number(runtime.event
|
|
55
|
+
const operatorUserId = Number(runtime.event.user_id ?? 0);
|
|
39
56
|
if (!operatorUserId) return "无法获取当前操作人ID";
|
|
40
57
|
|
|
41
|
-
const operatorRole = await
|
|
42
|
-
runtime.bot,
|
|
43
|
-
runtime.groupId,
|
|
44
|
-
|
|
45
|
-
);
|
|
46
|
-
const targetRole = await getMemberRole(
|
|
47
|
-
runtime.bot,
|
|
48
|
-
runtime.groupId,
|
|
49
|
-
targetUserId,
|
|
50
|
-
);
|
|
58
|
+
const [operatorRole, targetRole] = await Promise.all([
|
|
59
|
+
getMemberRole(runtime.bot, runtime.groupId, operatorUserId),
|
|
60
|
+
getMemberRole(runtime.bot, runtime.groupId, targetUserId),
|
|
61
|
+
]);
|
|
51
62
|
if (operatorRole !== targetRole) return null;
|
|
52
63
|
|
|
53
64
|
return `无权操作同级成员:操作人与被操作人均为${formatGroupRole(operatorRole)}`;
|
|
@@ -94,108 +105,74 @@ const groupAdminSkill: AISkill = {
|
|
|
94
105
|
},
|
|
95
106
|
required: ["action"],
|
|
96
107
|
},
|
|
97
|
-
handler: async (args:
|
|
98
|
-
const runtime =
|
|
108
|
+
handler: async (args: unknown, runtimeCtx?: SkillRuntimeContext) => {
|
|
109
|
+
const runtime = resolveGroupRuntime(runtimeCtx);
|
|
99
110
|
if ("error" in runtime) return { error: runtime.error };
|
|
100
|
-
const { bot,
|
|
101
|
-
const action = String(args?.action
|
|
111
|
+
const { bot, groupId, event } = runtime;
|
|
112
|
+
const action = String((args as { action?: unknown })?.action ?? "");
|
|
102
113
|
|
|
103
114
|
try {
|
|
104
115
|
switch (action) {
|
|
105
116
|
case "kick": {
|
|
106
|
-
const userId = Number(args?.user_id);
|
|
117
|
+
const userId = Number((args as { user_id?: unknown })?.user_id);
|
|
107
118
|
if (!userId) return { error: "kick 需要提供 user_id" };
|
|
108
|
-
const permErr = await checkDangerousTargetPermission(
|
|
109
|
-
runtime,
|
|
110
|
-
userId,
|
|
111
|
-
);
|
|
119
|
+
const permErr = await checkDangerousTargetPermission(runtime, userId);
|
|
112
120
|
if (permErr) return { error: permErr };
|
|
113
|
-
await bot.
|
|
114
|
-
group_id: groupId,
|
|
115
|
-
user_id: userId,
|
|
116
|
-
});
|
|
121
|
+
await bot.kickMember(groupId, userId);
|
|
117
122
|
return { success: true, message: `已将 ${userId} 移出当前群` };
|
|
118
123
|
}
|
|
119
124
|
case "mute": {
|
|
120
|
-
const userId = Number(args?.user_id);
|
|
125
|
+
const userId = Number((args as { user_id?: unknown })?.user_id);
|
|
121
126
|
if (!userId) return { error: "mute 需要提供 user_id" };
|
|
122
|
-
const permErr = await checkDangerousTargetPermission(
|
|
123
|
-
runtime,
|
|
124
|
-
userId,
|
|
125
|
-
);
|
|
127
|
+
const permErr = await checkDangerousTargetPermission(runtime, userId);
|
|
126
128
|
if (permErr) return { error: permErr };
|
|
127
129
|
const duration =
|
|
128
|
-
Number(args
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
};
|
|
130
|
+
Number((args as { duration?: unknown })?.duration) > 0
|
|
131
|
+
? Number((args as { duration?: unknown }).duration)
|
|
132
|
+
: 10 * 60;
|
|
133
|
+
await bot.banMember(groupId, userId, duration);
|
|
134
|
+
return { success: true, message: `已禁言 ${userId} ${duration}秒` };
|
|
134
135
|
}
|
|
135
136
|
case "unmute": {
|
|
136
|
-
const userId = Number(args?.user_id);
|
|
137
|
+
const userId = Number((args as { user_id?: unknown })?.user_id);
|
|
137
138
|
if (!userId) return { error: "unmute 需要提供 user_id" };
|
|
138
|
-
await bot.
|
|
139
|
+
await bot.banMember(groupId, userId, 0);
|
|
139
140
|
return { success: true, message: `已解除 ${userId} 的禁言` };
|
|
140
141
|
}
|
|
141
142
|
case "set_admin": {
|
|
142
|
-
const userId = Number(args?.user_id);
|
|
143
|
+
const userId = Number((args as { user_id?: unknown })?.user_id);
|
|
143
144
|
if (!userId) return { error: "set_admin 需要提供 user_id" };
|
|
144
|
-
await bot.
|
|
145
|
-
|
|
146
|
-
user_id: userId,
|
|
147
|
-
enable: true,
|
|
148
|
-
});
|
|
149
|
-
return {
|
|
150
|
-
success: true,
|
|
151
|
-
message: `已将 ${userId} 设为当前群的管理员`,
|
|
152
|
-
};
|
|
145
|
+
await bot.setMemberAdmin(groupId, userId, true);
|
|
146
|
+
return { success: true, message: `已将 ${userId} 设为当前群的管理员` };
|
|
153
147
|
}
|
|
154
148
|
case "unset_admin": {
|
|
155
|
-
const userId = Number(args?.user_id);
|
|
149
|
+
const userId = Number((args as { user_id?: unknown })?.user_id);
|
|
156
150
|
if (!userId) return { error: "unset_admin 需要提供 user_id" };
|
|
157
|
-
await bot.
|
|
158
|
-
|
|
159
|
-
user_id: userId,
|
|
160
|
-
enable: false,
|
|
161
|
-
});
|
|
162
|
-
return {
|
|
163
|
-
success: true,
|
|
164
|
-
message: `已取消 ${userId} 在当前群的管理员`,
|
|
165
|
-
};
|
|
151
|
+
await bot.setMemberAdmin(groupId, userId, false);
|
|
152
|
+
return { success: true, message: `已取消 ${userId} 在当前群的管理员` };
|
|
166
153
|
}
|
|
167
154
|
case "set_title": {
|
|
168
|
-
const userId = Number(args?.user_id);
|
|
169
|
-
const title = String(args?.title
|
|
155
|
+
const userId = Number((args as { user_id?: unknown })?.user_id);
|
|
156
|
+
const title = String((args as { title?: unknown })?.title ?? "").trim();
|
|
170
157
|
if (!userId || !title) {
|
|
171
158
|
return { error: "set_title 需要提供 user_id 和 title" };
|
|
172
159
|
}
|
|
173
|
-
await
|
|
174
|
-
return {
|
|
175
|
-
success: true,
|
|
176
|
-
message: `已将 ${userId} 的头衔设为 "${title}"`,
|
|
177
|
-
};
|
|
160
|
+
await bot.setMemberTitle(groupId, userId, title);
|
|
161
|
+
return { success: true, message: `已将 ${userId} 的头衔设为 "${title}"` };
|
|
178
162
|
}
|
|
179
163
|
case "set_self_title": {
|
|
180
|
-
const userId = Number(event
|
|
164
|
+
const userId = Number(event.user_id);
|
|
181
165
|
if (!userId) return { error: "无法获取当前用户ID" };
|
|
182
|
-
const title = String(args?.title
|
|
166
|
+
const title = String((args as { title?: unknown })?.title ?? "").trim();
|
|
183
167
|
if (!title) return { error: "set_self_title 需要提供 title" };
|
|
184
|
-
await
|
|
185
|
-
return {
|
|
186
|
-
success: true,
|
|
187
|
-
message: `已将你的头衔设为 "${title}"`,
|
|
188
|
-
};
|
|
168
|
+
await bot.setMemberTitle(groupId, userId, title);
|
|
169
|
+
return { success: true, message: `已将你的头衔设为 "${title}"` };
|
|
189
170
|
}
|
|
190
171
|
default:
|
|
191
172
|
return { error: `未知的 action: ${action}` };
|
|
192
173
|
}
|
|
193
174
|
} catch (err) {
|
|
194
|
-
logAdminSkillError(
|
|
195
|
-
runtimeCtx,
|
|
196
|
-
`admin_group.manage_member.${action}`,
|
|
197
|
-
err,
|
|
198
|
-
);
|
|
175
|
+
logAdminSkillError(runtimeCtx, `admin_group.manage_member.${action}`, err);
|
|
199
176
|
return { error: `执行 ${action} 失败: ${err}` };
|
|
200
177
|
}
|
|
201
178
|
},
|
|
@@ -240,87 +217,58 @@ const groupAdminSkill: AISkill = {
|
|
|
240
217
|
},
|
|
241
218
|
required: ["action"],
|
|
242
219
|
},
|
|
243
|
-
handler: async (args:
|
|
244
|
-
const runtime =
|
|
220
|
+
handler: async (args: unknown, runtimeCtx?: SkillRuntimeContext) => {
|
|
221
|
+
const runtime = resolveGroupRuntime(runtimeCtx);
|
|
245
222
|
if ("error" in runtime) return { error: runtime.error };
|
|
246
|
-
const { bot,
|
|
247
|
-
const action = String(args?.action
|
|
223
|
+
const { bot, groupId, selfId } = runtime;
|
|
224
|
+
const action = String((args as { action?: unknown })?.action ?? "");
|
|
248
225
|
|
|
249
226
|
try {
|
|
250
227
|
switch (action) {
|
|
251
228
|
case "set_whole_ban":
|
|
252
|
-
await bot.
|
|
253
|
-
group_id: groupId,
|
|
254
|
-
enable: true,
|
|
255
|
-
});
|
|
229
|
+
await bot.setGroupWholeBan(groupId, true);
|
|
256
230
|
return { success: true, message: "当前群已开启全体禁言" };
|
|
257
231
|
case "unset_whole_ban":
|
|
258
|
-
await bot.
|
|
259
|
-
group_id: groupId,
|
|
260
|
-
enable: false,
|
|
261
|
-
});
|
|
232
|
+
await bot.setGroupWholeBan(groupId, false);
|
|
262
233
|
return { success: true, message: "当前群已关闭全体禁言" };
|
|
263
234
|
case "set_group_name": {
|
|
264
|
-
const groupName = String(args?.group_name
|
|
265
|
-
if (!groupName)
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
group_id: groupId,
|
|
269
|
-
group_name: groupName,
|
|
270
|
-
});
|
|
271
|
-
return {
|
|
272
|
-
success: true,
|
|
273
|
-
message: `当前群名称已修改为 ${groupName}`,
|
|
274
|
-
};
|
|
235
|
+
const groupName = String((args as { group_name?: unknown })?.group_name ?? "").trim();
|
|
236
|
+
if (!groupName) return { error: "set_group_name 需要提供 group_name" };
|
|
237
|
+
await bot.setGroupName(groupId, groupName);
|
|
238
|
+
return { success: true, message: `当前群名称已修改为 ${groupName}` };
|
|
275
239
|
}
|
|
276
240
|
case "set_self_card": {
|
|
277
|
-
const card = String(args?.card
|
|
241
|
+
const card = String((args as { card?: unknown })?.card ?? "").trim();
|
|
278
242
|
if (!card) return { error: "set_self_card 需要提供 card" };
|
|
279
|
-
await bot.
|
|
280
|
-
return {
|
|
281
|
-
success: true,
|
|
282
|
-
message: `Bot在当前群的群名片已修改为 ${card}`,
|
|
283
|
-
};
|
|
243
|
+
await bot.setMemberCard(groupId, selfId, card);
|
|
244
|
+
return { success: true, message: `Bot在当前群的群名片已修改为 ${card}` };
|
|
284
245
|
}
|
|
285
246
|
case "set_group_avatar": {
|
|
286
|
-
const messageId = Number(args?.message_id);
|
|
247
|
+
const messageId = Number((args as { message_id?: unknown })?.message_id);
|
|
287
248
|
if (!Number.isFinite(messageId) || messageId <= 0) {
|
|
288
249
|
return { error: "set_group_avatar 需要提供有效的 message_id" };
|
|
289
250
|
}
|
|
290
251
|
const imageUrl = await getImageUrlByMessageId(bot, messageId);
|
|
291
|
-
if (!imageUrl) {
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
await bot.api("set_group_portrait", {
|
|
295
|
-
group_id: groupId,
|
|
296
|
-
file: imageUrl,
|
|
297
|
-
});
|
|
252
|
+
if (!imageUrl) return { error: "指定 message_id 中未找到图片" };
|
|
253
|
+
await bot.setGroupPortrait(groupId, imageUrl);
|
|
298
254
|
return { success: true, message: "当前群头像已修改" };
|
|
299
255
|
}
|
|
300
256
|
case "recall_messages": {
|
|
301
|
-
const ids = Array.isArray(args?.message_ids)
|
|
302
|
-
? args.message_ids
|
|
303
|
-
.map((v
|
|
304
|
-
.filter((n
|
|
257
|
+
const ids = Array.isArray((args as { message_ids?: unknown })?.message_ids)
|
|
258
|
+
? ((args as { message_ids: unknown[] }).message_ids)
|
|
259
|
+
.map((v) => Number(v))
|
|
260
|
+
.filter((n) => Number.isFinite(n) && n !== 0)
|
|
305
261
|
: [];
|
|
306
262
|
if (ids.length === 0) {
|
|
307
263
|
return { error: "recall_messages 需要提供至少一个 message_id" };
|
|
308
264
|
}
|
|
309
|
-
const results: Array<{
|
|
310
|
-
message_id: number;
|
|
311
|
-
success: boolean;
|
|
312
|
-
error?: string;
|
|
313
|
-
}> = [];
|
|
265
|
+
const results: Array<{ message_id: number; success: boolean; error?: string }> = [];
|
|
314
266
|
for (const id of ids) {
|
|
315
267
|
try {
|
|
316
|
-
await bot.
|
|
268
|
+
await bot.recallMessage(id);
|
|
317
269
|
results.push({ message_id: id, success: true });
|
|
318
270
|
} catch (err) {
|
|
319
|
-
results.push({
|
|
320
|
-
message_id: id,
|
|
321
|
-
success: false,
|
|
322
|
-
error: String(err),
|
|
323
|
-
});
|
|
271
|
+
results.push({ message_id: id, success: false, error: String(err) });
|
|
324
272
|
}
|
|
325
273
|
}
|
|
326
274
|
const ok = results.filter((r) => r.success).length;
|
|
@@ -334,11 +282,7 @@ const groupAdminSkill: AISkill = {
|
|
|
334
282
|
return { error: `未知的 action: ${action}` };
|
|
335
283
|
}
|
|
336
284
|
} catch (err) {
|
|
337
|
-
logAdminSkillError(
|
|
338
|
-
runtimeCtx,
|
|
339
|
-
`admin_group.manage_group.${action}`,
|
|
340
|
-
err,
|
|
341
|
-
);
|
|
285
|
+
logAdminSkillError(runtimeCtx, `admin_group.manage_group.${action}`, err);
|
|
342
286
|
return { error: `执行 ${action} 失败: ${err}` };
|
|
343
287
|
}
|
|
344
288
|
},
|
|
@@ -346,4 +290,4 @@ const groupAdminSkill: AISkill = {
|
|
|
346
290
|
],
|
|
347
291
|
};
|
|
348
292
|
|
|
349
|
-
export default groupAdminSkill;
|
|
293
|
+
export default groupAdminSkill;
|
package/skills/message-image.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
+
import type { Bot } from "mioku";
|
|
2
|
+
|
|
1
3
|
export async function getImageUrlByMessageId(
|
|
2
|
-
bot:
|
|
4
|
+
bot: Bot,
|
|
3
5
|
messageId: number,
|
|
4
6
|
): Promise<string | null> {
|
|
5
7
|
try {
|
|
6
|
-
const msg =
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
: await bot.api("get_msg", { message_id: messageId });
|
|
8
|
+
const msg = await bot.sendApi<{
|
|
9
|
+
message?: Array<{ type?: string; url?: string; file?: string }>;
|
|
10
|
+
}>("get_msg", { message_id: messageId });
|
|
10
11
|
const segments = Array.isArray(msg?.message) ? msg.message : [];
|
|
11
|
-
const imageSeg = segments.find((seg
|
|
12
|
-
if (!imageSeg)
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
12
|
+
const imageSeg = segments.find((seg) => seg?.type === "image");
|
|
13
|
+
if (!imageSeg) return null;
|
|
15
14
|
|
|
16
15
|
return imageSeg.url || imageSeg.file || null;
|
|
17
16
|
} catch {
|