koishi-plugin-onebot-group-manage 0.0.2 → 0.0.4
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.d.ts +1 -0
- package/lib/index.js +487 -127
- package/lib/tools.d.ts +17 -0
- package/package.json +5 -2
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -26,193 +26,553 @@ __export(src_exports, {
|
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(src_exports);
|
|
28
28
|
var import_koishi = require("koishi");
|
|
29
|
+
|
|
30
|
+
// src/tools.ts
|
|
31
|
+
var import_tools = require("@langchain/core/tools");
|
|
32
|
+
var import_zod = require("zod");
|
|
33
|
+
var GroupMuteTool = class extends import_tools.StructuredTool {
|
|
34
|
+
constructor(runMute, verbose) {
|
|
35
|
+
super();
|
|
36
|
+
this.runMute = runMute;
|
|
37
|
+
this.verbose = verbose;
|
|
38
|
+
}
|
|
39
|
+
static {
|
|
40
|
+
__name(this, "GroupMuteTool");
|
|
41
|
+
}
|
|
42
|
+
name = "group_mute";
|
|
43
|
+
description = "在群内禁言成员";
|
|
44
|
+
schema = import_zod.z.object({
|
|
45
|
+
groupId: import_zod.z.string().trim().min(1).describe("目标群号,不填则使用当前会话群").optional(),
|
|
46
|
+
userId: import_zod.z.string().trim().min(1).describe("要禁言的成员 QQ 号"),
|
|
47
|
+
duration: import_zod.z.number().int().positive().describe("禁言时长(秒),0秒为解除禁言")
|
|
48
|
+
});
|
|
49
|
+
async _call(input, _runManager, runnableConfig) {
|
|
50
|
+
const session = runnableConfig?.configurable?.session;
|
|
51
|
+
if (!session) return "缺少 session,无法执行禁言";
|
|
52
|
+
const result = await this.runMute(session, {
|
|
53
|
+
targetId: input.userId,
|
|
54
|
+
duration: input.duration,
|
|
55
|
+
guildId: input.groupId
|
|
56
|
+
});
|
|
57
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var GroupTitleTool = class extends import_tools.StructuredTool {
|
|
61
|
+
constructor(runTitle, verbose) {
|
|
62
|
+
super();
|
|
63
|
+
this.runTitle = runTitle;
|
|
64
|
+
this.verbose = verbose;
|
|
65
|
+
}
|
|
66
|
+
static {
|
|
67
|
+
__name(this, "GroupTitleTool");
|
|
68
|
+
}
|
|
69
|
+
name = "group_set_title";
|
|
70
|
+
description = "为成员设置群头衔(需群主权限)";
|
|
71
|
+
schema = import_zod.z.object({
|
|
72
|
+
groupId: import_zod.z.string().trim().min(1).describe("目标群号,不填则使用当前会话群").optional(),
|
|
73
|
+
userId: import_zod.z.string().trim().min(1).describe("要设置头衔的成员 QQ 号"),
|
|
74
|
+
title: import_zod.z.string().trim().min(1).describe("头衔文案")
|
|
75
|
+
});
|
|
76
|
+
async _call(input, _runManager, runnableConfig) {
|
|
77
|
+
const session = runnableConfig?.configurable?.session;
|
|
78
|
+
if (!session) return "缺少 session,无法设置头衔";
|
|
79
|
+
const result = await this.runTitle(session, {
|
|
80
|
+
targetId: input.userId,
|
|
81
|
+
title: input.title,
|
|
82
|
+
guildId: input.groupId
|
|
83
|
+
});
|
|
84
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
function registerChatlunaTools(ctx, config, logger, verbose, handlers) {
|
|
88
|
+
const platform = ctx.chatluna?.platform;
|
|
89
|
+
if (!platform) {
|
|
90
|
+
logger.debug("chatluna 未安装,跳过群管工具注册");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
ctx.effect(
|
|
94
|
+
() => platform.registerTool("group_mute", {
|
|
95
|
+
createTool: /* @__PURE__ */ __name(() => new GroupMuteTool(
|
|
96
|
+
(session, params) => handlers.performMute(ctx, session, params, config, logger, verbose),
|
|
97
|
+
verbose
|
|
98
|
+
), "createTool"),
|
|
99
|
+
selector: /* @__PURE__ */ __name(() => true, "selector")
|
|
100
|
+
})
|
|
101
|
+
);
|
|
102
|
+
ctx.effect(
|
|
103
|
+
() => platform.registerTool("group_set_title", {
|
|
104
|
+
createTool: /* @__PURE__ */ __name(() => new GroupTitleTool(
|
|
105
|
+
(session, params) => handlers.performTitle(ctx, session, params, config, logger, verbose),
|
|
106
|
+
verbose
|
|
107
|
+
), "createTool"),
|
|
108
|
+
selector: /* @__PURE__ */ __name(() => true, "selector")
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
__name(registerChatlunaTools, "registerChatlunaTools");
|
|
113
|
+
|
|
114
|
+
// src/index.ts
|
|
29
115
|
var name = "onebot-group-manage";
|
|
30
116
|
var ROLE_CACHE_TTL = 3e4;
|
|
31
117
|
var ROLE_QUERY_TIMEOUT = 1e4;
|
|
32
118
|
var roleCache = /* @__PURE__ */ new Map();
|
|
33
119
|
var actionSchema = import_koishi.Schema.object({
|
|
34
120
|
requireGroupAdmin: import_koishi.Schema.boolean().default(true).description("调用者必须是群管/群主"),
|
|
35
|
-
minAuthority: import_koishi.Schema.number().min(0).default(1).description("调用者最低 Koishi
|
|
121
|
+
minAuthority: import_koishi.Schema.number().min(0).default(1).description("调用者最低 Koishi 权限,为 0 表示不校验")
|
|
36
122
|
});
|
|
37
123
|
var Config = import_koishi.Schema.object({
|
|
38
124
|
mute: actionSchema.description("禁言配置"),
|
|
39
125
|
unmute: actionSchema.description("解禁配置"),
|
|
40
126
|
kick: actionSchema.description("踢人配置"),
|
|
41
|
-
title: actionSchema.description("
|
|
127
|
+
title: actionSchema.description("头衔配置(机器人需要群主权限)"),
|
|
42
128
|
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("
|
|
129
|
+
minSeconds: import_koishi.Schema.number().min(1).default(60).description("“禁言我”随机最小秒数"),
|
|
130
|
+
maxSeconds: import_koishi.Schema.number().min(1).default(600).description("“禁言我”随机最大秒数")
|
|
131
|
+
}).description("自助禁言配置"),
|
|
132
|
+
chatlunaTools: import_koishi.Schema.boolean().default(true).description("启用 ChatLuna 工具注册(需安装 chatluna)"),
|
|
46
133
|
debug: import_koishi.Schema.object({
|
|
47
|
-
verbose: import_koishi.Schema.boolean().default(false)
|
|
48
|
-
}).default({ verbose: false })
|
|
134
|
+
verbose: import_koishi.Schema.boolean().default(false).description("开启详细日志(仅建议排查问题时启用)")
|
|
135
|
+
}).default({ verbose: false }).description("调试选项")
|
|
49
136
|
});
|
|
50
|
-
function
|
|
137
|
+
function toCamel(action) {
|
|
138
|
+
return action.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
139
|
+
}
|
|
140
|
+
__name(toCamel, "toCamel");
|
|
141
|
+
function withTimeout(p, ms, label) {
|
|
51
142
|
return Promise.race([
|
|
52
143
|
p,
|
|
53
|
-
new Promise((_,
|
|
144
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`${label} timeout (${ms}ms)`)), ms))
|
|
54
145
|
]);
|
|
55
146
|
}
|
|
56
147
|
__name(withTimeout, "withTimeout");
|
|
57
148
|
function getBotOnebot(bot) {
|
|
58
|
-
|
|
149
|
+
const direct = bot?.onebot || bot?.internal?.onebot;
|
|
150
|
+
if (direct) return direct;
|
|
151
|
+
const internal = bot?.internal;
|
|
152
|
+
if (internal && (internal.setGroupBan || internal.set_group_ban || internal.callApi)) return internal;
|
|
153
|
+
return null;
|
|
59
154
|
}
|
|
60
155
|
__name(getBotOnebot, "getBotOnebot");
|
|
61
|
-
function
|
|
62
|
-
|
|
156
|
+
async function callOneBotApi(bot, action, params, onebotOverride) {
|
|
157
|
+
const gid = params?.group_id ?? params?.groupId ?? params?.group?.id;
|
|
158
|
+
const uid = params?.user_id ?? params?.userId ?? params?.user?.id;
|
|
159
|
+
const payload = {
|
|
160
|
+
...params,
|
|
161
|
+
group_id: gid != null ? Number(gid) : gid,
|
|
162
|
+
user_id: uid != null ? Number(uid) : uid
|
|
163
|
+
};
|
|
164
|
+
const ob = onebotOverride || getBotOnebot(bot);
|
|
165
|
+
if (ob) {
|
|
166
|
+
switch (action) {
|
|
167
|
+
case "get_group_member_info": {
|
|
168
|
+
if (typeof ob.getGroupMemberInfo === "function") {
|
|
169
|
+
return ob.getGroupMemberInfo(Number(payload.group_id), Number(payload.user_id), Boolean(payload.no_cache));
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case "set_group_ban": {
|
|
174
|
+
if (typeof ob.callApi === "function") return ob.callApi("set_group_ban", payload);
|
|
175
|
+
if (typeof ob.setGroupBan === "function") {
|
|
176
|
+
return ob.setGroupBan(Number(payload.group_id), Number(payload.user_id), Number(payload.duration ?? 0));
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case "set_group_kick": {
|
|
181
|
+
if (typeof ob.callApi === "function") return ob.callApi("set_group_kick", payload);
|
|
182
|
+
if (typeof ob.setGroupKick === "function") {
|
|
183
|
+
return ob.setGroupKick(Number(payload.group_id), Number(payload.user_id), Boolean(payload.reject_add_request));
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case "set_group_special_title": {
|
|
188
|
+
if (typeof ob.callApi === "function") return ob.callApi("set_group_special_title", payload);
|
|
189
|
+
if (typeof ob.setGroupSpecialTitle === "function") {
|
|
190
|
+
return ob.setGroupSpecialTitle(
|
|
191
|
+
Number(payload.group_id),
|
|
192
|
+
Number(payload.user_id),
|
|
193
|
+
payload.special_title ?? "",
|
|
194
|
+
Number(payload.duration ?? -1)
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
default: {
|
|
200
|
+
if (typeof ob[action] === "function") return ob[action](payload);
|
|
201
|
+
const camel = toCamel(action);
|
|
202
|
+
if (typeof ob[camel] === "function") return ob[camel](payload);
|
|
203
|
+
if (typeof ob.callApi === "function") return ob.callApi(action, payload);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
throw new Error("onebot instance not available on bot");
|
|
63
208
|
}
|
|
64
|
-
__name(
|
|
65
|
-
|
|
66
|
-
|
|
209
|
+
__name(callOneBotApi, "callOneBotApi");
|
|
210
|
+
function botKey(bot) {
|
|
211
|
+
return bot?.sid || `${bot?.platform || "onebot"}:${bot?.selfId || ""}`;
|
|
212
|
+
}
|
|
213
|
+
__name(botKey, "botKey");
|
|
214
|
+
async function getMemberRole(probeOnebot, targetBot, guildId, userId, logger, verbose) {
|
|
215
|
+
const key = `${botKey(targetBot)}::${guildId}::${userId}`;
|
|
67
216
|
const cached = roleCache.get(key);
|
|
68
217
|
if (cached && cached.expire > Date.now()) return cached.role;
|
|
69
218
|
try {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
219
|
+
const direct = probeOnebot?.getGroupMemberInfo;
|
|
220
|
+
let info = null;
|
|
221
|
+
if (typeof direct === "function") {
|
|
222
|
+
info = await withTimeout(
|
|
223
|
+
direct.call(probeOnebot, Number(guildId), Number(userId), false),
|
|
224
|
+
ROLE_QUERY_TIMEOUT,
|
|
225
|
+
"get_group_member_info"
|
|
226
|
+
);
|
|
227
|
+
} else {
|
|
228
|
+
info = await withTimeout(
|
|
229
|
+
callOneBotApi(targetBot, "get_group_member_info", {
|
|
230
|
+
group_id: Number(guildId),
|
|
231
|
+
user_id: Number(userId),
|
|
232
|
+
no_cache: false
|
|
233
|
+
}, probeOnebot),
|
|
234
|
+
ROLE_QUERY_TIMEOUT,
|
|
235
|
+
"get_group_member_info"
|
|
236
|
+
);
|
|
237
|
+
}
|
|
74
238
|
const role = info?.role ?? null;
|
|
75
|
-
if (role)
|
|
239
|
+
if (role) {
|
|
240
|
+
roleCache.set(key, { role, expire: Date.now() + ROLE_CACHE_TTL });
|
|
241
|
+
logger?.[verbose ? "info" : "debug"](
|
|
242
|
+
`get_group_member_info ok bot=${botKey(targetBot)} guild=${guildId} user=${userId} role=${role}`
|
|
243
|
+
);
|
|
244
|
+
} else {
|
|
245
|
+
logger?.[verbose ? "warn" : "debug"](
|
|
246
|
+
`get_group_member_info empty bot=${botKey(targetBot)} guild=${guildId} user=${userId}`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
76
249
|
return role;
|
|
77
|
-
} catch {
|
|
250
|
+
} catch (error) {
|
|
251
|
+
logger?.[verbose ? "warn" : "debug"](error, `get_group_member_info failed bot=${botKey(targetBot)} guild=${guildId} user=${userId}`);
|
|
78
252
|
return null;
|
|
79
253
|
}
|
|
80
254
|
}
|
|
81
255
|
__name(getMemberRole, "getMemberRole");
|
|
82
|
-
async function verifyCaller(session, rule) {
|
|
256
|
+
async function verifyCaller(session, rule, logger, verbose, guildIdOverride) {
|
|
83
257
|
const authority = Number(session.user?.authority ?? 0);
|
|
84
258
|
if (!rule.requireGroupAdmin && rule.minAuthority <= 0) return null;
|
|
85
259
|
if (rule.minAuthority > 0 && authority >= rule.minAuthority) return null;
|
|
86
|
-
|
|
260
|
+
const guildId = guildIdOverride ?? session.guildId;
|
|
261
|
+
if (rule.requireGroupAdmin && !guildId) return "仅限群聊使用";
|
|
262
|
+
if (rule.requireGroupAdmin && guildId) {
|
|
87
263
|
const probe = session.onebot || getBotOnebot(session.bot);
|
|
88
|
-
const role = await getMemberRole(
|
|
89
|
-
probe,
|
|
90
|
-
session.bot,
|
|
91
|
-
session.guildId,
|
|
92
|
-
session.userId
|
|
93
|
-
);
|
|
264
|
+
const role = await getMemberRole(probe, session.bot, guildId, session.userId, logger, verbose);
|
|
94
265
|
if (role === "owner" || role === "admin") return null;
|
|
95
266
|
}
|
|
96
267
|
if (rule.requireGroupAdmin && rule.minAuthority > 0) {
|
|
97
|
-
return
|
|
268
|
+
return `需要群管理/群主或至少 ${rule.minAuthority} 级 Koishi 权限`;
|
|
98
269
|
}
|
|
99
|
-
if (rule.requireGroupAdmin) return "
|
|
100
|
-
return
|
|
270
|
+
if (rule.requireGroupAdmin) return "需要群管理或群主权限";
|
|
271
|
+
return `需要至少 ${rule.minAuthority} 级 Koishi 权限`;
|
|
101
272
|
}
|
|
102
273
|
__name(verifyCaller, "verifyCaller");
|
|
274
|
+
async function pickManageBot(ctx, guildId, requireOwner, logger, preferBot, verbose, probeOnebot) {
|
|
275
|
+
const candidates = ctx.bots.filter((b) => b.platform === "onebot");
|
|
276
|
+
const probeBot = probeOnebot || preferBot && preferBot.onebot || candidates.find((b) => b.onebot) || candidates.find((b) => b.internal?.onebot);
|
|
277
|
+
const probeInstance = probeBot?.onebot || probeBot?.internal?.onebot || probeOnebot;
|
|
278
|
+
if (!probeInstance) {
|
|
279
|
+
logger.warn(`no probe bot with onebot instance for guild=${guildId}`);
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
const seen = /* @__PURE__ */ new Set();
|
|
283
|
+
const ordered = [];
|
|
284
|
+
if (preferBot && preferBot.platform === "onebot") {
|
|
285
|
+
ordered.push(preferBot);
|
|
286
|
+
seen.add(botKey(preferBot));
|
|
287
|
+
}
|
|
288
|
+
for (const bot of candidates) {
|
|
289
|
+
const k = botKey(bot);
|
|
290
|
+
if (seen.has(k)) continue;
|
|
291
|
+
ordered.push(bot);
|
|
292
|
+
seen.add(k);
|
|
293
|
+
}
|
|
294
|
+
const roles = await Promise.all(
|
|
295
|
+
ordered.map(async (bot) => {
|
|
296
|
+
const role = await getMemberRole(probeInstance, bot, guildId, bot.selfId, logger, verbose);
|
|
297
|
+
return { bot, role };
|
|
298
|
+
})
|
|
299
|
+
);
|
|
300
|
+
for (const { bot, role } of roles) {
|
|
301
|
+
if (!role) continue;
|
|
302
|
+
const ok = requireOwner ? role === "owner" : role === "owner" || role === "admin";
|
|
303
|
+
if (ok) {
|
|
304
|
+
logger[verbose ? "info" : "debug"](`picked bot=${botKey(bot)} role=${role} guild=${guildId}`);
|
|
305
|
+
return { bot, role, probeOnebot: probeInstance };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
logger.warn(`no available bot with manage permission in guild=${guildId}`);
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
__name(pickManageBot, "pickManageBot");
|
|
103
312
|
function parseTargetId(raw, session) {
|
|
104
313
|
if (raw) {
|
|
105
|
-
const
|
|
106
|
-
const at =
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
314
|
+
const segments = import_koishi.segment.parse(raw);
|
|
315
|
+
const at = segments.find((seg) => seg.type === "at" && seg.attrs?.id);
|
|
316
|
+
const atId = at?.attrs?.id;
|
|
317
|
+
if (atId) return String(atId);
|
|
318
|
+
const match = raw.match(/\d{5,}/);
|
|
319
|
+
if (match) return match[0];
|
|
110
320
|
}
|
|
111
|
-
|
|
321
|
+
if (session.quote?.user?.id) return String(session.quote.user.id);
|
|
322
|
+
return null;
|
|
112
323
|
}
|
|
113
324
|
__name(parseTargetId, "parseTargetId");
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
325
|
+
function formatRule(rule) {
|
|
326
|
+
const needAdmin = rule.requireGroupAdmin;
|
|
327
|
+
const needAuth = rule.minAuthority > 0;
|
|
328
|
+
if (needAdmin && needAuth) return `群管/群主 或 Koishi≥${rule.minAuthority}`;
|
|
329
|
+
if (needAdmin) return "群管/群主";
|
|
330
|
+
if (needAuth) return `Koishi≥${rule.minAuthority}`;
|
|
331
|
+
return "无限制";
|
|
332
|
+
}
|
|
333
|
+
__name(formatRule, "formatRule");
|
|
334
|
+
function randomInt(min, max) {
|
|
335
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
336
|
+
}
|
|
337
|
+
__name(randomInt, "randomInt");
|
|
338
|
+
async function performMute(ctx, session, params, config, logger, verbose) {
|
|
339
|
+
const guildId = params.guildId ?? session.guildId;
|
|
340
|
+
if (!guildId) return "仅限群聊使用";
|
|
341
|
+
const targetId = params.targetId ?? parseTargetId(params.targetRaw || "", session);
|
|
342
|
+
if (!targetId) return "请提供要禁言的用户(@ 或 QQ 号)";
|
|
343
|
+
const seconds = Number(params.duration);
|
|
344
|
+
if (!Number.isFinite(seconds) || seconds <= 0) return "禁言时长必须为大于 0 的整数(秒)";
|
|
345
|
+
const msg = await verifyCaller(session, config.mute, logger, verbose, guildId);
|
|
346
|
+
if (msg) return msg;
|
|
347
|
+
verbose && logger.info(
|
|
348
|
+
`mute: guild=${guildId} caller=${session.userId} bot=${botKey(session.bot)} target=${targetId} seconds=${seconds}`
|
|
349
|
+
);
|
|
350
|
+
const picked = await pickManageBot(ctx, guildId, false, logger, session.bot, verbose, session.onebot);
|
|
351
|
+
if (!picked) return "没有可用且有群管权限的 Bot";
|
|
352
|
+
const targetRole = await getMemberRole(picked.probeOnebot, picked.bot, guildId, targetId, logger, verbose);
|
|
353
|
+
if (targetRole === "owner") return "无法对群主执行该操作";
|
|
354
|
+
const actionOnebot = getBotOnebot(picked.bot);
|
|
355
|
+
if (!actionOnebot) return "选中的 Bot 缺少 onebot 实例,无法执行";
|
|
356
|
+
try {
|
|
357
|
+
verbose && logger.info(`mute: use bot=${botKey(picked.bot)} role=${picked.role} duration=${seconds}`);
|
|
358
|
+
if (typeof actionOnebot.setGroupBan === "function") {
|
|
359
|
+
await actionOnebot.setGroupBan(Number(guildId), Number(targetId), Number(seconds));
|
|
360
|
+
} else {
|
|
361
|
+
await callOneBotApi(picked.bot, "set_group_ban", {
|
|
362
|
+
group_id: Number(guildId),
|
|
363
|
+
user_id: Number(targetId),
|
|
364
|
+
duration: Number(seconds)
|
|
365
|
+
});
|
|
122
366
|
}
|
|
367
|
+
roleCache.clear();
|
|
368
|
+
return `已使用 ${picked.bot.selfId} 禁言 ${targetId} ${seconds} 秒`;
|
|
369
|
+
} catch (error) {
|
|
370
|
+
logger.warn(error);
|
|
371
|
+
return "禁言失败,检查 Bot 权限或参数";
|
|
123
372
|
}
|
|
124
|
-
return null;
|
|
125
373
|
}
|
|
126
|
-
__name(
|
|
127
|
-
function
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
374
|
+
__name(performMute, "performMute");
|
|
375
|
+
async function performTitle(ctx, session, params, config, logger, verbose) {
|
|
376
|
+
const guildId = params.guildId ?? session.guildId;
|
|
377
|
+
if (!guildId) return "仅限群聊使用";
|
|
378
|
+
const title = params.title?.trim();
|
|
379
|
+
if (!title) return "请提供要设置的头衔";
|
|
380
|
+
const userId = params.targetId ?? parseTargetId(params.targetRaw || "", session);
|
|
381
|
+
if (!userId) return "请提供要设置头衔的用户(@ 或 QQ 号)";
|
|
382
|
+
const msg = await verifyCaller(session, config.title, logger, verbose, guildId);
|
|
383
|
+
if (msg) return msg;
|
|
384
|
+
verbose && logger.info(
|
|
385
|
+
`title: guild=${guildId} caller=${session.userId} bot=${botKey(session.bot)} target=${userId} title=${title}`
|
|
386
|
+
);
|
|
387
|
+
const picked = await pickManageBot(ctx, guildId, true, logger, session.bot, verbose, session.onebot);
|
|
388
|
+
if (!picked) return "没有具备群主权限的 Bot 可用";
|
|
389
|
+
const actionOnebot = getBotOnebot(picked.bot);
|
|
390
|
+
if (!actionOnebot) return "选中的 Bot 缺少 onebot 实例,无法执行";
|
|
391
|
+
try {
|
|
392
|
+
verbose && logger.info(`title: use bot=${botKey(picked.bot)} role=${picked.role}`);
|
|
393
|
+
if (typeof actionOnebot.setGroupSpecialTitle === "function") {
|
|
394
|
+
await actionOnebot.setGroupSpecialTitle(Number(guildId), Number(userId), title, -1);
|
|
395
|
+
} else {
|
|
396
|
+
await callOneBotApi(picked.bot, "set_group_special_title", {
|
|
397
|
+
group_id: Number(guildId),
|
|
398
|
+
user_id: Number(userId),
|
|
399
|
+
special_title: title,
|
|
400
|
+
duration: -1
|
|
401
|
+
});
|
|
402
|
+
}
|
|
140
403
|
roleCache.clear();
|
|
141
|
-
return
|
|
404
|
+
return `已使用 ${picked.bot.selfId} 为 ${userId} 设置头衔:${title}`;
|
|
405
|
+
} catch (error) {
|
|
406
|
+
logger.warn(error);
|
|
407
|
+
return "设置头衔失败,检查 Bot 权限或参数";
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
__name(performTitle, "performTitle");
|
|
411
|
+
function apply(ctx, config) {
|
|
412
|
+
const logger = ctx.logger(name);
|
|
413
|
+
const verbose = config.debug?.verbose ?? false;
|
|
414
|
+
if (config.chatlunaTools) {
|
|
415
|
+
registerChatlunaTools(ctx, config, logger, verbose, {
|
|
416
|
+
performMute,
|
|
417
|
+
performTitle
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
ctx.command("group.manage", "OneBot 群管", { authority: 0 }).channelFields(["platform"]);
|
|
421
|
+
ctx.command("group.manage/check <target:string>", "调试:查询 get_group_member_info").alias("gcheck").action(async ({ session }, target) => {
|
|
422
|
+
if (!session?.guildId) return "仅限群聊使用";
|
|
423
|
+
const userId = parseTargetId(target, session);
|
|
424
|
+
if (!userId) return "请提供要查询的用户(@ 或 QQ 号)";
|
|
425
|
+
try {
|
|
426
|
+
const start = Date.now();
|
|
427
|
+
const probe = session.onebot || getBotOnebot(session.bot);
|
|
428
|
+
const info = await callOneBotApi(
|
|
429
|
+
session.bot,
|
|
430
|
+
"get_group_member_info",
|
|
431
|
+
{
|
|
432
|
+
group_id: Number(session.guildId),
|
|
433
|
+
user_id: Number(userId),
|
|
434
|
+
no_cache: false
|
|
435
|
+
},
|
|
436
|
+
probe
|
|
437
|
+
);
|
|
438
|
+
const cost = Date.now() - start;
|
|
439
|
+
const role = info?.role ?? "unknown";
|
|
440
|
+
return `查询成功,用时 ${cost}ms,role=${role},raw=${JSON.stringify(info)}`;
|
|
441
|
+
} catch (error) {
|
|
442
|
+
return `查询失败: ${error?.message || String(error)}`;
|
|
443
|
+
}
|
|
142
444
|
});
|
|
143
|
-
ctx.command("group.manage/
|
|
144
|
-
if (!session?.guildId) return "
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
445
|
+
ctx.command("group.manage/status", "查看本群 Bot 权限状态").alias("gstatus").action(async ({ session }) => {
|
|
446
|
+
if (!session?.guildId) return "仅限群聊使用";
|
|
447
|
+
const probe = session.onebot || getBotOnebot(session.bot);
|
|
448
|
+
if (!probe) return "当前 Bot 无 onebot 实例,无法查询";
|
|
449
|
+
const bots = ctx.bots.filter((b) => b.platform === "onebot");
|
|
450
|
+
const lines = [];
|
|
451
|
+
for (const b of bots) {
|
|
452
|
+
const role = await getMemberRole(probe, b, session.guildId, b.selfId, logger, verbose);
|
|
453
|
+
lines.push(`${b.selfId}: ${role ?? "unknown"}`);
|
|
454
|
+
}
|
|
455
|
+
lines.push(`配置:mute=${formatRule(config.mute)} / unmute=${formatRule(config.unmute)} / kick=${formatRule(config.kick)} / title=${formatRule(config.title)}`);
|
|
456
|
+
lines.push(`自助:gmute.me 随机范围 ${config.selfMute.minSeconds}~${config.selfMute.maxSeconds} 秒`);
|
|
457
|
+
return lines.join("\n");
|
|
154
458
|
});
|
|
155
|
-
ctx.command("group.manage/
|
|
156
|
-
if (!session
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
await picked.probe.setGroupKick(Number(session.guildId), Number(uid), false);
|
|
166
|
-
roleCache.clear();
|
|
167
|
-
return `已踢出 ${uid}`;
|
|
459
|
+
ctx.command("group.manage/mute <target:string> <duration:number>", "禁言目标(秒,必须指定)").alias("gmute").action(async ({ session }, target, duration) => {
|
|
460
|
+
if (!session) return "缺少 session";
|
|
461
|
+
return performMute(
|
|
462
|
+
ctx,
|
|
463
|
+
session,
|
|
464
|
+
{ targetRaw: target, duration: Number(duration) },
|
|
465
|
+
config,
|
|
466
|
+
logger,
|
|
467
|
+
verbose
|
|
468
|
+
);
|
|
168
469
|
});
|
|
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
|
-
|
|
470
|
+
ctx.command("group.manage/unmute <target:string>", "解除禁言").alias("gunmute").action(async ({ session }, target) => {
|
|
471
|
+
if (!session?.guildId) return "仅限群聊使用";
|
|
472
|
+
const msg = await verifyCaller(session, config.unmute, logger, verbose);
|
|
473
|
+
if (msg) return msg;
|
|
474
|
+
const userId = parseTargetId(target, session);
|
|
475
|
+
if (!userId) return "请提供要解禁的用户(@ 或 QQ 号)";
|
|
476
|
+
verbose && logger.info(`unmute: guild=${session.guildId} caller=${session.userId} bot=${botKey(session.bot)} target=${userId}`);
|
|
477
|
+
const picked = await pickManageBot(ctx, session.guildId, false, logger, session.bot, verbose, session.onebot);
|
|
478
|
+
if (!picked) return "没有可用且有群管权限的 Bot";
|
|
479
|
+
const targetRole = await getMemberRole(picked.probeOnebot, picked.bot, session.guildId, userId, logger, verbose);
|
|
480
|
+
if (targetRole === "owner") return "无法对群主执行该操作";
|
|
481
|
+
const actionOnebot = getBotOnebot(picked.bot);
|
|
482
|
+
if (!actionOnebot) return "选中的 Bot 缺少 onebot 实例,无法执行";
|
|
483
|
+
try {
|
|
484
|
+
verbose && logger.info(`unmute: use bot=${botKey(picked.bot)} role=${picked.role}`);
|
|
485
|
+
if (typeof actionOnebot.setGroupBan === "function") {
|
|
486
|
+
await actionOnebot.setGroupBan(Number(session.guildId), Number(userId), 0);
|
|
487
|
+
} else {
|
|
488
|
+
await callOneBotApi(picked.bot, "set_group_ban", {
|
|
489
|
+
group_id: Number(session.guildId),
|
|
490
|
+
user_id: Number(userId),
|
|
491
|
+
duration: 0
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
roleCache.clear();
|
|
495
|
+
return `已使用 ${picked.bot.selfId} 解除 ${userId} 的禁言`;
|
|
496
|
+
} catch (error) {
|
|
497
|
+
logger.warn(error);
|
|
498
|
+
return "解禁失败,检查 Bot 权限或参数";
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
ctx.command("group.manage/kick <target:string> [reject:boolean]", "踢出成员").alias("gkick").option("reject", "-r 设为 true 拒绝再次申请").action(async ({ session, options }, target, rejectFlag) => {
|
|
502
|
+
if (!session?.guildId) return "仅限群聊使用";
|
|
503
|
+
const msg = await verifyCaller(session, config.kick, logger, verbose);
|
|
504
|
+
if (msg) return msg;
|
|
505
|
+
const userId = parseTargetId(target, session);
|
|
506
|
+
if (!userId) return "请提供要踢出的用户(@ 或 QQ 号)";
|
|
507
|
+
const reject = Boolean(options?.reject ?? rejectFlag);
|
|
508
|
+
verbose && logger.info(`kick: guild=${session.guildId} caller=${session.userId} bot=${botKey(session.bot)} target=${userId} reject=${reject}`);
|
|
509
|
+
const picked = await pickManageBot(ctx, session.guildId, false, logger, session.bot, verbose, session.onebot);
|
|
510
|
+
if (!picked) return "没有可用且有群管权限的 Bot";
|
|
511
|
+
const targetRole = await getMemberRole(picked.probeOnebot, picked.bot, session.guildId, userId, logger, verbose);
|
|
512
|
+
if (targetRole === "owner") return "无法对群主执行该操作";
|
|
513
|
+
const actionOnebot = getBotOnebot(picked.bot);
|
|
514
|
+
if (!actionOnebot) return "选中的 Bot 缺少 onebot 实例,无法执行";
|
|
515
|
+
try {
|
|
516
|
+
verbose && logger.info(`kick: use bot=${botKey(picked.bot)} role=${picked.role}`);
|
|
517
|
+
if (typeof actionOnebot.setGroupKick === "function") {
|
|
518
|
+
await actionOnebot.setGroupKick(Number(session.guildId), Number(userId), reject);
|
|
519
|
+
} else {
|
|
520
|
+
await callOneBotApi(picked.bot, "set_group_kick", {
|
|
521
|
+
group_id: Number(session.guildId),
|
|
522
|
+
user_id: Number(userId),
|
|
523
|
+
reject_add_request: reject
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
roleCache.clear();
|
|
527
|
+
return `已使用 ${picked.bot.selfId} 踢出 ${userId}${reject ? "(已拒绝再次申请)" : ""}`;
|
|
528
|
+
} catch (error) {
|
|
529
|
+
logger.warn(error);
|
|
530
|
+
return "踢人失败,检查 Bot 权限或参数";
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
ctx.command("group.manage/title <target:string> <title:text>", "设置群头衔(需群主 Bot)").alias("gtitle").action(async ({ session }, target, title) => {
|
|
534
|
+
if (!session) return "缺少 session";
|
|
535
|
+
return performTitle(
|
|
536
|
+
ctx,
|
|
537
|
+
session,
|
|
538
|
+
{ targetRaw: target, title: title || "", guildId: session.guildId },
|
|
539
|
+
config,
|
|
540
|
+
logger,
|
|
541
|
+
verbose
|
|
182
542
|
);
|
|
183
|
-
roleCache.clear();
|
|
184
|
-
return `已设置头衔`;
|
|
185
543
|
});
|
|
186
|
-
ctx.command("group.manage/mute.me", "
|
|
187
|
-
if (!session?.guildId) return "
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
const
|
|
544
|
+
ctx.command("group.manage/mute.me", "禁言我(随机时长)").alias("gmute.me").action(async ({ session }) => {
|
|
545
|
+
if (!session?.guildId) return "仅限群聊使用";
|
|
546
|
+
const minS = Math.max(1, Math.floor(Number(config.selfMute?.minSeconds ?? 60)));
|
|
547
|
+
const maxS = Math.max(1, Math.floor(Number(config.selfMute?.maxSeconds ?? 600)));
|
|
548
|
+
const minSeconds = Math.min(minS, maxS);
|
|
549
|
+
const maxSeconds = Math.max(minS, maxS);
|
|
191
550
|
const seconds = randomInt(minSeconds, maxSeconds);
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
551
|
+
const result = await performMute(
|
|
552
|
+
ctx,
|
|
553
|
+
session,
|
|
554
|
+
{ targetId: session.userId, duration: seconds, guildId: session.guildId },
|
|
555
|
+
config,
|
|
556
|
+
logger,
|
|
557
|
+
verbose
|
|
198
558
|
);
|
|
199
|
-
|
|
200
|
-
return
|
|
559
|
+
if (typeof result === "string" && result.startsWith("已使用")) return `你已被禁言 ${seconds} 秒`;
|
|
560
|
+
return result;
|
|
201
561
|
});
|
|
202
|
-
ctx.command("group.manage/title.apply <title:text>", "
|
|
203
|
-
if (!session?.guildId) return "
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
title.trim(),
|
|
212
|
-
-1
|
|
562
|
+
ctx.command("group.manage/title.apply <title:text>", "申请群头衔(给自己)").alias("gtitle.apply").action(async ({ session }, title) => {
|
|
563
|
+
if (!session?.guildId) return "仅限群聊使用";
|
|
564
|
+
const result = await performTitle(
|
|
565
|
+
ctx,
|
|
566
|
+
session,
|
|
567
|
+
{ targetId: session.userId, title: title || "", guildId: session.guildId },
|
|
568
|
+
config,
|
|
569
|
+
logger,
|
|
570
|
+
verbose
|
|
213
571
|
);
|
|
214
|
-
|
|
215
|
-
|
|
572
|
+
if (typeof result === "string" && result.startsWith("已使用")) {
|
|
573
|
+
return `已为你设置头衔:${(title || "").trim()}`;
|
|
574
|
+
}
|
|
575
|
+
return result;
|
|
216
576
|
});
|
|
217
577
|
}
|
|
218
578
|
__name(apply, "apply");
|
package/lib/tools.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Context, Session } from 'koishi';
|
|
2
|
+
interface Handlers {
|
|
3
|
+
performMute: (ctx: Context, session: Session, params: {
|
|
4
|
+
targetId: string;
|
|
5
|
+
duration: number;
|
|
6
|
+
guildId?: string;
|
|
7
|
+
}, ...rest: any[]) => Promise<string>;
|
|
8
|
+
performTitle: (ctx: Context, session: Session, params: {
|
|
9
|
+
targetId: string;
|
|
10
|
+
title: string;
|
|
11
|
+
guildId?: string;
|
|
12
|
+
}, ...rest: any[]) => Promise<string>;
|
|
13
|
+
}
|
|
14
|
+
export declare function registerChatlunaTools(ctx: Context, config: {
|
|
15
|
+
chatlunaTools?: boolean;
|
|
16
|
+
}, logger: ReturnType<Context['logger']>, verbose: boolean | undefined, handlers: Handlers): void;
|
|
17
|
+
export {};
|
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.
|
|
4
|
+
"version": "0.0.4",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
"plugin"
|
|
16
16
|
],
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"koishi": "^4.18.7"
|
|
18
|
+
"koishi": "^4.18.7",
|
|
19
|
+
"@langchain/core": "^0.3.0",
|
|
20
|
+
"koishi-plugin-chatluna": "^1.3.0",
|
|
21
|
+
"zod": "^3.23.8"
|
|
19
22
|
}
|
|
20
23
|
}
|