koishi-plugin-onebot-group-manage 0.0.2 → 0.0.3
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/lib/index.js +394 -131
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -32,187 +32,450 @@ var ROLE_QUERY_TIMEOUT = 1e4;
|
|
|
32
32
|
var roleCache = /* @__PURE__ */ new Map();
|
|
33
33
|
var actionSchema = import_koishi.Schema.object({
|
|
34
34
|
requireGroupAdmin: import_koishi.Schema.boolean().default(true).description("调用者必须是群管/群主"),
|
|
35
|
-
minAuthority: import_koishi.Schema.number().min(0).default(1).description("调用者最低 Koishi
|
|
35
|
+
minAuthority: import_koishi.Schema.number().min(0).default(1).description("调用者最低 Koishi 权限,为 0 表示不校验")
|
|
36
36
|
});
|
|
37
37
|
var Config = import_koishi.Schema.object({
|
|
38
38
|
mute: actionSchema.description("禁言配置"),
|
|
39
39
|
unmute: actionSchema.description("解禁配置"),
|
|
40
40
|
kick: actionSchema.description("踢人配置"),
|
|
41
|
-
title: actionSchema.description("
|
|
41
|
+
title: actionSchema.description("头衔配置(机器人需要群主权限)"),
|
|
42
42
|
selfMute: import_koishi.Schema.object({
|
|
43
|
-
minSeconds: import_koishi.Schema.number().min(1).default(60).description("
|
|
44
|
-
maxSeconds: import_koishi.Schema.number().min(1).default(600).description("
|
|
45
|
-
}).description("
|
|
43
|
+
minSeconds: import_koishi.Schema.number().min(1).default(60).description("“禁言我”随机最小秒数"),
|
|
44
|
+
maxSeconds: import_koishi.Schema.number().min(1).default(600).description("“禁言我”随机最大秒数")
|
|
45
|
+
}).description("自助禁言配置"),
|
|
46
46
|
debug: import_koishi.Schema.object({
|
|
47
|
-
verbose: import_koishi.Schema.boolean().default(false)
|
|
48
|
-
}).default({ verbose: false })
|
|
47
|
+
verbose: import_koishi.Schema.boolean().default(false).description("开启详细日志(仅建议排查问题时启用)")
|
|
48
|
+
}).default({ verbose: false }).description("调试选项")
|
|
49
49
|
});
|
|
50
|
-
function
|
|
50
|
+
function toCamel(action) {
|
|
51
|
+
return action.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
52
|
+
}
|
|
53
|
+
__name(toCamel, "toCamel");
|
|
54
|
+
function withTimeout(p, ms, label) {
|
|
51
55
|
return Promise.race([
|
|
52
56
|
p,
|
|
53
|
-
new Promise((_,
|
|
57
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`${label} timeout (${ms}ms)`)), ms))
|
|
54
58
|
]);
|
|
55
59
|
}
|
|
56
60
|
__name(withTimeout, "withTimeout");
|
|
57
61
|
function getBotOnebot(bot) {
|
|
58
|
-
|
|
62
|
+
const direct = bot?.onebot || bot?.internal?.onebot;
|
|
63
|
+
if (direct) return direct;
|
|
64
|
+
const internal = bot?.internal;
|
|
65
|
+
if (internal && (internal.setGroupBan || internal.set_group_ban || internal.callApi)) return internal;
|
|
66
|
+
return null;
|
|
59
67
|
}
|
|
60
68
|
__name(getBotOnebot, "getBotOnebot");
|
|
61
|
-
function
|
|
62
|
-
|
|
69
|
+
async function callOneBotApi(bot, action, params, onebotOverride) {
|
|
70
|
+
const gid = params?.group_id ?? params?.groupId ?? params?.group?.id;
|
|
71
|
+
const uid = params?.user_id ?? params?.userId ?? params?.user?.id;
|
|
72
|
+
const payload = {
|
|
73
|
+
...params,
|
|
74
|
+
group_id: gid != null ? Number(gid) : gid,
|
|
75
|
+
user_id: uid != null ? Number(uid) : uid
|
|
76
|
+
};
|
|
77
|
+
const ob = onebotOverride || getBotOnebot(bot);
|
|
78
|
+
if (ob) {
|
|
79
|
+
switch (action) {
|
|
80
|
+
case "get_group_member_info": {
|
|
81
|
+
if (typeof ob.getGroupMemberInfo === "function") {
|
|
82
|
+
return ob.getGroupMemberInfo(Number(payload.group_id), Number(payload.user_id), Boolean(payload.no_cache));
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case "set_group_ban": {
|
|
87
|
+
if (typeof ob.callApi === "function") return ob.callApi("set_group_ban", payload);
|
|
88
|
+
if (typeof ob.setGroupBan === "function") {
|
|
89
|
+
return ob.setGroupBan(Number(payload.group_id), Number(payload.user_id), Number(payload.duration ?? 0));
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case "set_group_kick": {
|
|
94
|
+
if (typeof ob.callApi === "function") return ob.callApi("set_group_kick", payload);
|
|
95
|
+
if (typeof ob.setGroupKick === "function") {
|
|
96
|
+
return ob.setGroupKick(Number(payload.group_id), Number(payload.user_id), Boolean(payload.reject_add_request));
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
case "set_group_special_title": {
|
|
101
|
+
if (typeof ob.callApi === "function") return ob.callApi("set_group_special_title", payload);
|
|
102
|
+
if (typeof ob.setGroupSpecialTitle === "function") {
|
|
103
|
+
return ob.setGroupSpecialTitle(
|
|
104
|
+
Number(payload.group_id),
|
|
105
|
+
Number(payload.user_id),
|
|
106
|
+
payload.special_title ?? "",
|
|
107
|
+
Number(payload.duration ?? -1)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
default: {
|
|
113
|
+
if (typeof ob[action] === "function") return ob[action](payload);
|
|
114
|
+
const camel = toCamel(action);
|
|
115
|
+
if (typeof ob[camel] === "function") return ob[camel](payload);
|
|
116
|
+
if (typeof ob.callApi === "function") return ob.callApi(action, payload);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
throw new Error("onebot instance not available on bot");
|
|
63
121
|
}
|
|
64
|
-
__name(
|
|
65
|
-
|
|
66
|
-
|
|
122
|
+
__name(callOneBotApi, "callOneBotApi");
|
|
123
|
+
function botKey(bot) {
|
|
124
|
+
return bot?.sid || `${bot?.platform || "onebot"}:${bot?.selfId || ""}`;
|
|
125
|
+
}
|
|
126
|
+
__name(botKey, "botKey");
|
|
127
|
+
async function getMemberRole(probeOnebot, targetBot, guildId, userId, logger, verbose) {
|
|
128
|
+
const key = `${botKey(targetBot)}::${guildId}::${userId}`;
|
|
67
129
|
const cached = roleCache.get(key);
|
|
68
130
|
if (cached && cached.expire > Date.now()) return cached.role;
|
|
69
131
|
try {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
132
|
+
const direct = probeOnebot?.getGroupMemberInfo;
|
|
133
|
+
let info = null;
|
|
134
|
+
if (typeof direct === "function") {
|
|
135
|
+
info = await withTimeout(
|
|
136
|
+
direct.call(probeOnebot, Number(guildId), Number(userId), false),
|
|
137
|
+
ROLE_QUERY_TIMEOUT,
|
|
138
|
+
"get_group_member_info"
|
|
139
|
+
);
|
|
140
|
+
} else {
|
|
141
|
+
info = await withTimeout(
|
|
142
|
+
callOneBotApi(targetBot, "get_group_member_info", {
|
|
143
|
+
group_id: Number(guildId),
|
|
144
|
+
user_id: Number(userId),
|
|
145
|
+
no_cache: false
|
|
146
|
+
}, probeOnebot),
|
|
147
|
+
ROLE_QUERY_TIMEOUT,
|
|
148
|
+
"get_group_member_info"
|
|
149
|
+
);
|
|
150
|
+
}
|
|
74
151
|
const role = info?.role ?? null;
|
|
75
|
-
if (role)
|
|
152
|
+
if (role) {
|
|
153
|
+
roleCache.set(key, { role, expire: Date.now() + ROLE_CACHE_TTL });
|
|
154
|
+
logger?.[verbose ? "info" : "debug"](
|
|
155
|
+
`get_group_member_info ok bot=${botKey(targetBot)} guild=${guildId} user=${userId} role=${role}`
|
|
156
|
+
);
|
|
157
|
+
} else {
|
|
158
|
+
logger?.[verbose ? "warn" : "debug"](
|
|
159
|
+
`get_group_member_info empty bot=${botKey(targetBot)} guild=${guildId} user=${userId}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
76
162
|
return role;
|
|
77
|
-
} catch {
|
|
163
|
+
} catch (error) {
|
|
164
|
+
logger?.[verbose ? "warn" : "debug"](error, `get_group_member_info failed bot=${botKey(targetBot)} guild=${guildId} user=${userId}`);
|
|
78
165
|
return null;
|
|
79
166
|
}
|
|
80
167
|
}
|
|
81
168
|
__name(getMemberRole, "getMemberRole");
|
|
82
|
-
async function verifyCaller(session, rule) {
|
|
169
|
+
async function verifyCaller(session, rule, logger, verbose) {
|
|
83
170
|
const authority = Number(session.user?.authority ?? 0);
|
|
84
171
|
if (!rule.requireGroupAdmin && rule.minAuthority <= 0) return null;
|
|
85
172
|
if (rule.minAuthority > 0 && authority >= rule.minAuthority) return null;
|
|
86
173
|
if (rule.requireGroupAdmin && session.guildId) {
|
|
87
174
|
const probe = session.onebot || getBotOnebot(session.bot);
|
|
88
|
-
const role = await getMemberRole(
|
|
89
|
-
probe,
|
|
90
|
-
session.bot,
|
|
91
|
-
session.guildId,
|
|
92
|
-
session.userId
|
|
93
|
-
);
|
|
175
|
+
const role = await getMemberRole(probe, session.bot, session.guildId, session.userId, logger, verbose);
|
|
94
176
|
if (role === "owner" || role === "admin") return null;
|
|
95
177
|
}
|
|
96
178
|
if (rule.requireGroupAdmin && rule.minAuthority > 0) {
|
|
97
|
-
return
|
|
179
|
+
return `需要群管理/群主或至少 ${rule.minAuthority} 级 Koishi 权限`;
|
|
98
180
|
}
|
|
99
|
-
if (rule.requireGroupAdmin) return "
|
|
100
|
-
return
|
|
181
|
+
if (rule.requireGroupAdmin) return "需要群管理或群主权限";
|
|
182
|
+
return `需要至少 ${rule.minAuthority} 级 Koishi 权限`;
|
|
101
183
|
}
|
|
102
184
|
__name(verifyCaller, "verifyCaller");
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
185
|
+
async function pickManageBot(ctx, guildId, requireOwner, logger, preferBot, verbose, probeOnebot) {
|
|
186
|
+
const candidates = ctx.bots.filter((b) => b.platform === "onebot");
|
|
187
|
+
const probeBot = probeOnebot || preferBot && preferBot.onebot || candidates.find((b) => b.onebot) || candidates.find((b) => b.internal?.onebot);
|
|
188
|
+
const probeInstance = probeBot?.onebot || probeBot?.internal?.onebot || probeOnebot;
|
|
189
|
+
if (!probeInstance) {
|
|
190
|
+
logger.warn(`no probe bot with onebot instance for guild=${guildId}`);
|
|
191
|
+
return null;
|
|
110
192
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
193
|
+
const seen = /* @__PURE__ */ new Set();
|
|
194
|
+
const ordered = [];
|
|
195
|
+
if (preferBot && preferBot.platform === "onebot") {
|
|
196
|
+
ordered.push(preferBot);
|
|
197
|
+
seen.add(botKey(preferBot));
|
|
198
|
+
}
|
|
199
|
+
for (const bot of candidates) {
|
|
200
|
+
const k = botKey(bot);
|
|
201
|
+
if (seen.has(k)) continue;
|
|
202
|
+
ordered.push(bot);
|
|
203
|
+
seen.add(k);
|
|
204
|
+
}
|
|
205
|
+
const roles = await Promise.all(
|
|
206
|
+
ordered.map(async (bot) => {
|
|
207
|
+
const role = await getMemberRole(probeInstance, bot, guildId, bot.selfId, logger, verbose);
|
|
208
|
+
return { bot, role };
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
for (const { bot, role } of roles) {
|
|
212
|
+
if (!role) continue;
|
|
213
|
+
const ok = requireOwner ? role === "owner" : role === "owner" || role === "admin";
|
|
214
|
+
if (ok) {
|
|
215
|
+
logger[verbose ? "info" : "debug"](`picked bot=${botKey(bot)} role=${role} guild=${guildId}`);
|
|
216
|
+
return { bot, role, probeOnebot: probeInstance };
|
|
122
217
|
}
|
|
123
218
|
}
|
|
219
|
+
logger.warn(`no available bot with manage permission in guild=${guildId}`);
|
|
124
220
|
return null;
|
|
125
221
|
}
|
|
126
222
|
__name(pickManageBot, "pickManageBot");
|
|
223
|
+
function parseTargetId(raw, session) {
|
|
224
|
+
if (raw) {
|
|
225
|
+
const segments = import_koishi.segment.parse(raw);
|
|
226
|
+
const at = segments.find((seg) => seg.type === "at" && seg.attrs?.id);
|
|
227
|
+
const atId = at?.attrs?.id;
|
|
228
|
+
if (atId) return String(atId);
|
|
229
|
+
const match = raw.match(/\d{5,}/);
|
|
230
|
+
if (match) return match[0];
|
|
231
|
+
}
|
|
232
|
+
if (session.quote?.user?.id) return String(session.quote.user.id);
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
__name(parseTargetId, "parseTargetId");
|
|
236
|
+
function formatRule(rule) {
|
|
237
|
+
const needAdmin = rule.requireGroupAdmin;
|
|
238
|
+
const needAuth = rule.minAuthority > 0;
|
|
239
|
+
if (needAdmin && needAuth) return `群管/群主 或 Koishi≥${rule.minAuthority}`;
|
|
240
|
+
if (needAdmin) return "群管/群主";
|
|
241
|
+
if (needAuth) return `Koishi≥${rule.minAuthority}`;
|
|
242
|
+
return "无限制";
|
|
243
|
+
}
|
|
244
|
+
__name(formatRule, "formatRule");
|
|
245
|
+
function randomInt(min, max) {
|
|
246
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
247
|
+
}
|
|
248
|
+
__name(randomInt, "randomInt");
|
|
127
249
|
function apply(ctx, config) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (
|
|
133
|
-
const
|
|
134
|
-
if (!
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
250
|
+
const logger = ctx.logger(name);
|
|
251
|
+
const verbose = config.debug?.verbose ?? false;
|
|
252
|
+
ctx.command("group.manage", "OneBot 群管", { authority: 0 }).channelFields(["platform"]);
|
|
253
|
+
ctx.command("group.manage/check <target:string>", "调试:查询 get_group_member_info").alias("gcheck").action(async ({ session }, target) => {
|
|
254
|
+
if (!session?.guildId) return "仅限群聊使用";
|
|
255
|
+
const userId = parseTargetId(target, session);
|
|
256
|
+
if (!userId) return "请提供要查询的用户(@ 或 QQ 号)";
|
|
257
|
+
try {
|
|
258
|
+
const start = Date.now();
|
|
259
|
+
const probe = session.onebot || getBotOnebot(session.bot);
|
|
260
|
+
const info = await callOneBotApi(
|
|
261
|
+
session.bot,
|
|
262
|
+
"get_group_member_info",
|
|
263
|
+
{
|
|
264
|
+
group_id: Number(session.guildId),
|
|
265
|
+
user_id: Number(userId),
|
|
266
|
+
no_cache: false
|
|
267
|
+
},
|
|
268
|
+
probe
|
|
269
|
+
);
|
|
270
|
+
const cost = Date.now() - start;
|
|
271
|
+
const role = info?.role ?? "unknown";
|
|
272
|
+
return `查询成功,用时 ${cost}ms,role=${role},raw=${JSON.stringify(info)}`;
|
|
273
|
+
} catch (error) {
|
|
274
|
+
return `查询失败: ${error?.message || String(error)}`;
|
|
275
|
+
}
|
|
142
276
|
});
|
|
143
|
-
ctx.command("group.manage/
|
|
144
|
-
if (!session?.guildId) return "
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
277
|
+
ctx.command("group.manage/status", "查看本群 Bot 权限状态").alias("gstatus").action(async ({ session }) => {
|
|
278
|
+
if (!session?.guildId) return "仅限群聊使用";
|
|
279
|
+
const probe = session.onebot || getBotOnebot(session.bot);
|
|
280
|
+
if (!probe) return "当前 Bot 无 onebot 实例,无法查询";
|
|
281
|
+
const bots = ctx.bots.filter((b) => b.platform === "onebot");
|
|
282
|
+
const lines = [];
|
|
283
|
+
for (const b of bots) {
|
|
284
|
+
const role = await getMemberRole(probe, b, session.guildId, b.selfId, logger, verbose);
|
|
285
|
+
lines.push(`${b.selfId}: ${role ?? "unknown"}`);
|
|
286
|
+
}
|
|
287
|
+
lines.push(`配置:mute=${formatRule(config.mute)} / unmute=${formatRule(config.unmute)} / kick=${formatRule(config.kick)} / title=${formatRule(config.title)}`);
|
|
288
|
+
lines.push(`自助:gmute.me 随机范围 ${config.selfMute.minSeconds}~${config.selfMute.maxSeconds} 秒`);
|
|
289
|
+
return lines.join("\n");
|
|
154
290
|
});
|
|
155
|
-
ctx.command("group.manage/
|
|
156
|
-
if (!session?.guildId) return "
|
|
157
|
-
const
|
|
158
|
-
if (
|
|
159
|
-
const
|
|
160
|
-
if (!
|
|
161
|
-
const
|
|
162
|
-
if (!
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return
|
|
291
|
+
ctx.command("group.manage/mute <target:string> <duration:number>", "禁言目标(秒,必须指定)").alias("gmute").action(async ({ session }, target, duration) => {
|
|
292
|
+
if (!session?.guildId) return "仅限群聊使用";
|
|
293
|
+
const msg = await verifyCaller(session, config.mute, logger, verbose);
|
|
294
|
+
if (msg) return msg;
|
|
295
|
+
const userId = parseTargetId(target, session);
|
|
296
|
+
if (!userId) return "请提供要禁言的用户(@ 或 QQ 号)";
|
|
297
|
+
const seconds = Number(duration);
|
|
298
|
+
if (!Number.isFinite(seconds) || seconds <= 0) return "禁言时长必须为大于 0 的整数(秒)";
|
|
299
|
+
verbose && logger.info(`mute: guild=${session.guildId} caller=${session.userId} bot=${botKey(session.bot)} target=${userId} seconds=${seconds}`);
|
|
300
|
+
const picked = await pickManageBot(ctx, session.guildId, false, logger, session.bot, verbose, session.onebot);
|
|
301
|
+
if (!picked) return "没有可用且有群管权限的 Bot";
|
|
302
|
+
const targetRole = await getMemberRole(picked.probeOnebot, picked.bot, session.guildId, userId, logger, verbose);
|
|
303
|
+
if (targetRole === "owner") return "无法对群主执行该操作";
|
|
304
|
+
const actionOnebot = getBotOnebot(picked.bot);
|
|
305
|
+
if (!actionOnebot) return "选中的 Bot 缺少 onebot 实例,无法执行";
|
|
306
|
+
try {
|
|
307
|
+
verbose && logger.info(`mute: use bot=${botKey(picked.bot)} role=${picked.role} duration=${seconds}`);
|
|
308
|
+
if (typeof actionOnebot.setGroupBan === "function") {
|
|
309
|
+
await actionOnebot.setGroupBan(Number(session.guildId), Number(userId), Number(seconds));
|
|
310
|
+
} else {
|
|
311
|
+
await callOneBotApi(picked.bot, "set_group_ban", {
|
|
312
|
+
group_id: Number(session.guildId),
|
|
313
|
+
user_id: Number(userId),
|
|
314
|
+
duration: Number(seconds)
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
roleCache.clear();
|
|
318
|
+
return `已使用 ${picked.bot.selfId} 禁言 ${userId} ${seconds} 秒`;
|
|
319
|
+
} catch (error) {
|
|
320
|
+
logger.warn(error);
|
|
321
|
+
return "禁言失败,检查 Bot 权限或参数";
|
|
322
|
+
}
|
|
168
323
|
});
|
|
169
|
-
ctx.command("group.manage/
|
|
170
|
-
if (!session?.guildId) return "
|
|
171
|
-
const
|
|
172
|
-
if (
|
|
173
|
-
const
|
|
174
|
-
if (!
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
324
|
+
ctx.command("group.manage/unmute <target:string>", "解除禁言").alias("gunmute").action(async ({ session }, target) => {
|
|
325
|
+
if (!session?.guildId) return "仅限群聊使用";
|
|
326
|
+
const msg = await verifyCaller(session, config.unmute, logger, verbose);
|
|
327
|
+
if (msg) return msg;
|
|
328
|
+
const userId = parseTargetId(target, session);
|
|
329
|
+
if (!userId) return "请提供要解禁的用户(@ 或 QQ 号)";
|
|
330
|
+
verbose && logger.info(`unmute: guild=${session.guildId} caller=${session.userId} bot=${botKey(session.bot)} target=${userId}`);
|
|
331
|
+
const picked = await pickManageBot(ctx, session.guildId, false, logger, session.bot, verbose, session.onebot);
|
|
332
|
+
if (!picked) return "没有可用且有群管权限的 Bot";
|
|
333
|
+
const targetRole = await getMemberRole(picked.probeOnebot, picked.bot, session.guildId, userId, logger, verbose);
|
|
334
|
+
if (targetRole === "owner") return "无法对群主执行该操作";
|
|
335
|
+
const actionOnebot = getBotOnebot(picked.bot);
|
|
336
|
+
if (!actionOnebot) return "选中的 Bot 缺少 onebot 实例,无法执行";
|
|
337
|
+
try {
|
|
338
|
+
verbose && logger.info(`unmute: use bot=${botKey(picked.bot)} role=${picked.role}`);
|
|
339
|
+
if (typeof actionOnebot.setGroupBan === "function") {
|
|
340
|
+
await actionOnebot.setGroupBan(Number(session.guildId), Number(userId), 0);
|
|
341
|
+
} else {
|
|
342
|
+
await callOneBotApi(picked.bot, "set_group_ban", {
|
|
343
|
+
group_id: Number(session.guildId),
|
|
344
|
+
user_id: Number(userId),
|
|
345
|
+
duration: 0
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
roleCache.clear();
|
|
349
|
+
return `已使用 ${picked.bot.selfId} 解除 ${userId} 的禁言`;
|
|
350
|
+
} catch (error) {
|
|
351
|
+
logger.warn(error);
|
|
352
|
+
return "解禁失败,检查 Bot 权限或参数";
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
ctx.command("group.manage/kick <target:string> [reject:boolean]", "踢出成员").alias("gkick").option("reject", "-r 设为 true 拒绝再次申请").action(async ({ session, options }, target, rejectFlag) => {
|
|
356
|
+
if (!session?.guildId) return "仅限群聊使用";
|
|
357
|
+
const msg = await verifyCaller(session, config.kick, logger, verbose);
|
|
358
|
+
if (msg) return msg;
|
|
359
|
+
const userId = parseTargetId(target, session);
|
|
360
|
+
if (!userId) return "请提供要踢出的用户(@ 或 QQ 号)";
|
|
361
|
+
const reject = Boolean(options?.reject ?? rejectFlag);
|
|
362
|
+
verbose && logger.info(`kick: guild=${session.guildId} caller=${session.userId} bot=${botKey(session.bot)} target=${userId} reject=${reject}`);
|
|
363
|
+
const picked = await pickManageBot(ctx, session.guildId, false, logger, session.bot, verbose, session.onebot);
|
|
364
|
+
if (!picked) return "没有可用且有群管权限的 Bot";
|
|
365
|
+
const targetRole = await getMemberRole(picked.probeOnebot, picked.bot, session.guildId, userId, logger, verbose);
|
|
366
|
+
if (targetRole === "owner") return "无法对群主执行该操作";
|
|
367
|
+
const actionOnebot = getBotOnebot(picked.bot);
|
|
368
|
+
if (!actionOnebot) return "选中的 Bot 缺少 onebot 实例,无法执行";
|
|
369
|
+
try {
|
|
370
|
+
verbose && logger.info(`kick: use bot=${botKey(picked.bot)} role=${picked.role}`);
|
|
371
|
+
if (typeof actionOnebot.setGroupKick === "function") {
|
|
372
|
+
await actionOnebot.setGroupKick(Number(session.guildId), Number(userId), reject);
|
|
373
|
+
} else {
|
|
374
|
+
await callOneBotApi(picked.bot, "set_group_kick", {
|
|
375
|
+
group_id: Number(session.guildId),
|
|
376
|
+
user_id: Number(userId),
|
|
377
|
+
reject_add_request: reject
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
roleCache.clear();
|
|
381
|
+
return `已使用 ${picked.bot.selfId} 踢出 ${userId}${reject ? "(已拒绝再次申请)" : ""}`;
|
|
382
|
+
} catch (error) {
|
|
383
|
+
logger.warn(error);
|
|
384
|
+
return "踢人失败,检查 Bot 权限或参数";
|
|
385
|
+
}
|
|
185
386
|
});
|
|
186
|
-
ctx.command("group.manage/
|
|
187
|
-
if (!session?.guildId) return "
|
|
188
|
-
const
|
|
189
|
-
if (
|
|
190
|
-
|
|
387
|
+
ctx.command("group.manage/title <target:string> <title:text>", "设置群头衔(需群主 Bot)").alias("gtitle").action(async ({ session }, target, title) => {
|
|
388
|
+
if (!session?.guildId) return "仅限群聊使用";
|
|
389
|
+
const msg = await verifyCaller(session, config.title, logger, verbose);
|
|
390
|
+
if (msg) return msg;
|
|
391
|
+
if (!title?.trim()) return "请提供要设置的头衔";
|
|
392
|
+
const userId = parseTargetId(target, session);
|
|
393
|
+
if (!userId) return "请提供要设置头衔的用户(@ 或 QQ 号)";
|
|
394
|
+
verbose && logger.info(`title: guild=${session.guildId} caller=${session.userId} bot=${botKey(session.bot)} target=${userId} title=${title.trim()}`);
|
|
395
|
+
const picked = await pickManageBot(ctx, session.guildId, true, logger, session.bot, verbose, session.onebot);
|
|
396
|
+
if (!picked) return "没有具备群主权限的 Bot 可用";
|
|
397
|
+
const actionOnebot = getBotOnebot(picked.bot);
|
|
398
|
+
if (!actionOnebot) return "选中的 Bot 缺少 onebot 实例,无法执行";
|
|
399
|
+
try {
|
|
400
|
+
verbose && logger.info(`title: use bot=${botKey(picked.bot)} role=${picked.role}`);
|
|
401
|
+
if (typeof actionOnebot.setGroupSpecialTitle === "function") {
|
|
402
|
+
await actionOnebot.setGroupSpecialTitle(Number(session.guildId), Number(userId), title.trim(), -1);
|
|
403
|
+
} else {
|
|
404
|
+
await callOneBotApi(picked.bot, "set_group_special_title", {
|
|
405
|
+
group_id: Number(session.guildId),
|
|
406
|
+
user_id: Number(userId),
|
|
407
|
+
special_title: title.trim(),
|
|
408
|
+
duration: -1
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
roleCache.clear();
|
|
412
|
+
return `已使用 ${picked.bot.selfId} 为 ${userId} 设置头衔:${title.trim()}`;
|
|
413
|
+
} catch (error) {
|
|
414
|
+
logger.warn(error);
|
|
415
|
+
return "设置头衔失败,检查 Bot 权限或参数";
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
ctx.command("group.manage/mute.me", "禁言我(随机时长)").alias("gmute.me").action(async ({ session }) => {
|
|
419
|
+
if (!session?.guildId) return "仅限群聊使用";
|
|
420
|
+
const msg = await verifyCaller(session, config.mute, logger, verbose);
|
|
421
|
+
if (msg) return msg;
|
|
422
|
+
const minS = Math.max(1, Math.floor(Number(config.selfMute?.minSeconds ?? 60)));
|
|
423
|
+
const maxS = Math.max(1, Math.floor(Number(config.selfMute?.maxSeconds ?? 600)));
|
|
424
|
+
const minSeconds = Math.min(minS, maxS);
|
|
425
|
+
const maxSeconds = Math.max(minS, maxS);
|
|
191
426
|
const seconds = randomInt(minSeconds, maxSeconds);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
|
|
427
|
+
verbose && logger.info(`mute.me: guild=${session.guildId} caller=${session.userId} seconds=${seconds}`);
|
|
428
|
+
const picked = await pickManageBot(ctx, session.guildId, false, logger, session.bot, verbose, session.onebot);
|
|
429
|
+
if (!picked) return "没有可用且有群管权限的 Bot";
|
|
430
|
+
const myRole = await getMemberRole(picked.probeOnebot, picked.bot, session.guildId, session.userId, logger, verbose);
|
|
431
|
+
if (myRole === "owner") return "群主不能禁言自己";
|
|
432
|
+
const actionOnebot = getBotOnebot(picked.bot);
|
|
433
|
+
if (!actionOnebot) return "选中的 Bot 缺少 onebot 实例,无法执行";
|
|
434
|
+
try {
|
|
435
|
+
if (typeof actionOnebot.setGroupBan === "function") {
|
|
436
|
+
await actionOnebot.setGroupBan(Number(session.guildId), Number(session.userId), Number(seconds));
|
|
437
|
+
} else {
|
|
438
|
+
await callOneBotApi(picked.bot, "set_group_ban", {
|
|
439
|
+
group_id: Number(session.guildId),
|
|
440
|
+
user_id: Number(session.userId),
|
|
441
|
+
duration: Number(seconds)
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
roleCache.clear();
|
|
445
|
+
return `你已被禁言 ${seconds} 秒`;
|
|
446
|
+
} catch (error) {
|
|
447
|
+
logger.warn(error);
|
|
448
|
+
return "禁言失败,检查 Bot 权限或参数";
|
|
449
|
+
}
|
|
201
450
|
});
|
|
202
|
-
ctx.command("group.manage/title.apply <title:text>", "
|
|
203
|
-
if (!session?.guildId) return "
|
|
204
|
-
const
|
|
205
|
-
if (
|
|
206
|
-
const
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
451
|
+
ctx.command("group.manage/title.apply <title:text>", "申请群头衔(给自己)").alias("gtitle.apply").action(async ({ session }, title) => {
|
|
452
|
+
if (!session?.guildId) return "仅限群聊使用";
|
|
453
|
+
const t = title?.trim();
|
|
454
|
+
if (!t) return "请提供要申请的头衔";
|
|
455
|
+
const msg = await verifyCaller(session, config.title, logger, verbose);
|
|
456
|
+
if (msg) return msg;
|
|
457
|
+
verbose && logger.info(`title.apply: guild=${session.guildId} caller=${session.userId} title=${t}`);
|
|
458
|
+
const picked = await pickManageBot(ctx, session.guildId, true, logger, session.bot, verbose, session.onebot);
|
|
459
|
+
if (!picked) return "没有具备群主权限的 Bot 可用";
|
|
460
|
+
const actionOnebot = getBotOnebot(picked.bot);
|
|
461
|
+
if (!actionOnebot) return "选中的 Bot 缺少 onebot 实例,无法执行";
|
|
462
|
+
try {
|
|
463
|
+
if (typeof actionOnebot.setGroupSpecialTitle === "function") {
|
|
464
|
+
await actionOnebot.setGroupSpecialTitle(Number(session.guildId), Number(session.userId), t, -1);
|
|
465
|
+
} else {
|
|
466
|
+
await callOneBotApi(picked.bot, "set_group_special_title", {
|
|
467
|
+
group_id: Number(session.guildId),
|
|
468
|
+
user_id: Number(session.userId),
|
|
469
|
+
special_title: t,
|
|
470
|
+
duration: -1
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
roleCache.clear();
|
|
474
|
+
return `已为你设置头衔:${t}`;
|
|
475
|
+
} catch (error) {
|
|
476
|
+
logger.warn(error);
|
|
477
|
+
return "设置头衔失败,检查 Bot 权限或参数";
|
|
478
|
+
}
|
|
216
479
|
});
|
|
217
480
|
}
|
|
218
481
|
__name(apply, "apply");
|