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.
Files changed (2) hide show
  1. package/lib/index.js +394 -131
  2. 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 权限,0 表示不校验")
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 withTimeout(p, ms) {
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((_, r) => setTimeout(() => r(new Error(`timeout ${ms}ms`)), ms))
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
- return bot?.onebot || bot?.internal?.onebot || bot?.internal || null;
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 randomInt(min, max) {
62
- return Math.floor(Math.random() * (max - min + 1)) + min;
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(randomInt, "randomInt");
65
- async function getMemberRole(probe, bot, guildId, userId, logger, verbose) {
66
- const key = `${bot.selfId}:${guildId}:${userId}`;
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 info = await withTimeout(
71
- probe.getGroupMemberInfo(Number(guildId), Number(userId), false),
72
- ROLE_QUERY_TIMEOUT
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) roleCache.set(key, { role, expire: Date.now() + ROLE_CACHE_TTL });
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 `需要群管/群主或 Koishi≥${rule.minAuthority}`;
179
+ return `需要群管理/群主或至少 ${rule.minAuthority} 级 Koishi 权限`;
98
180
  }
99
- if (rule.requireGroupAdmin) return "需要群管或群主权限";
100
- return `需要 Koishi≥${rule.minAuthority}`;
181
+ if (rule.requireGroupAdmin) return "需要群管理或群主权限";
182
+ return `需要至少 ${rule.minAuthority} 级 Koishi 权限`;
101
183
  }
102
184
  __name(verifyCaller, "verifyCaller");
103
- function parseTargetId(raw, session) {
104
- if (raw) {
105
- const segs = import_koishi.segment.parse(raw);
106
- const at = segs.find((s) => s.type === "at" && s.attrs?.id);
107
- if (at) return String(at.attrs.id);
108
- const m = raw.match(/\d{5,}/);
109
- if (m) return m[0];
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
- return session.quote?.user?.id || null;
112
- }
113
- __name(parseTargetId, "parseTargetId");
114
- async function pickManageBot(ctx, guildId, requireOwner, preferBot) {
115
- const bots = ctx.bots.filter((b) => b.platform === "onebot");
116
- const probe = getBotOnebot(preferBot || bots[0]);
117
- if (!probe) return null;
118
- for (const bot of bots) {
119
- const role = await getMemberRole(probe, bot, guildId, bot.selfId);
120
- if (requireOwner ? role === "owner" : role === "owner" || role === "admin") {
121
- return { bot, probe };
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
- ctx.command("group.manage", "OneBot 群管理").channelFields(["platform"]);
129
- ctx.command("group.manage/mute <target:string> <duration:number>", "禁言").alias("gmute").action(async ({ session }, target, duration) => {
130
- if (!session?.guildId) return "仅限群聊";
131
- const deny = await verifyCaller(session, config.mute);
132
- if (deny) return deny;
133
- const uid = parseTargetId(target, session);
134
- if (!uid || duration <= 0) return "参数错误";
135
- const picked = await pickManageBot(ctx, session.guildId, false, session.bot);
136
- if (!picked) return "无可用群管 Bot";
137
- const role = await getMemberRole(picked.probe, picked.bot, session.guildId, uid);
138
- if (role === "owner") return "不能禁言群主";
139
- await picked.probe.setGroupBan(Number(session.guildId), Number(uid), duration);
140
- roleCache.clear();
141
- return `已禁言 ${uid} ${duration} 秒`;
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/unmute <target:string>", "解禁").alias("gunmute").action(async ({ session }, target) => {
144
- if (!session?.guildId) return "仅限群聊";
145
- const deny = await verifyCaller(session, config.unmute);
146
- if (deny) return deny;
147
- const uid = parseTargetId(target, session);
148
- if (!uid) return "参数错误";
149
- const picked = await pickManageBot(ctx, session.guildId, false, session.bot);
150
- if (!picked) return "无可用群管 Bot";
151
- await picked.probe.setGroupBan(Number(session.guildId), Number(uid), 0);
152
- roleCache.clear();
153
- return `已解禁 ${uid}`;
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/kick <target:string>", "踢人").alias("gkick").action(async ({ session }, target) => {
156
- if (!session?.guildId) return "仅限群聊";
157
- const deny = await verifyCaller(session, config.kick);
158
- if (deny) return deny;
159
- const uid = parseTargetId(target, session);
160
- if (!uid) return "参数错误";
161
- const picked = await pickManageBot(ctx, session.guildId, false, session.bot);
162
- if (!picked) return "无可用群管 Bot";
163
- const role = await getMemberRole(picked.probe, picked.bot, session.guildId, uid);
164
- if (role === "owner") return "不能踢群主";
165
- await picked.probe.setGroupKick(Number(session.guildId), Number(uid), false);
166
- roleCache.clear();
167
- return `已踢出 ${uid}`;
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/title <target:string> <title:text>", "设置头衔").alias("gtitle").action(async ({ session }, target, title) => {
170
- if (!session?.guildId) return "仅限群聊";
171
- const deny = await verifyCaller(session, config.title);
172
- if (deny) return deny;
173
- const uid = parseTargetId(target, session);
174
- if (!uid) return "参数错误";
175
- const picked = await pickManageBot(ctx, session.guildId, true, session.bot);
176
- if (!picked) return "无群主 Bot";
177
- await picked.probe.setGroupSpecialTitle(
178
- Number(session.guildId),
179
- Number(uid),
180
- title.trim(),
181
- -1
182
- );
183
- roleCache.clear();
184
- return `已设置头衔`;
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/mute.me", "禁言我").alias("gmute.me").action(async ({ session }) => {
187
- if (!session?.guildId) return "仅限群聊";
188
- const deny = await verifyCaller(session, config.mute);
189
- if (deny) return deny;
190
- const { minSeconds, maxSeconds } = config.selfMute;
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
- const picked = await pickManageBot(ctx, session.guildId, false, session.bot);
193
- if (!picked) return "无可用群管 Bot";
194
- await picked.probe.setGroupBan(
195
- Number(session.guildId),
196
- Number(session.userId),
197
- seconds
198
- );
199
- roleCache.clear();
200
- return `你被禁言 ${seconds} 秒`;
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>", "申请群头衔").alias("gtitle.apply").action(async ({ session }, title) => {
203
- if (!session?.guildId) return "仅限群聊";
204
- const deny = await verifyCaller(session, config.title);
205
- if (deny) return deny;
206
- const picked = await pickManageBot(ctx, session.guildId, true, session.bot);
207
- if (!picked) return "无群主 Bot";
208
- await picked.probe.setGroupSpecialTitle(
209
- Number(session.guildId),
210
- Number(session.userId),
211
- title.trim(),
212
- -1
213
- );
214
- roleCache.clear();
215
- return `你的头衔已设置为:${title}`;
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");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-onebot-group-manage",
3
3
  "description": "主要针对多bot环境的群管插件,自动使用有权限的bot",
4
- "version": "0.0.2",
4
+ "version": "0.0.3",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [