mioku-plugin-admin 1.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/LICENSE +21 -0
- package/README.md +78 -0
- package/commands/group.ts +457 -0
- package/commands/notice.ts +60 -0
- package/commands/personal.ts +589 -0
- package/config.md +53 -0
- package/config.ts +114 -0
- package/index.ts +52 -0
- package/notify/index.ts +653 -0
- package/package.json +135 -0
- package/skills/group.ts +407 -0
- package/skills/message-image.ts +20 -0
- package/skills/personal.ts +376 -0
- package/skills.ts +7 -0
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
import { MiokiContext } from "mioki";
|
|
2
|
+
import type {
|
|
3
|
+
MessageEvent,
|
|
4
|
+
RecvAtElement,
|
|
5
|
+
RecvElement,
|
|
6
|
+
RecvFaceElement,
|
|
7
|
+
RecvFileElement,
|
|
8
|
+
RecvForwardElement,
|
|
9
|
+
RecvImageElement,
|
|
10
|
+
RecvJsonElement,
|
|
11
|
+
RecvRecordElement,
|
|
12
|
+
RecvReplyElement,
|
|
13
|
+
RecvTextElement,
|
|
14
|
+
RecvVideoElement,
|
|
15
|
+
} from "napcat-sdk";
|
|
16
|
+
import { extractImageUrl } from "../config";
|
|
17
|
+
import { replyAdminErrorNotice } from "./notice";
|
|
18
|
+
|
|
19
|
+
function parseProfileSex(value: string): 0 | 1 | 2 {
|
|
20
|
+
const normalized = String(value || "").trim();
|
|
21
|
+
if (normalized === "男" || normalized === "1") return 1;
|
|
22
|
+
if (normalized === "女" || normalized === "2") return 2;
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function toSendSegment(ctx: MiokiContext, seg: RecvElement): any | null {
|
|
27
|
+
if (seg.type === "text") {
|
|
28
|
+
const text = String((seg as RecvTextElement).text || "");
|
|
29
|
+
return text ? ctx.segment.text(text) : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (seg.type === "image" || seg.type === "record" || seg.type === "video") {
|
|
33
|
+
const mediaSeg = seg as
|
|
34
|
+
| RecvImageElement
|
|
35
|
+
| RecvRecordElement
|
|
36
|
+
| RecvVideoElement;
|
|
37
|
+
const source = String(mediaSeg.url || mediaSeg.file || "").trim();
|
|
38
|
+
if (!source) return null;
|
|
39
|
+
if (seg.type === "image") {
|
|
40
|
+
return ctx.segment.image(source);
|
|
41
|
+
}
|
|
42
|
+
if (seg.type === "record") {
|
|
43
|
+
return (ctx.segment as any).record(source);
|
|
44
|
+
}
|
|
45
|
+
return (ctx.segment as any).video(source);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (seg.type === "file") {
|
|
49
|
+
const fileSeg = seg as RecvFileElement;
|
|
50
|
+
const source = String(fileSeg.url || fileSeg.file || "").trim();
|
|
51
|
+
if (!source) return null;
|
|
52
|
+
return (ctx.segment as any).file(source);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (seg.type === "at") {
|
|
56
|
+
const qq = (seg as RecvAtElement).qq;
|
|
57
|
+
return qq == null ? null : ctx.segment.at(String(qq));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (seg.type === "face") {
|
|
61
|
+
const id = (seg as RecvFaceElement).id;
|
|
62
|
+
return id == null ? null : ctx.segment.face(Number(id));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (seg.type === "reply") {
|
|
66
|
+
const id = (seg as RecvReplyElement).id;
|
|
67
|
+
return id == null ? null : ctx.segment.reply(String(id));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (seg.type === "forward") {
|
|
71
|
+
const id = (seg as RecvForwardElement).id;
|
|
72
|
+
return id == null
|
|
73
|
+
? null
|
|
74
|
+
: ((ctx.segment as any).forward?.(String(id)) ?? null);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (seg.type === "json") {
|
|
78
|
+
const data = (seg as RecvJsonElement).data;
|
|
79
|
+
return ctx.segment.json ? ctx.segment.json(data) : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeIncomingSegments(
|
|
86
|
+
ctx: MiokiContext,
|
|
87
|
+
segments: RecvElement[],
|
|
88
|
+
): any[] {
|
|
89
|
+
if (!Array.isArray(segments)) return [];
|
|
90
|
+
return segments.map((seg) => toSendSegment(ctx, seg)).filter(Boolean);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function buildForwardPayloadAfterCommand(
|
|
94
|
+
ctx: MiokiContext,
|
|
95
|
+
message: RecvElement[],
|
|
96
|
+
commandPattern: RegExp,
|
|
97
|
+
fallbackText?: string,
|
|
98
|
+
): any[] {
|
|
99
|
+
const payload: any[] = [];
|
|
100
|
+
let stripped = false;
|
|
101
|
+
|
|
102
|
+
for (const seg of message) {
|
|
103
|
+
if (seg.type !== "text") {
|
|
104
|
+
const converted = toSendSegment(ctx, seg);
|
|
105
|
+
if (converted) {
|
|
106
|
+
payload.push(converted);
|
|
107
|
+
}
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const original = String((seg as RecvTextElement).text || "");
|
|
111
|
+
if (!stripped) {
|
|
112
|
+
const nextText = original.replace(commandPattern, "");
|
|
113
|
+
if (nextText !== original) {
|
|
114
|
+
stripped = true;
|
|
115
|
+
const text = nextText.trim();
|
|
116
|
+
if (text) {
|
|
117
|
+
payload.push(ctx.segment.text(text));
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (original.trim()) {
|
|
123
|
+
payload.push(ctx.segment.text(original));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (payload.length === 0) {
|
|
128
|
+
const text = String(fallbackText || "").trim();
|
|
129
|
+
if (text) {
|
|
130
|
+
payload.push(ctx.segment.text(text));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return payload;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function toForwardMessages(bot: any, nodes: any[]): any[] {
|
|
138
|
+
const normalizeElements = (elements: any[]): any[] => {
|
|
139
|
+
if (typeof bot?.normalizeSendable === "function") {
|
|
140
|
+
return bot.normalizeSendable(elements);
|
|
141
|
+
}
|
|
142
|
+
return elements.map((element: any) => {
|
|
143
|
+
if (
|
|
144
|
+
element &&
|
|
145
|
+
typeof element === "object" &&
|
|
146
|
+
"type" in element &&
|
|
147
|
+
"data" in element
|
|
148
|
+
) {
|
|
149
|
+
return element;
|
|
150
|
+
}
|
|
151
|
+
if (element && typeof element === "object" && "type" in element) {
|
|
152
|
+
const { type, ...data } = element;
|
|
153
|
+
return { type, data };
|
|
154
|
+
}
|
|
155
|
+
return element;
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return nodes.map((node: any) => {
|
|
160
|
+
const rawNode =
|
|
161
|
+
node && typeof node === "object" && "type" in node && "data" in node
|
|
162
|
+
? { type: node.type, ...node.data }
|
|
163
|
+
: node;
|
|
164
|
+
if (!rawNode || rawNode.type !== "node") {
|
|
165
|
+
return normalizeElements([rawNode])[0];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const content = Array.isArray(rawNode.content) ? rawNode.content : [];
|
|
169
|
+
if ("id" in rawNode && rawNode.id) {
|
|
170
|
+
return {
|
|
171
|
+
type: "node",
|
|
172
|
+
data: {
|
|
173
|
+
user_id: rawNode.user_id,
|
|
174
|
+
nickname: rawNode.nickname,
|
|
175
|
+
id: rawNode.id,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
type: "node",
|
|
182
|
+
data: {
|
|
183
|
+
user_id: rawNode.user_id,
|
|
184
|
+
nickname: rawNode.nickname,
|
|
185
|
+
content: normalizeElements(content),
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function sendForwardByEvent(options: {
|
|
192
|
+
bot: any;
|
|
193
|
+
event: any;
|
|
194
|
+
messages: any[];
|
|
195
|
+
}): Promise<void> {
|
|
196
|
+
const { bot, event, messages } = options;
|
|
197
|
+
const chunkSize = 50;
|
|
198
|
+
|
|
199
|
+
for (let i = 0; i < messages.length; i += chunkSize) {
|
|
200
|
+
const chunk = messages.slice(i, i + chunkSize);
|
|
201
|
+
if (event?.message_type === "group" && event?.group_id) {
|
|
202
|
+
await bot.api("send_group_forward_msg", {
|
|
203
|
+
group_id: event.group_id,
|
|
204
|
+
messages: chunk,
|
|
205
|
+
});
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
await bot.api("send_private_forward_msg", {
|
|
210
|
+
user_id: event.user_id,
|
|
211
|
+
messages: chunk,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function registerPersonalCommands(ctx: MiokiContext) {
|
|
217
|
+
ctx.handle("message", async (event: MessageEvent) => {
|
|
218
|
+
const text = ctx.text(event)?.trim();
|
|
219
|
+
if (!text) return;
|
|
220
|
+
if (event.user_id === event.self_id) return;
|
|
221
|
+
|
|
222
|
+
const isMaster = ctx.isOwner?.(event) ?? false;
|
|
223
|
+
|
|
224
|
+
const selfId = event.self_id;
|
|
225
|
+
const bot = ctx.pickBot(selfId);
|
|
226
|
+
if (!bot) return;
|
|
227
|
+
|
|
228
|
+
const isGroup = event.message_type === "group";
|
|
229
|
+
|
|
230
|
+
if (text.startsWith("/改头像")) {
|
|
231
|
+
if (!isMaster) {
|
|
232
|
+
ctx.logger.warn("[admin] 改头像功能仅主人可用");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const imageUrl = extractImageUrl(event.message);
|
|
236
|
+
if (!imageUrl) {
|
|
237
|
+
return event.reply("图片呢图片呢~", true);
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
await bot.api("set_qq_avatar", { file: imageUrl });
|
|
241
|
+
await event.reply("done");
|
|
242
|
+
} catch (err) {
|
|
243
|
+
await replyAdminErrorNotice({
|
|
244
|
+
ctx,
|
|
245
|
+
event,
|
|
246
|
+
instruction: "改头像执行失败,请简要说明失败并建议稍后重试。",
|
|
247
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
248
|
+
error: err,
|
|
249
|
+
});
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (text.startsWith("/改昵称")) {
|
|
256
|
+
if (!isMaster) {
|
|
257
|
+
ctx.logger.warn("[admin] 改昵称功能仅主人可用");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const nickname = text.replace(/^\/改昵称\s*/, "").trim();
|
|
261
|
+
if (!nickname) {
|
|
262
|
+
return event.reply("想改成什么昵称呀~", true);
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
await bot.api("set_qq_profile", { nickname });
|
|
266
|
+
await event.reply("done");
|
|
267
|
+
} catch (err) {
|
|
268
|
+
await replyAdminErrorNotice({
|
|
269
|
+
ctx,
|
|
270
|
+
event,
|
|
271
|
+
instruction: "改昵称执行失败,请简要说明失败并建议稍后重试。",
|
|
272
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
273
|
+
error: err,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (text.startsWith("/改签名")) {
|
|
280
|
+
if (!isMaster) {
|
|
281
|
+
ctx.logger.warn("[admin] 改签名功能仅主人可用");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const personalNote = text.replace(/^\/改签名\s*/, "").trim();
|
|
285
|
+
if (!personalNote) {
|
|
286
|
+
return event.reply("想改成什么签名呀~", true);
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
await bot.api("set_qq_profile", { personal_note: personalNote });
|
|
290
|
+
await event.reply("done");
|
|
291
|
+
} catch (err) {
|
|
292
|
+
await replyAdminErrorNotice({
|
|
293
|
+
ctx,
|
|
294
|
+
event,
|
|
295
|
+
instruction: "改签名执行失败,请简要说明失败并建议稍后重试。",
|
|
296
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
297
|
+
error: err,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (text.startsWith("/改性别")) {
|
|
304
|
+
if (!isMaster) {
|
|
305
|
+
ctx.logger.warn("[admin] 改性别功能仅主人可用");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const genderText = text.replace(/^\/改性别\s*/, "").trim();
|
|
309
|
+
const sex = parseProfileSex(genderText);
|
|
310
|
+
try {
|
|
311
|
+
await bot.api("set_qq_profile", { sex });
|
|
312
|
+
await event.reply("done");
|
|
313
|
+
} catch (err) {
|
|
314
|
+
await replyAdminErrorNotice({
|
|
315
|
+
ctx,
|
|
316
|
+
event,
|
|
317
|
+
instruction: "改性别执行失败,请简要说明失败并建议稍后重试。",
|
|
318
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
319
|
+
error: err,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (text.startsWith("/删好友")) {
|
|
326
|
+
if (!isMaster) {
|
|
327
|
+
ctx.logger.warn("[admin] 删好友功能仅主人可用");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const qq = parseInt(text.replace(/^\/删好友\s*/, "").trim(), 10);
|
|
331
|
+
if (!qq) {
|
|
332
|
+
await replyAdminErrorNotice({
|
|
333
|
+
ctx,
|
|
334
|
+
event,
|
|
335
|
+
instruction: "用户触发删好友时缺少QQ号,请给出示例:/删好友 123456。",
|
|
336
|
+
fallbackMessage: "你要删谁呀~",
|
|
337
|
+
});
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
await bot.api("delete_friend", { user_id: qq });
|
|
342
|
+
await event.reply("done");
|
|
343
|
+
} catch (err) {
|
|
344
|
+
await replyAdminErrorNotice({
|
|
345
|
+
ctx,
|
|
346
|
+
event,
|
|
347
|
+
instruction: "删好友执行失败,请简要说明失败并建议稍后重试。",
|
|
348
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
349
|
+
error: err,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (text.startsWith("/退群")) {
|
|
356
|
+
if (!isMaster) {
|
|
357
|
+
ctx.logger.warn("[admin] 退群功能仅主人可用");
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const targetGroup = parseInt(text.replace(/^\/退群\s*/, "").trim(), 10);
|
|
361
|
+
if (!targetGroup) {
|
|
362
|
+
await replyAdminErrorNotice({
|
|
363
|
+
ctx,
|
|
364
|
+
event,
|
|
365
|
+
instruction: "用户触发退群时缺少群号,请给出示例:/退群 123456。",
|
|
366
|
+
fallbackMessage: "你想退哪个群呀~",
|
|
367
|
+
});
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
await bot.api("set_group_leave", {
|
|
372
|
+
group_id: targetGroup,
|
|
373
|
+
is_dismiss: false,
|
|
374
|
+
});
|
|
375
|
+
await event.reply("done");
|
|
376
|
+
} catch (err) {
|
|
377
|
+
await replyAdminErrorNotice({
|
|
378
|
+
ctx,
|
|
379
|
+
event,
|
|
380
|
+
instruction: "退群执行失败,请简要说明失败并建议稍后重试。",
|
|
381
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
382
|
+
error: err,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (text.startsWith("/发好友")) {
|
|
389
|
+
if (!isMaster) {
|
|
390
|
+
ctx.logger.warn("[admin] 发好友功能仅主人可用");
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const matched = text.match(/^\/发好友\s*(\d+)\s*([\s\S]*)$/);
|
|
394
|
+
if (!matched) {
|
|
395
|
+
await replyAdminErrorNotice({
|
|
396
|
+
ctx,
|
|
397
|
+
event,
|
|
398
|
+
instruction:
|
|
399
|
+
"用户触发发好友指令时参数不足,请提示用法:/发好友 QQ号 内容。",
|
|
400
|
+
fallbackMessage: "要发给谁、发什么呀~",
|
|
401
|
+
});
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const targetUser = Number(matched[1]);
|
|
405
|
+
const fallbackText = String(matched[2] || "").trim();
|
|
406
|
+
const payload = buildForwardPayloadAfterCommand(
|
|
407
|
+
ctx,
|
|
408
|
+
event?.message || [],
|
|
409
|
+
/^\/发好友\s*\d+\s*/,
|
|
410
|
+
fallbackText,
|
|
411
|
+
);
|
|
412
|
+
if (!targetUser || !payload.length) {
|
|
413
|
+
await replyAdminErrorNotice({
|
|
414
|
+
ctx,
|
|
415
|
+
event,
|
|
416
|
+
instruction:
|
|
417
|
+
"用户触发发好友指令时缺少目标QQ号或内容,请提示用法:/发好友 QQ号 内容(也支持图片/语音/视频)。",
|
|
418
|
+
fallbackMessage: "要发给谁、发什么呀~",
|
|
419
|
+
});
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
await bot.sendPrivateMsg(targetUser, payload);
|
|
424
|
+
await event.reply("done");
|
|
425
|
+
} catch (err) {
|
|
426
|
+
await replyAdminErrorNotice({
|
|
427
|
+
ctx,
|
|
428
|
+
event,
|
|
429
|
+
instruction: "发好友消息执行失败,请简要说明失败并建议稍后重试。",
|
|
430
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
431
|
+
error: err,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (text.startsWith("/发群聊")) {
|
|
438
|
+
if (!isMaster) {
|
|
439
|
+
ctx.logger.warn("[admin] 发群聊功能仅主人可用");
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const matched = text.match(/^\/发群聊\s*(\d+)\s*([\s\S]*)$/);
|
|
443
|
+
if (!matched) {
|
|
444
|
+
await replyAdminErrorNotice({
|
|
445
|
+
ctx,
|
|
446
|
+
event,
|
|
447
|
+
instruction:
|
|
448
|
+
"用户触发发群聊指令时参数不足,请提示用法:/发群聊 群号 内容。",
|
|
449
|
+
fallbackMessage: "要发到哪个群、发什么呀~",
|
|
450
|
+
});
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const targetGroup = Number(matched[1]);
|
|
454
|
+
const fallbackText = String(matched[2] || "").trim();
|
|
455
|
+
const payload = buildForwardPayloadAfterCommand(
|
|
456
|
+
ctx,
|
|
457
|
+
event?.message || [],
|
|
458
|
+
/^\/发群聊\s*\d+\s*/,
|
|
459
|
+
fallbackText,
|
|
460
|
+
);
|
|
461
|
+
if (!targetGroup || !payload.length) {
|
|
462
|
+
await replyAdminErrorNotice({
|
|
463
|
+
ctx,
|
|
464
|
+
event,
|
|
465
|
+
instruction:
|
|
466
|
+
"用户触发发群聊指令时缺少群号或内容,请提示用法:/发群聊 群号 内容(也支持图片/语音/视频)。",
|
|
467
|
+
fallbackMessage: "要发到哪个群、发什么呀~",
|
|
468
|
+
});
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
await bot.sendGroupMsg(targetGroup, payload);
|
|
473
|
+
await event.reply("done");
|
|
474
|
+
} catch (err) {
|
|
475
|
+
await replyAdminErrorNotice({
|
|
476
|
+
ctx,
|
|
477
|
+
event,
|
|
478
|
+
instruction: "发群聊消息执行失败,请简要说明失败并建议稍后重试。",
|
|
479
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
480
|
+
error: err,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (text === "/全部好友") {
|
|
487
|
+
if (!isMaster) {
|
|
488
|
+
ctx.logger.warn("[admin] 全部好友功能仅主人可用");
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (isGroup) {
|
|
492
|
+
await event.reply("在私聊使用试试看吧~", true);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
try {
|
|
496
|
+
const friendList: any[] = await bot.api("get_friend_list");
|
|
497
|
+
if (!Array.isArray(friendList) || friendList.length === 0) {
|
|
498
|
+
await replyAdminErrorNotice({
|
|
499
|
+
ctx,
|
|
500
|
+
event,
|
|
501
|
+
instruction: "查询全部好友时列表为空,请简短告知当前没有好友数据。",
|
|
502
|
+
fallbackMessage: "你现在还没有好友哦~",
|
|
503
|
+
});
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const nodes = friendList.map((friend: any) =>
|
|
507
|
+
ctx.segment.node({
|
|
508
|
+
user_id: String(friend.user_id),
|
|
509
|
+
nickname:
|
|
510
|
+
friend.nickname || friend.remark || String(friend.user_id),
|
|
511
|
+
content: [
|
|
512
|
+
ctx.segment.image(
|
|
513
|
+
`https://q1.qlogo.cn/g?b=qq&nk=${friend.user_id}&s=640`,
|
|
514
|
+
),
|
|
515
|
+
ctx.segment.text(
|
|
516
|
+
`昵称:${friend.nickname || "未知"}\nQQ号:${friend.user_id}\n备注:${friend.remark || "无"}`,
|
|
517
|
+
),
|
|
518
|
+
],
|
|
519
|
+
}),
|
|
520
|
+
);
|
|
521
|
+
await sendForwardByEvent({
|
|
522
|
+
bot,
|
|
523
|
+
event,
|
|
524
|
+
messages: toForwardMessages(bot, nodes),
|
|
525
|
+
});
|
|
526
|
+
} catch (err) {
|
|
527
|
+
await replyAdminErrorNotice({
|
|
528
|
+
ctx,
|
|
529
|
+
event,
|
|
530
|
+
instruction: "获取好友列表失败,请简要说明失败并建议稍后重试。",
|
|
531
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
532
|
+
error: err,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (text === "/全部群聊") {
|
|
539
|
+
if (!isMaster) {
|
|
540
|
+
ctx.logger.warn("[admin] 全部群聊功能仅主人可用");
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
if (isGroup) {
|
|
544
|
+
await event.reply("在私聊使用试试看吧~", true);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
const groupList: any[] = await bot.api("get_group_list");
|
|
549
|
+
if (!Array.isArray(groupList) || groupList.length === 0) {
|
|
550
|
+
await replyAdminErrorNotice({
|
|
551
|
+
ctx,
|
|
552
|
+
event,
|
|
553
|
+
instruction: "查询全部群聊时列表为空,请简短告知当前没有群聊数据。",
|
|
554
|
+
fallbackMessage: "你现在还没有群聊哦~",
|
|
555
|
+
});
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const nodes = groupList.map((group: any) =>
|
|
559
|
+
ctx.segment.node({
|
|
560
|
+
user_id: String(selfId),
|
|
561
|
+
nickname: String(selfId),
|
|
562
|
+
content: [
|
|
563
|
+
ctx.segment.image(
|
|
564
|
+
`https://p.qlogo.cn/gh/${group.group_id}/${group.group_id}/640/`,
|
|
565
|
+
),
|
|
566
|
+
ctx.segment.text(
|
|
567
|
+
`群名称:${group.group_name || "未知"}\n群号:${group.group_id}\n人数:${group.member_count || "未知"}`,
|
|
568
|
+
),
|
|
569
|
+
],
|
|
570
|
+
}),
|
|
571
|
+
);
|
|
572
|
+
await sendForwardByEvent({
|
|
573
|
+
bot,
|
|
574
|
+
event,
|
|
575
|
+
messages: toForwardMessages(bot, nodes),
|
|
576
|
+
});
|
|
577
|
+
} catch (err) {
|
|
578
|
+
await replyAdminErrorNotice({
|
|
579
|
+
ctx,
|
|
580
|
+
event,
|
|
581
|
+
instruction: "获取群聊列表失败,请简要说明失败并建议稍后重试。",
|
|
582
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
583
|
+
error: err,
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
}
|
package/config.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Admin 插件配置
|
|
3
|
+
description: 配置Bot管理插件的通知和功能选项
|
|
4
|
+
fields:
|
|
5
|
+
- key: base.notifyTarget
|
|
6
|
+
label: 通知目标
|
|
7
|
+
type: multi-select
|
|
8
|
+
source: qq_friends
|
|
9
|
+
description: 选择接收通知的主人QQ号,空为全部主人都发送
|
|
10
|
+
placeholder: 选择通知目标
|
|
11
|
+
|
|
12
|
+
- key: base.notifyFriendMsg
|
|
13
|
+
label: 好友消息通知
|
|
14
|
+
type: switch
|
|
15
|
+
description: 收到好友私聊消息时通知主人
|
|
16
|
+
|
|
17
|
+
- key: base.notifyFriendRequest
|
|
18
|
+
label: 好友申请通知
|
|
19
|
+
type: switch
|
|
20
|
+
description: 收到好友申请时通知主人
|
|
21
|
+
|
|
22
|
+
- key: base.notifyGroupInvite
|
|
23
|
+
label: 群邀请通知
|
|
24
|
+
type: switch
|
|
25
|
+
description: 收到群邀请时通知主人
|
|
26
|
+
|
|
27
|
+
- key: base.notifyGroupBan
|
|
28
|
+
label: Bot被禁言通知
|
|
29
|
+
type: switch
|
|
30
|
+
description: Bot被禁言时通知主人
|
|
31
|
+
|
|
32
|
+
- key: base.notifyGroupUnban
|
|
33
|
+
label: Bot解除禁言通知
|
|
34
|
+
type: switch
|
|
35
|
+
description: Bot被解除禁言时通知主人
|
|
36
|
+
|
|
37
|
+
- key: base.notifyGroupKick
|
|
38
|
+
label: Bot被踢通知
|
|
39
|
+
type: switch
|
|
40
|
+
description: Bot被踢出群聊时通知主人
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
```mioku-fields
|
|
45
|
+
keys:
|
|
46
|
+
- base.notifyTarget
|
|
47
|
+
- base.notifyFriendMsg
|
|
48
|
+
- base.notifyFriendRequest
|
|
49
|
+
- base.notifyGroupInvite
|
|
50
|
+
- base.notifyGroupBan
|
|
51
|
+
- base.notifyGroupUnban
|
|
52
|
+
- base.notifyGroupKick
|
|
53
|
+
```
|