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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mioku Lab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Admin Plugin
|
|
2
|
+
|
|
3
|
+
Mioku 的管理插件,提供事件通知与 Bot 管理指令。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- **事件通知**:好友消息、好友申请、群邀请、Bot 被禁言时自动通知主人
|
|
8
|
+
- **个人管理**:修改 Bot 头像、昵称、个性签名、性别,发送消息,查看好友/群列表等
|
|
9
|
+
- **群管理**:踢人、禁言、解禁、设管理、全体禁言、改头衔等群管操作
|
|
10
|
+
- **AI 技能**:支持 AI 调用个人管理工具(owner 权限)和群管理工具(admin 权限)
|
|
11
|
+
- **WebUI 配置**:可视化配置通知开关
|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
将 `admin` 文件夹放入 Mioku 的 `plugins/` 目录即可
|
|
16
|
+
|
|
17
|
+
## 配置
|
|
18
|
+
|
|
19
|
+
配置文件位于 `config/admin/base.json`,也可通过 WebUI 管理。
|
|
20
|
+
|
|
21
|
+
| 配置项 | 类型 | 默认值 | 说明 |
|
|
22
|
+
|-----------------------|----------|------|--------------------|
|
|
23
|
+
| `notifyTarget` | number[] | [] | 通知目标 QQ 号,空则通知全部主人 |
|
|
24
|
+
| `notifyFriendMsg` | boolean | true | 好友私聊消息通知 |
|
|
25
|
+
| `notifyFriendRequest` | boolean | true | 好友申请通知 |
|
|
26
|
+
| `notifyGroupInvite` | boolean | true | 群邀请通知 |
|
|
27
|
+
| `notifyGroupBan` | boolean | true | Bot 被禁言通知 |
|
|
28
|
+
| `notifyGroupUnban` | boolean | true | Bot 被解除禁言通知 |
|
|
29
|
+
| `notifyGroupKick` | boolean | true | Bot 被踢出群聊通知 |
|
|
30
|
+
|
|
31
|
+
## 事件通知
|
|
32
|
+
|
|
33
|
+
当配置开启后,以下事件会通过私聊通知主人:
|
|
34
|
+
|
|
35
|
+
- **好友消息**:`[好友消息] 昵称(QQ号): 内容`
|
|
36
|
+
- **好友申请**:`[好友申请] QQ号 验证消息: xxx`,回复「通过」即可同意
|
|
37
|
+
- **群邀请**:`[群邀请] 群号 邀请人QQ号`,回复「通过」即可同意
|
|
38
|
+
- **Bot 被禁言**:`[Bot被禁言] 群号 操作人 时长`
|
|
39
|
+
- **Bot 被踢**:`[Bot被踢] 群号 操作者QQ 类型`
|
|
40
|
+
|
|
41
|
+
通知由触发事件的 Bot 发送,多 Bot 实例时不会重复通知。
|
|
42
|
+
|
|
43
|
+
## 指令
|
|
44
|
+
|
|
45
|
+
除特别说明外,管理指令需要使用 `/` 前缀。
|
|
46
|
+
|
|
47
|
+
### 主人指令
|
|
48
|
+
|
|
49
|
+
| 指令 | 用法 | 说明 |
|
|
50
|
+
|-------|---------------|------------------------|
|
|
51
|
+
| /改头像 | `/改头像 [图片]` | 修改 Bot 头像,发送图片或引用图片 |
|
|
52
|
+
| /改昵称 | `/改昵称 昵称` | 修改 Bot 昵称 |
|
|
53
|
+
| /改签名 | `/改签名 签名内容` | 修改 Bot 个性签名 |
|
|
54
|
+
| /改性别 | `/改性别 男/女/无` | 修改 Bot 性别 |
|
|
55
|
+
| 改群名片 | `/改群名片 名片` | 修改 Bot 群名片(群聊中使用) |
|
|
56
|
+
| /改群昵称 | `/改群昵称 昵称` | 同改群名片 |
|
|
57
|
+
| /改群头像 | `/改群头像 [图片]` | 修改群头像(群聊中使用) |
|
|
58
|
+
| /删好友 | `/删好友 QQ号` | 删除好友 |
|
|
59
|
+
| /退群 | `/退群 群号` | 退出指定群聊 |
|
|
60
|
+
| /发好友 | `/发好友 QQ号 内容` | 给好友发送私聊消息 |
|
|
61
|
+
| /发群聊 | `/发群聊 群号 内容` | 给群聊发送消息 |
|
|
62
|
+
| /全部好友 | `/全部好友` | 获取全部好友列表(仅私聊,转发消息,含头像) |
|
|
63
|
+
| /全部群聊 | `/全部群聊` | 获取全部群聊列表(仅私聊,转发消息,含头像) |
|
|
64
|
+
|
|
65
|
+
### 群管指令
|
|
66
|
+
|
|
67
|
+
以下指令在群聊中使用,需要发送者为群主、群管理或主人。
|
|
68
|
+
|
|
69
|
+
| 指令 | 用法 | 说明 |
|
|
70
|
+
|-------|---------------|------------------------|
|
|
71
|
+
| 改头衔 | `/改头衔 @人 头衔` | 设置群成员头衔(需 Bot 为群主) |
|
|
72
|
+
| 我要头衔 | `我要头衔 头衔` | 普通群员给自己设置头衔(需 Bot 为群主) |
|
|
73
|
+
| 踢 | `/踢 @人` | 踢出群成员 |
|
|
74
|
+
| 禁言 | `/禁言 @人 10分钟` | 禁言成员,支持分钟/小时/天 |
|
|
75
|
+
| 解禁 | `/解禁 @人` | 解除禁言 |
|
|
76
|
+
| 设管理 | `/设管理 @人` | 设置群管理员(需 Bot 为群主) |
|
|
77
|
+
| /全体禁言 | `/全体禁言` | 开启全体禁言 |
|
|
78
|
+
| /全体解禁 | `/全体解禁` | 关闭全体禁言 |
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import type { MiokiContext } from "mioki";
|
|
2
|
+
import {
|
|
3
|
+
extractImageUrl,
|
|
4
|
+
getAtUserId,
|
|
5
|
+
getMemberRole,
|
|
6
|
+
parseDuration,
|
|
7
|
+
} from "../config";
|
|
8
|
+
import { replyAdminErrorNotice } from "./notice";
|
|
9
|
+
|
|
10
|
+
function formatGroupRole(role: string): string {
|
|
11
|
+
if (role === "owner") return "群主";
|
|
12
|
+
if (role === "admin") return "管理员";
|
|
13
|
+
return "群成员";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function registerGroupAdminCommands(ctx: MiokiContext) {
|
|
17
|
+
ctx.handle("message", async (event: any) => {
|
|
18
|
+
const text = ctx.text(event)?.trim();
|
|
19
|
+
if (!text) return;
|
|
20
|
+
if (event.user_id === event.self_id) return;
|
|
21
|
+
|
|
22
|
+
const isGroupAdminCommand = [
|
|
23
|
+
"改头衔",
|
|
24
|
+
"/改头衔",
|
|
25
|
+
"我要头衔",
|
|
26
|
+
"踢",
|
|
27
|
+
"/踢",
|
|
28
|
+
"禁言",
|
|
29
|
+
"/禁言",
|
|
30
|
+
"解禁",
|
|
31
|
+
"/解禁",
|
|
32
|
+
"设管理",
|
|
33
|
+
"/设管理",
|
|
34
|
+
"/全体禁言",
|
|
35
|
+
"/全体解禁",
|
|
36
|
+
"改群名片",
|
|
37
|
+
"/改群名片",
|
|
38
|
+
"/改群昵称",
|
|
39
|
+
"/改群头像",
|
|
40
|
+
].some((prefix) => text.startsWith(prefix));
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const selfId = event.self_id;
|
|
44
|
+
const bot = ctx.pickBot(selfId);
|
|
45
|
+
if (!bot) return;
|
|
46
|
+
|
|
47
|
+
const isGroup = event.message_type === "group";
|
|
48
|
+
const groupId: number | undefined = isGroup ? event.group_id : undefined;
|
|
49
|
+
if (!isGroup || !groupId) return;
|
|
50
|
+
|
|
51
|
+
const isMaster = ctx.isOwner?.(event) ?? false;
|
|
52
|
+
const senderRole = await getMemberRole(bot, groupId, event.user_id);
|
|
53
|
+
const hasAdminPermission =
|
|
54
|
+
isMaster || senderRole === "owner" || senderRole === "admin";
|
|
55
|
+
|
|
56
|
+
const replyDone = async () => {
|
|
57
|
+
await event.reply("done");
|
|
58
|
+
};
|
|
59
|
+
const ensureAdminPermission = async (instruction?: string) => {
|
|
60
|
+
if (hasAdminPermission) return true;
|
|
61
|
+
await replyAdminErrorNotice({
|
|
62
|
+
ctx,
|
|
63
|
+
event,
|
|
64
|
+
instruction:
|
|
65
|
+
instruction ||
|
|
66
|
+
"用户想让你执行群管操作但用户不是群主或管理员,无权限让操作",
|
|
67
|
+
fallbackMessage: "你得是群主或管理员才行哦~",
|
|
68
|
+
});
|
|
69
|
+
return false;
|
|
70
|
+
};
|
|
71
|
+
const ensureDangerousTargetPermission = async (
|
|
72
|
+
targetUserId: number,
|
|
73
|
+
actionName: string,
|
|
74
|
+
) => {
|
|
75
|
+
const targetRole = await getMemberRole(bot, groupId, targetUserId);
|
|
76
|
+
if (senderRole !== targetRole) return true;
|
|
77
|
+
|
|
78
|
+
await replyAdminErrorNotice({
|
|
79
|
+
ctx,
|
|
80
|
+
event,
|
|
81
|
+
instruction: `用户想${actionName}目标成员,但操作人与被操作人群身份相同,均为${formatGroupRole(senderRole)},群管危险操作无权操作同等级成员。请明确告诉用户无权操作同级成员。`,
|
|
82
|
+
fallbackMessage: "无权操作同级成员哦~",
|
|
83
|
+
});
|
|
84
|
+
return false;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// 普通群员只能给自己设置头衔。
|
|
88
|
+
if (text.startsWith("我要头衔")) {
|
|
89
|
+
const atUser = getAtUserId(event.message);
|
|
90
|
+
if (atUser && atUser !== event.user_id) {
|
|
91
|
+
await event.reply("管好自己呗~", true);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const title = text.replace(/^我要头衔\s*/, "").trim();
|
|
96
|
+
if (!title) {
|
|
97
|
+
await event.reply("想要什么头衔呀~", true);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await (bot as any).setGroupSpecialTitle(
|
|
103
|
+
groupId,
|
|
104
|
+
event.user_id,
|
|
105
|
+
title,
|
|
106
|
+
);
|
|
107
|
+
await replyDone();
|
|
108
|
+
} catch (err) {
|
|
109
|
+
await replyAdminErrorNotice({
|
|
110
|
+
ctx,
|
|
111
|
+
event,
|
|
112
|
+
instruction: `为用户设置个人头衔执行失败:${String(err)},请简要说明失败并建议稍后重试`,
|
|
113
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
114
|
+
error: err,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 改头衔
|
|
121
|
+
if (text.startsWith("改头衔") || text.startsWith("/改头衔")) {
|
|
122
|
+
if (!(await ensureAdminPermission())) return;
|
|
123
|
+
|
|
124
|
+
const rest = text.replace(/^\/?改头衔\s*/, "").trim();
|
|
125
|
+
const atUser = getAtUserId(event.message);
|
|
126
|
+
let targetUser: number | undefined = atUser;
|
|
127
|
+
let title: string;
|
|
128
|
+
|
|
129
|
+
if (atUser) {
|
|
130
|
+
title = rest.replace(/@\d+\s*/, "").trim();
|
|
131
|
+
} else {
|
|
132
|
+
const parts = rest.split(/\s+/);
|
|
133
|
+
if (parts.length < 2) {
|
|
134
|
+
await replyAdminErrorNotice({
|
|
135
|
+
ctx,
|
|
136
|
+
event,
|
|
137
|
+
instruction:
|
|
138
|
+
"用户在让你修改别人的头衔时缺少参数,请提示用户命令后需要加qq号或@人",
|
|
139
|
+
fallbackMessage: "想改谁的头衔呀~",
|
|
140
|
+
});
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
targetUser = parseInt(parts[0], 10);
|
|
144
|
+
title = parts.slice(1).join(" ");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!targetUser || !title) {
|
|
148
|
+
await replyAdminErrorNotice({
|
|
149
|
+
ctx,
|
|
150
|
+
event,
|
|
151
|
+
instruction:
|
|
152
|
+
"用户触发改头衔时参数无效,请提示用户命令后需要加qq号或@人",
|
|
153
|
+
fallbackMessage: "想改谁的头衔呀~",
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
await (bot as any).setGroupSpecialTitle(groupId, targetUser, title);
|
|
160
|
+
await replyDone();
|
|
161
|
+
} catch (err) {
|
|
162
|
+
await replyAdminErrorNotice({
|
|
163
|
+
ctx,
|
|
164
|
+
event,
|
|
165
|
+
instruction: `改头衔执行失败: ${String(err)},请简要说明失败并建议稍后重试`,
|
|
166
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
167
|
+
error: err,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 踢
|
|
174
|
+
if (text.startsWith("踢") || text.startsWith("/踢")) {
|
|
175
|
+
if (!(await ensureAdminPermission())) return;
|
|
176
|
+
const atUser = getAtUserId(event.message);
|
|
177
|
+
if (!atUser) {
|
|
178
|
+
await replyAdminErrorNotice({
|
|
179
|
+
ctx,
|
|
180
|
+
event,
|
|
181
|
+
instruction:
|
|
182
|
+
"用户希望从群聊中踢出一个人,请提醒要在命令后@要踢出的人",
|
|
183
|
+
fallbackMessage: "你想踢掉谁呀~",
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (!(await ensureDangerousTargetPermission(atUser, "踢出"))) return;
|
|
188
|
+
try {
|
|
189
|
+
await bot.api("set_group_kick", {
|
|
190
|
+
group_id: groupId,
|
|
191
|
+
user_id: atUser,
|
|
192
|
+
});
|
|
193
|
+
await replyDone();
|
|
194
|
+
} catch (err) {
|
|
195
|
+
await replyAdminErrorNotice({
|
|
196
|
+
ctx,
|
|
197
|
+
event,
|
|
198
|
+
instruction: `踢执行失败${String(err)},请简要说明失败`,
|
|
199
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
200
|
+
error: err,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (
|
|
207
|
+
text.startsWith("禁言") ||
|
|
208
|
+
text.startsWith("/禁言") ||
|
|
209
|
+
text.startsWith("/禁") ||
|
|
210
|
+
text.startsWith("禁")
|
|
211
|
+
) {
|
|
212
|
+
if (!(await ensureAdminPermission())) return;
|
|
213
|
+
const atUser = getAtUserId(event.message);
|
|
214
|
+
if (!atUser) {
|
|
215
|
+
await replyAdminErrorNotice({
|
|
216
|
+
ctx,
|
|
217
|
+
event,
|
|
218
|
+
instruction:
|
|
219
|
+
"用户希望禁言一个人,但是没有目标,请提醒要在命令后@要禁言的人",
|
|
220
|
+
fallbackMessage: "想把谁关小黑屋呀~",
|
|
221
|
+
});
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (!(await ensureDangerousTargetPermission(atUser, "禁言"))) return;
|
|
225
|
+
const rest = text.replace(/^\/?禁言\s*/, "").trim();
|
|
226
|
+
const durationStr = rest.replace(/@\d+\s*/, "").trim();
|
|
227
|
+
const durationSec = parseDuration(durationStr) || 10 * 60;
|
|
228
|
+
try {
|
|
229
|
+
await bot.setGroupBan(groupId, atUser, durationSec);
|
|
230
|
+
await replyDone();
|
|
231
|
+
} catch (err) {
|
|
232
|
+
await replyAdminErrorNotice({
|
|
233
|
+
ctx,
|
|
234
|
+
event,
|
|
235
|
+
instruction: `禁言执行失败:${String(err)},请简要说明失败并建议稍后重试`,
|
|
236
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
237
|
+
error: err,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (
|
|
244
|
+
text.startsWith("解禁") ||
|
|
245
|
+
text.startsWith("/解禁") ||
|
|
246
|
+
text.startsWith("/解") ||
|
|
247
|
+
text.startsWith("解")
|
|
248
|
+
) {
|
|
249
|
+
if (!(await ensureAdminPermission())) return;
|
|
250
|
+
const atUser = getAtUserId(event.message);
|
|
251
|
+
if (!atUser) {
|
|
252
|
+
await replyAdminErrorNotice({
|
|
253
|
+
ctx,
|
|
254
|
+
event,
|
|
255
|
+
instruction:
|
|
256
|
+
"用户想让你解除一个人的禁言,请提醒要在命令后@要解禁的人",
|
|
257
|
+
fallbackMessage: "你想拉谁出来呀~",
|
|
258
|
+
});
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
await bot.setGroupBan(groupId, atUser, 0);
|
|
263
|
+
await replyDone();
|
|
264
|
+
} catch (err) {
|
|
265
|
+
await replyAdminErrorNotice({
|
|
266
|
+
ctx,
|
|
267
|
+
event,
|
|
268
|
+
instruction: `解禁执行失败:${String(err)},请简要说明失败`,
|
|
269
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
270
|
+
error: err,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (text.startsWith("设管理") || text.startsWith("/设管理")) {
|
|
277
|
+
if (!(await ensureAdminPermission())) return;
|
|
278
|
+
const atUser = getAtUserId(event.message);
|
|
279
|
+
if (!atUser) {
|
|
280
|
+
await replyAdminErrorNotice({
|
|
281
|
+
ctx,
|
|
282
|
+
event,
|
|
283
|
+
instruction:
|
|
284
|
+
"用户希望你把一个人设置成群管理员,但是没有目标,请提醒要在命令后@要设为管理的人",
|
|
285
|
+
fallbackMessage: "你想给谁管理呀~",
|
|
286
|
+
});
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
await bot.api("set_group_admin", {
|
|
291
|
+
group_id: groupId,
|
|
292
|
+
user_id: atUser,
|
|
293
|
+
enable: true,
|
|
294
|
+
});
|
|
295
|
+
await replyDone();
|
|
296
|
+
} catch (err) {
|
|
297
|
+
await replyAdminErrorNotice({
|
|
298
|
+
ctx,
|
|
299
|
+
event,
|
|
300
|
+
instruction: `设管理执行失败:${String(err)},请简要说明失败`,
|
|
301
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
302
|
+
error: err,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (text === "/全体禁言") {
|
|
309
|
+
if (!(await ensureAdminPermission())) return;
|
|
310
|
+
try {
|
|
311
|
+
await bot.api("set_group_whole_ban", {
|
|
312
|
+
group_id: groupId,
|
|
313
|
+
enable: true,
|
|
314
|
+
});
|
|
315
|
+
await replyDone();
|
|
316
|
+
} catch (err) {
|
|
317
|
+
await replyAdminErrorNotice({
|
|
318
|
+
ctx,
|
|
319
|
+
event,
|
|
320
|
+
instruction: `全体禁言执行失败:${String(err)},请简要说明失败`,
|
|
321
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
322
|
+
error: err,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (text === "/全体解禁") {
|
|
329
|
+
if (!(await ensureAdminPermission())) return;
|
|
330
|
+
try {
|
|
331
|
+
await bot.api("set_group_whole_ban", {
|
|
332
|
+
group_id: groupId,
|
|
333
|
+
enable: false,
|
|
334
|
+
});
|
|
335
|
+
await replyDone();
|
|
336
|
+
} catch (err) {
|
|
337
|
+
await replyAdminErrorNotice({
|
|
338
|
+
ctx,
|
|
339
|
+
event,
|
|
340
|
+
instruction: `全体解禁执行失败:${String(err)},请简要说明失败`,
|
|
341
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
342
|
+
error: err,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (text.startsWith("改群名片") || text.startsWith("/改群名片")) {
|
|
349
|
+
if (!isGroup || !groupId) {
|
|
350
|
+
return event.reply("这个要在群里用哦~", true);
|
|
351
|
+
}
|
|
352
|
+
if (!(await ensureAdminPermission())) return;
|
|
353
|
+
const card = text.replace(/^\/?改群名片\s*/, "").trim();
|
|
354
|
+
if (!card) {
|
|
355
|
+
await replyAdminErrorNotice({
|
|
356
|
+
ctx,
|
|
357
|
+
event,
|
|
358
|
+
instruction: "用户希望修改你在群里的昵称,但是没有指定名称。",
|
|
359
|
+
fallbackMessage: "名片内容呢~",
|
|
360
|
+
});
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
await bot.setGroupCard(groupId, selfId, card);
|
|
365
|
+
await event.reply("done");
|
|
366
|
+
} catch (err) {
|
|
367
|
+
await replyAdminErrorNotice({
|
|
368
|
+
ctx,
|
|
369
|
+
event,
|
|
370
|
+
instruction: `改群名片执行失败,${String(err)}`,
|
|
371
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
372
|
+
error: err,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (text.startsWith("/改群昵称")) {
|
|
379
|
+
if (!isGroup || !groupId) {
|
|
380
|
+
return event.reply("这个要在群里用哦~", true);
|
|
381
|
+
}
|
|
382
|
+
if (!(await ensureAdminPermission())) return;
|
|
383
|
+
const groupName = text.replace(/^\/改群昵称\s*/, "").trim();
|
|
384
|
+
if (!groupName) {
|
|
385
|
+
await replyAdminErrorNotice({
|
|
386
|
+
ctx,
|
|
387
|
+
event,
|
|
388
|
+
instruction:
|
|
389
|
+
"用户希望修改群聊的名称,但是没有告诉你新的名称是啥,请提醒输入群聊名称",
|
|
390
|
+
fallbackMessage: "群名想改成什么呀~",
|
|
391
|
+
});
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
await bot.api("set_group_name", {
|
|
396
|
+
group_id: groupId,
|
|
397
|
+
group_name: groupName,
|
|
398
|
+
});
|
|
399
|
+
await event.reply("done");
|
|
400
|
+
} catch (err) {
|
|
401
|
+
await replyAdminErrorNotice({
|
|
402
|
+
ctx,
|
|
403
|
+
event,
|
|
404
|
+
instruction: `改群昵称执行失败${String(err)}`,
|
|
405
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
406
|
+
error: err,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (text.startsWith("/改群头像")) {
|
|
413
|
+
if (!isGroup || !groupId) {
|
|
414
|
+
return event.reply("这个要在群里用哦~", true);
|
|
415
|
+
}
|
|
416
|
+
if (!(await ensureAdminPermission())) return;
|
|
417
|
+
const imageUrl = extractImageUrl(event.message);
|
|
418
|
+
if (!imageUrl) {
|
|
419
|
+
await replyAdminErrorNotice({
|
|
420
|
+
ctx,
|
|
421
|
+
event,
|
|
422
|
+
instruction:
|
|
423
|
+
"用户希望修改群聊的群头像,但是没有发你图片,请提醒附带图片或引用图片。",
|
|
424
|
+
fallbackMessage: "图片呢图片呢~",
|
|
425
|
+
});
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
await bot.api("set_group_portrait", {
|
|
430
|
+
group_id: groupId,
|
|
431
|
+
file: imageUrl,
|
|
432
|
+
});
|
|
433
|
+
await event.reply("done");
|
|
434
|
+
} catch (err) {
|
|
435
|
+
await replyAdminErrorNotice({
|
|
436
|
+
ctx,
|
|
437
|
+
event,
|
|
438
|
+
instruction: `改群头像执行失败 ${String(err)}`,
|
|
439
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
440
|
+
error: err,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
} catch (err) {
|
|
446
|
+
ctx.logger.error(`[admin group] 未捕获异常: ${String(err)}`);
|
|
447
|
+
if (!isGroupAdminCommand) return;
|
|
448
|
+
await replyAdminErrorNotice({
|
|
449
|
+
ctx,
|
|
450
|
+
event,
|
|
451
|
+
instruction: `群管理指令执行时发生未捕获异常:${String(err)},请简要说明失败并建议稍后重试`,
|
|
452
|
+
fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
|
|
453
|
+
error: err,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { AIService } from "../../../src/services/ai/types";
|
|
2
|
+
import type { MiokiContext } from "mioki";
|
|
3
|
+
|
|
4
|
+
function normalizeErrorMessage(error: unknown): string {
|
|
5
|
+
if (error instanceof Error && error.message) {
|
|
6
|
+
return error.message;
|
|
7
|
+
}
|
|
8
|
+
if (typeof error === "string") {
|
|
9
|
+
return error;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
return JSON.stringify(error);
|
|
13
|
+
} catch {
|
|
14
|
+
return String(error);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function replyAdminErrorNotice(options: {
|
|
19
|
+
ctx: MiokiContext;
|
|
20
|
+
event: any;
|
|
21
|
+
instruction: string;
|
|
22
|
+
fallbackMessage: string;
|
|
23
|
+
error?: unknown;
|
|
24
|
+
}): Promise<void> {
|
|
25
|
+
if (options.error != null) {
|
|
26
|
+
options.ctx.logger.error(
|
|
27
|
+
`[admin] ${options.instruction}\n执行错误: ${normalizeErrorMessage(options.error)}`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const aiService = options.ctx.services?.ai as AIService | undefined;
|
|
32
|
+
const chatRuntime = aiService?.getChatRuntime();
|
|
33
|
+
if (chatRuntime) {
|
|
34
|
+
try {
|
|
35
|
+
const instructionLines = [options.instruction];
|
|
36
|
+
if (options.error != null) {
|
|
37
|
+
instructionLines.push(
|
|
38
|
+
`执行错误: ${normalizeErrorMessage(options.error)}`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
await chatRuntime.generateNotice({
|
|
42
|
+
event: options.event,
|
|
43
|
+
instruction: instructionLines.join("\n"),
|
|
44
|
+
send: true,
|
|
45
|
+
promptInjections: [
|
|
46
|
+
{
|
|
47
|
+
title: "Admin Plugin Notice",
|
|
48
|
+
content:
|
|
49
|
+
"An admin-related action was triggered. Judge whether the user likely intended this action or triggered it accidentally. If it looks accidental or like a casual mention, weave a natural reply into the conversation without mentioning the plugin, tools, or commands. If the user seems to want this feature, respond helpfully.",
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
} catch (noticeError) {
|
|
55
|
+
options.ctx.logger.error(`admin error notice failed: ${noticeError}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await options.event.reply(options.fallbackMessage, true);
|
|
60
|
+
}
|