koishi-plugin-group-control 0.2.11 → 1.0.0-alpha.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/lib/config.d.ts +1 -0
- package/lib/database.d.ts +15 -0
- package/lib/index.js +107 -46
- package/lib/utils.d.ts +5 -0
- package/package.json +1 -1
- package/readme.md +208 -0
- package/README.md +0 -9
package/lib/config.d.ts
CHANGED
package/lib/database.d.ts
CHANGED
|
@@ -23,12 +23,22 @@ export interface SmallGroupWhitelist {
|
|
|
23
23
|
platform: string;
|
|
24
24
|
guildId: string;
|
|
25
25
|
}
|
|
26
|
+
export interface PendingInvite {
|
|
27
|
+
platform: string;
|
|
28
|
+
groupId: string;
|
|
29
|
+
userId: string;
|
|
30
|
+
userName: string;
|
|
31
|
+
groupName: string;
|
|
32
|
+
time: number;
|
|
33
|
+
flag: string;
|
|
34
|
+
}
|
|
26
35
|
declare module 'koishi' {
|
|
27
36
|
interface Tables {
|
|
28
37
|
blacklisted_guild: BlacklistedGuild;
|
|
29
38
|
command_frequency_record: CommandFrequencyRecord;
|
|
30
39
|
group_bot_status: GroupBotStatus;
|
|
31
40
|
small_group_whitelist: SmallGroupWhitelist;
|
|
41
|
+
pending_invite: PendingInvite;
|
|
32
42
|
}
|
|
33
43
|
}
|
|
34
44
|
export declare const name = "group-control-database";
|
|
@@ -47,3 +57,8 @@ export declare function isInSmallGroupWhitelist(ctx: Context, guildId: string):
|
|
|
47
57
|
export declare function addToSmallGroupWhitelist(ctx: Context, guildId: string): Promise<void>;
|
|
48
58
|
export declare function removeFromSmallGroupWhitelist(ctx: Context, guildId: string): Promise<void>;
|
|
49
59
|
export declare function getAllSmallGroupWhitelist(ctx: Context): Promise<SmallGroupWhitelist[]>;
|
|
60
|
+
export declare function getPendingInvite(ctx: Context, groupId: string): Promise<PendingInvite>;
|
|
61
|
+
export declare function addPendingInvite(ctx: Context, inviteUser: Omit<PendingInvite, 'platform'>): Promise<void>;
|
|
62
|
+
export declare function removePendingInvite(ctx: Context, groupId: string): Promise<void>;
|
|
63
|
+
export declare function getAllPendingInvites(ctx: Context): Promise<PendingInvite[]>;
|
|
64
|
+
export declare function clearExpiredPendingInvites(ctx: Context, expireTimeMs: number): Promise<number>;
|
package/lib/index.js
CHANGED
|
@@ -30,19 +30,24 @@ module.exports = __toCommonJS(src_exports);
|
|
|
30
30
|
var database_exports = {};
|
|
31
31
|
__export(database_exports, {
|
|
32
32
|
BLACKLIST_PLATFORM: () => BLACKLIST_PLATFORM,
|
|
33
|
+
addPendingInvite: () => addPendingInvite,
|
|
33
34
|
addToSmallGroupWhitelist: () => addToSmallGroupWhitelist,
|
|
34
35
|
apply: () => apply,
|
|
35
36
|
clearBlacklistedGuilds: () => clearBlacklistedGuilds,
|
|
37
|
+
clearExpiredPendingInvites: () => clearExpiredPendingInvites,
|
|
36
38
|
createBlacklistedGuild: () => createBlacklistedGuild,
|
|
37
39
|
getAllBlacklistedGuilds: () => getAllBlacklistedGuilds,
|
|
40
|
+
getAllPendingInvites: () => getAllPendingInvites,
|
|
38
41
|
getAllSmallGroupWhitelist: () => getAllSmallGroupWhitelist,
|
|
39
42
|
getBlacklistedGuild: () => getBlacklistedGuild,
|
|
40
43
|
getCommandFrequencyRecord: () => getCommandFrequencyRecord,
|
|
41
44
|
getGroupBotStatus: () => getGroupBotStatus,
|
|
45
|
+
getPendingInvite: () => getPendingInvite,
|
|
42
46
|
isInSmallGroupWhitelist: () => isInSmallGroupWhitelist,
|
|
43
47
|
name: () => name,
|
|
44
48
|
removeBlacklistedGuild: () => removeBlacklistedGuild,
|
|
45
49
|
removeFromSmallGroupWhitelist: () => removeFromSmallGroupWhitelist,
|
|
50
|
+
removePendingInvite: () => removePendingInvite,
|
|
46
51
|
setGroupBotStatus: () => setGroupBotStatus,
|
|
47
52
|
updateCommandFrequencyRecord: () => updateCommandFrequencyRecord
|
|
48
53
|
});
|
|
@@ -72,6 +77,15 @@ function apply(ctx) {
|
|
|
72
77
|
platform: "string",
|
|
73
78
|
guildId: "string"
|
|
74
79
|
}, { primary: ["platform", "guildId"] });
|
|
80
|
+
ctx.model.extend("pending_invite", {
|
|
81
|
+
platform: "string",
|
|
82
|
+
groupId: "string",
|
|
83
|
+
userId: "string",
|
|
84
|
+
userName: "string",
|
|
85
|
+
groupName: "string",
|
|
86
|
+
time: "integer",
|
|
87
|
+
flag: "string"
|
|
88
|
+
}, { primary: ["platform", "groupId"] });
|
|
75
89
|
}
|
|
76
90
|
__name(apply, "apply");
|
|
77
91
|
var BLACKLIST_PLATFORM = "onebot";
|
|
@@ -139,6 +153,33 @@ async function getAllSmallGroupWhitelist(ctx) {
|
|
|
139
153
|
return await ctx.model.get("small_group_whitelist", { platform: BLACKLIST_PLATFORM });
|
|
140
154
|
}
|
|
141
155
|
__name(getAllSmallGroupWhitelist, "getAllSmallGroupWhitelist");
|
|
156
|
+
async function getPendingInvite(ctx, groupId) {
|
|
157
|
+
const records = await ctx.model.get("pending_invite", { platform: BLACKLIST_PLATFORM, groupId });
|
|
158
|
+
return records.length > 0 ? records[0] : null;
|
|
159
|
+
}
|
|
160
|
+
__name(getPendingInvite, "getPendingInvite");
|
|
161
|
+
async function addPendingInvite(ctx, inviteUser) {
|
|
162
|
+
await ctx.model.upsert("pending_invite", [{ platform: BLACKLIST_PLATFORM, ...inviteUser }]);
|
|
163
|
+
}
|
|
164
|
+
__name(addPendingInvite, "addPendingInvite");
|
|
165
|
+
async function removePendingInvite(ctx, groupId) {
|
|
166
|
+
await ctx.model.remove("pending_invite", { platform: BLACKLIST_PLATFORM, groupId });
|
|
167
|
+
}
|
|
168
|
+
__name(removePendingInvite, "removePendingInvite");
|
|
169
|
+
async function getAllPendingInvites(ctx) {
|
|
170
|
+
return await ctx.model.get("pending_invite", { platform: BLACKLIST_PLATFORM });
|
|
171
|
+
}
|
|
172
|
+
__name(getAllPendingInvites, "getAllPendingInvites");
|
|
173
|
+
async function clearExpiredPendingInvites(ctx, expireTimeMs) {
|
|
174
|
+
const cutoff = Date.now() - expireTimeMs;
|
|
175
|
+
const all = await ctx.model.get("pending_invite", { platform: BLACKLIST_PLATFORM });
|
|
176
|
+
const expired = all.filter((r) => r.time < cutoff);
|
|
177
|
+
for (const record of expired) {
|
|
178
|
+
await ctx.model.remove("pending_invite", { platform: BLACKLIST_PLATFORM, groupId: record.groupId });
|
|
179
|
+
}
|
|
180
|
+
return expired.length;
|
|
181
|
+
}
|
|
182
|
+
__name(clearExpiredPendingInvites, "clearExpiredPendingInvites");
|
|
142
183
|
|
|
143
184
|
// src/modules/basic.ts
|
|
144
185
|
var basic_exports = {};
|
|
@@ -183,6 +224,9 @@ async function notifyAdmins(bot, config, message) {
|
|
|
183
224
|
}
|
|
184
225
|
__name(notifyAdmins, "notifyAdmins");
|
|
185
226
|
async function hasPermission(session, config) {
|
|
227
|
+
if (config.invite.adminQQs?.includes(session.userId)) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
186
230
|
if (config.permission.mode === "koishi") {
|
|
187
231
|
try {
|
|
188
232
|
const user = session.user;
|
|
@@ -194,9 +238,6 @@ async function hasPermission(session, config) {
|
|
|
194
238
|
return false;
|
|
195
239
|
}
|
|
196
240
|
const userId = session.userId;
|
|
197
|
-
if (config.invite.adminQQs?.includes(userId)) {
|
|
198
|
-
return true;
|
|
199
|
-
}
|
|
200
241
|
try {
|
|
201
242
|
const member = await session.bot.getGuildMember(session.guildId, userId);
|
|
202
243
|
const roles = member?.roles || member?.role;
|
|
@@ -223,20 +264,31 @@ async function hasPermission(session, config) {
|
|
|
223
264
|
return false;
|
|
224
265
|
}
|
|
225
266
|
__name(hasPermission, "hasPermission");
|
|
267
|
+
function isGlobalAdmin(session, config) {
|
|
268
|
+
if (config.invite.adminQQs?.includes(session.userId)) {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
const user = session.user;
|
|
272
|
+
if (config.permission.mode === "koishi" && user && typeof user.authority === "number") {
|
|
273
|
+
return user.authority >= 4;
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
__name(isGlobalAdmin, "isGlobalAdmin");
|
|
226
278
|
var ADMIN_COMMANDS = /* @__PURE__ */ new Set([
|
|
227
279
|
"bot-on",
|
|
228
280
|
"bot-off",
|
|
229
281
|
"quit",
|
|
230
|
-
"
|
|
231
|
-
"
|
|
232
|
-
"
|
|
233
|
-
"
|
|
282
|
+
"banlist",
|
|
283
|
+
"unban",
|
|
284
|
+
"ban",
|
|
285
|
+
"clearban",
|
|
234
286
|
"approve",
|
|
235
287
|
"reject",
|
|
236
|
-
"pending
|
|
237
|
-
"
|
|
238
|
-
"
|
|
239
|
-
"
|
|
288
|
+
"pending",
|
|
289
|
+
"sg-add",
|
|
290
|
+
"sg-rm",
|
|
291
|
+
"sg-list"
|
|
240
292
|
]);
|
|
241
293
|
|
|
242
294
|
// src/state.ts
|
|
@@ -427,19 +479,17 @@ __export(invite_exports, {
|
|
|
427
479
|
var name3 = "group-control-invite";
|
|
428
480
|
function apply3(ctx, config) {
|
|
429
481
|
if (!config.invite.enabled) return;
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
pendingInvites.delete(key);
|
|
437
|
-
if (config.invite.showDetailedLog) {
|
|
438
|
-
console.log(`邀请超时已清理: 群号=${invite.groupId}, 邀请者=${invite.userId}`);
|
|
439
|
-
}
|
|
482
|
+
setInterval(async () => {
|
|
483
|
+
const expireMs = config.invite.inviteExpireDays * 24 * 60 * 60 * 1e3;
|
|
484
|
+
try {
|
|
485
|
+
const count = await clearExpiredPendingInvites(ctx, expireMs);
|
|
486
|
+
if (count > 0 && config.invite.showDetailedLog) {
|
|
487
|
+
console.log(`已自动清理 ${count} 个过期邀请`);
|
|
440
488
|
}
|
|
489
|
+
} catch (error) {
|
|
490
|
+
console.error("清理过期邀请失败:", error);
|
|
441
491
|
}
|
|
442
|
-
}, 60 * 1e3);
|
|
492
|
+
}, 60 * 60 * 1e3);
|
|
443
493
|
ctx.on("guild-request", async (session) => {
|
|
444
494
|
const raw = session.original || session.raw || session.event?._data || {};
|
|
445
495
|
const flag = raw.flag || session.flag || session.messageId;
|
|
@@ -486,7 +536,7 @@ function apply3(ctx, config) {
|
|
|
486
536
|
}
|
|
487
537
|
return;
|
|
488
538
|
}
|
|
489
|
-
|
|
539
|
+
await addPendingInvite(ctx, {
|
|
490
540
|
groupId: rawGroupId,
|
|
491
541
|
userId: rawUserId,
|
|
492
542
|
userName,
|
|
@@ -524,14 +574,15 @@ function apply3(ctx, config) {
|
|
|
524
574
|
console.warn("群邀请请求发送失败:未配置通知群且管理员私聊发送失败");
|
|
525
575
|
}
|
|
526
576
|
});
|
|
527
|
-
ctx.command("approve <groupId:string>", "同意群聊邀请"
|
|
577
|
+
ctx.command("approve <groupId:string>", "同意群聊邀请").action(async ({ session }, groupId) => {
|
|
528
578
|
if (!groupId) return "请指定群号。用法:approve <群号>";
|
|
529
579
|
if (!config.invite.adminQQs.includes(session.userId)) {
|
|
530
580
|
return "权限不足,只有管理员可以审核邀请。";
|
|
531
581
|
}
|
|
532
|
-
const inviteData =
|
|
582
|
+
const inviteData = await getPendingInvite(ctx, groupId);
|
|
533
583
|
if (!inviteData) {
|
|
534
|
-
|
|
584
|
+
const allInvites = await getAllPendingInvites(ctx);
|
|
585
|
+
return `未找到群号 ${groupId} 的待处理邀请。当前待处理邀请:${allInvites.length > 0 ? allInvites.map((i) => `${i.groupId}(${i.groupName})`).join(", ") : "无"}`;
|
|
535
586
|
}
|
|
536
587
|
try {
|
|
537
588
|
await session.bot.internal.setGroupAddRequest(inviteData.flag, "invite", true, "");
|
|
@@ -541,21 +592,22 @@ function apply3(ctx, config) {
|
|
|
541
592
|
} catch (error) {
|
|
542
593
|
console.error("通知邀请者失败:", error);
|
|
543
594
|
}
|
|
544
|
-
|
|
595
|
+
await removePendingInvite(ctx, groupId);
|
|
545
596
|
return `已同意加入群 ${groupId}(${inviteData.groupName}),邀请者:${inviteData.userName}`;
|
|
546
597
|
} catch (error) {
|
|
547
598
|
console.error("处理同意邀请失败:", error);
|
|
548
599
|
return `处理同意邀请失败: ${error.message}`;
|
|
549
600
|
}
|
|
550
601
|
});
|
|
551
|
-
ctx.command("reject <groupId:string>", "拒绝群聊邀请"
|
|
602
|
+
ctx.command("reject <groupId:string>", "拒绝群聊邀请").action(async ({ session }, groupId) => {
|
|
552
603
|
if (!groupId) return "请指定群号。用法:reject <群号>";
|
|
553
604
|
if (!config.invite.adminQQs.includes(session.userId)) {
|
|
554
605
|
return "权限不足,只有管理员可以审核邀请。";
|
|
555
606
|
}
|
|
556
|
-
const inviteData =
|
|
607
|
+
const inviteData = await getPendingInvite(ctx, groupId);
|
|
557
608
|
if (!inviteData) {
|
|
558
|
-
|
|
609
|
+
const allInvites = await getAllPendingInvites(ctx);
|
|
610
|
+
return `未找到群号 ${groupId} 的待处理邀请。当前待处理邀请:${allInvites.length > 0 ? allInvites.map((i) => `${i.groupId}(${i.groupName})`).join(", ") : "无"}`;
|
|
559
611
|
}
|
|
560
612
|
try {
|
|
561
613
|
await session.bot.internal.setGroupAddRequest(inviteData.flag, "invite", false, "已拒绝");
|
|
@@ -564,22 +616,23 @@ function apply3(ctx, config) {
|
|
|
564
616
|
} catch (error) {
|
|
565
617
|
console.error("通知邀请者失败:", error);
|
|
566
618
|
}
|
|
567
|
-
|
|
619
|
+
await removePendingInvite(ctx, groupId);
|
|
568
620
|
return `已拒绝加入群 ${groupId}(${inviteData.groupName}),邀请者:${inviteData.userName}`;
|
|
569
621
|
} catch (error) {
|
|
570
622
|
console.error("处理拒绝邀请失败:", error);
|
|
571
623
|
return `处理拒绝邀请失败: ${error.message}`;
|
|
572
624
|
}
|
|
573
625
|
});
|
|
574
|
-
ctx.command("pending
|
|
626
|
+
ctx.command("pending", "查看待处理的群聊邀请").action(async ({ session }) => {
|
|
575
627
|
if (!config.invite.adminQQs.includes(session.userId)) {
|
|
576
628
|
return "权限不足,只有管理员可以查看待处理邀请。";
|
|
577
629
|
}
|
|
578
|
-
|
|
630
|
+
const allInvites = await getAllPendingInvites(ctx);
|
|
631
|
+
if (allInvites.length === 0) {
|
|
579
632
|
return "当前没有待处理的群聊邀请。";
|
|
580
633
|
}
|
|
581
634
|
const lines = ["待处理的群聊邀请列表:"];
|
|
582
|
-
for (const
|
|
635
|
+
for (const invite of allInvites) {
|
|
583
636
|
const elapsed = Math.floor((Date.now() - invite.time) / 1e3 / 60);
|
|
584
637
|
lines.push(`- 群:${invite.groupName}(${invite.groupId})`);
|
|
585
638
|
lines.push(` 邀请者:${invite.userName}(${invite.userId})`);
|
|
@@ -673,7 +726,8 @@ __export(commands_exports, {
|
|
|
673
726
|
});
|
|
674
727
|
var name5 = "group-control-commands";
|
|
675
728
|
function apply5(ctx, config) {
|
|
676
|
-
async function viewBlacklist() {
|
|
729
|
+
async function viewBlacklist({ session }) {
|
|
730
|
+
if (!isGlobalAdmin(session, config)) return "权限不足,只有全局管理员可以执行此操作。";
|
|
677
731
|
const errorMsg = isBlacklistEnabled(config.basic);
|
|
678
732
|
if (errorMsg) return errorMsg;
|
|
679
733
|
const records = await getAllBlacklistedGuilds(ctx);
|
|
@@ -681,8 +735,9 @@ function apply5(ctx, config) {
|
|
|
681
735
|
return "黑名单列表:\n" + records.map((r) => `- ${r.guildId} (时间: ${formatDate(r.timestamp)})`).join("\n");
|
|
682
736
|
}
|
|
683
737
|
__name(viewBlacklist, "viewBlacklist");
|
|
684
|
-
ctx.command("
|
|
685
|
-
async function removeFromBlacklist({}, input) {
|
|
738
|
+
ctx.command("banlist", "查看被拉黑的群聊列表").action(viewBlacklist);
|
|
739
|
+
async function removeFromBlacklist({ session }, input) {
|
|
740
|
+
if (!isGlobalAdmin(session, config)) return "权限不足,只有全局管理员可以执行此操作。";
|
|
686
741
|
const errorMsg = isBlacklistEnabled(config.basic);
|
|
687
742
|
if (errorMsg) return errorMsg;
|
|
688
743
|
const guildId = parseGuildId(input);
|
|
@@ -691,8 +746,9 @@ function apply5(ctx, config) {
|
|
|
691
746
|
return removed ? `已移除群聊 ${guildId}` : `群聊 ${guildId} 不在黑名单中。`;
|
|
692
747
|
}
|
|
693
748
|
__name(removeFromBlacklist, "removeFromBlacklist");
|
|
694
|
-
ctx.command("
|
|
695
|
-
async function addToBlacklist({}, input) {
|
|
749
|
+
ctx.command("unban <groupId:text>", "从黑名单移除指定群聊").action(removeFromBlacklist);
|
|
750
|
+
async function addToBlacklist({ session }, input) {
|
|
751
|
+
if (!isGlobalAdmin(session, config)) return "权限不足,只有全局管理员可以执行此操作。";
|
|
696
752
|
const errorMsg = isBlacklistEnabled(config.basic);
|
|
697
753
|
if (errorMsg) return errorMsg;
|
|
698
754
|
const guildId = parseGuildId(input);
|
|
@@ -703,8 +759,9 @@ function apply5(ctx, config) {
|
|
|
703
759
|
return `已添加群聊 ${guildId} 到黑名单。`;
|
|
704
760
|
}
|
|
705
761
|
__name(addToBlacklist, "addToBlacklist");
|
|
706
|
-
ctx.command("
|
|
707
|
-
async function clearBlacklist() {
|
|
762
|
+
ctx.command("ban <groupId:text>", "手动添加群聊到黑名单").action(addToBlacklist);
|
|
763
|
+
async function clearBlacklist({ session }) {
|
|
764
|
+
if (!isGlobalAdmin(session, config)) return "权限不足,只有全局管理员可以执行此操作。";
|
|
708
765
|
const errorMsg = isBlacklistEnabled(config.basic);
|
|
709
766
|
if (errorMsg) return errorMsg;
|
|
710
767
|
const records = await getAllBlacklistedGuilds(ctx);
|
|
@@ -713,8 +770,9 @@ function apply5(ctx, config) {
|
|
|
713
770
|
return `已清空黑名单,共移除 ${records.length} 个群聊。`;
|
|
714
771
|
}
|
|
715
772
|
__name(clearBlacklist, "clearBlacklist");
|
|
716
|
-
ctx.command("
|
|
717
|
-
ctx.command("
|
|
773
|
+
ctx.command("clearban", "清空黑名单").action(clearBlacklist);
|
|
774
|
+
ctx.command("sg-add <groupId:text>", "解除指定群聊的小群人数限制").action(async ({ session }, input) => {
|
|
775
|
+
if (!isGlobalAdmin(session, config)) return "权限不足,只有全局管理员可以执行此操作。";
|
|
718
776
|
const guildId = parseGuildId(input);
|
|
719
777
|
if (!guildId) return "输入格式错误,请输入群号。";
|
|
720
778
|
const exists = await isInSmallGroupWhitelist(ctx, guildId);
|
|
@@ -722,7 +780,8 @@ function apply5(ctx, config) {
|
|
|
722
780
|
await addToSmallGroupWhitelist(ctx, guildId);
|
|
723
781
|
return `已将群聊 ${guildId} 加入小群白名单,该群不再受小群人数限制。`;
|
|
724
782
|
});
|
|
725
|
-
ctx.command("
|
|
783
|
+
ctx.command("sg-rm <groupId:text>", "恢复指定群聊的小群人数限制").action(async ({ session }, input) => {
|
|
784
|
+
if (!isGlobalAdmin(session, config)) return "权限不足,只有全局管理员可以执行此操作。";
|
|
726
785
|
const guildId = parseGuildId(input);
|
|
727
786
|
if (!guildId) return "输入格式错误,请输入群号。";
|
|
728
787
|
const exists = await isInSmallGroupWhitelist(ctx, guildId);
|
|
@@ -730,7 +789,8 @@ function apply5(ctx, config) {
|
|
|
730
789
|
await removeFromSmallGroupWhitelist(ctx, guildId);
|
|
731
790
|
return `已将群聊 ${guildId} 从小群白名单移除,该群将恢复小群人数限制。`;
|
|
732
791
|
});
|
|
733
|
-
ctx.command("
|
|
792
|
+
ctx.command("sg-list", "查看小群白名单").action(async ({ session }) => {
|
|
793
|
+
if (!isGlobalAdmin(session, config)) return "权限不足,只有全局管理员可以执行此操作。";
|
|
734
794
|
const records = await getAllSmallGroupWhitelist(ctx);
|
|
735
795
|
if (records.length === 0) return "小群白名单为空。";
|
|
736
796
|
return "小群白名单列表(以下群不受小群人数限制):\n" + records.map((r) => `- ${r.guildId}`).join("\n");
|
|
@@ -871,7 +931,8 @@ var Config = import_koishi.Schema.intersect([
|
|
|
871
931
|
inviteWaitMessage: import_koishi.Schema.string().default("已收到您的群聊邀请,正在等待管理员审核,请耐心等待。").description("发送给邀请者的等待审核提示消息"),
|
|
872
932
|
inviteRequestMessage: import_koishi.Schema.string().default("收到新的群聊邀请请求:\n群名称:{groupName}\n群号:{groupId}\n邀请者:{userName} (QQ: {userId})\n\n请管理员使用指令 approve {groupId} 同意或 reject {groupId} 拒绝。").description("发送给管理员的邀请请求消息模板,支持变量{groupName}, {groupId}, {userName}, {userId}"),
|
|
873
933
|
autoApprove: import_koishi.Schema.boolean().default(false).description("是否自动同意邀请(仅在没有指定管理员时)"),
|
|
874
|
-
showDetailedLog: import_koishi.Schema.boolean().default(false).description("是否显示详细日志")
|
|
934
|
+
showDetailedLog: import_koishi.Schema.boolean().default(false).description("是否显示详细日志"),
|
|
935
|
+
inviteExpireDays: import_koishi.Schema.number().default(3).description("邀请记录过期时间(天),超过此时间未处理的邀请将被自动清理")
|
|
875
936
|
}).description("群聊邀请审核")
|
|
876
937
|
}),
|
|
877
938
|
import_koishi.Schema.object({
|
package/lib/utils.d.ts
CHANGED
|
@@ -10,5 +10,10 @@ export declare function notifyAdmins(bot: any, config: Config, message: string):
|
|
|
10
10
|
* - builtin 模式: 检查用户是否为群管理员/群主,或在管理员QQ列表中
|
|
11
11
|
*/
|
|
12
12
|
export declare function hasPermission(session: Session, config: Config): Promise<boolean>;
|
|
13
|
+
/**
|
|
14
|
+
* 检查当前用户是否拥有全局管理权限
|
|
15
|
+
* (供独立于群聊的全局指令(如黑名单、白名单)使用)
|
|
16
|
+
*/
|
|
17
|
+
export declare function isGlobalAdmin(session: Session, config: Config): boolean;
|
|
13
18
|
/** 管理指令列表 - 这些指令始终不受 bot-off 影响 */
|
|
14
19
|
export declare const ADMIN_COMMANDS: Set<string>;
|
package/package.json
CHANGED
package/readme.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# koishi-plugin-group-control
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/koishi-plugin-group-control)
|
|
4
|
+
|
|
5
|
+
Koishi 插件,多功能群聊自管理工具。仅支持 OneBot 适配器。
|
|
6
|
+
|
|
7
|
+
> 使用 Qwen3-Coder & Gemini-3.1-Pro-Preview & Claude Sonnet 4.6 协助完成
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 功能概览
|
|
12
|
+
|
|
13
|
+
- **黑名单管理**:被踢出群后自动拉黑,下次被邀请时自动退出
|
|
14
|
+
- **小群自动退群**:加入人数不足的群时自动退出并通知管理员
|
|
15
|
+
- **合格小群通知**:未经审核被拉入人数达标的群时,通知管理员确认
|
|
16
|
+
- **群聊邀请审核**:收到邀请时暂缓加入,等待管理员 approve/reject
|
|
17
|
+
- **好友申请管理**:收到好友申请时通知管理员,或自动通过
|
|
18
|
+
- **频率控制**:限制群聊/私聊的指令及对话频率,支持指数增长屏蔽时长
|
|
19
|
+
- **Bot 开关**:按群独立开关 bot,关闭后屏蔽所有响应
|
|
20
|
+
- **被禁言通知**:bot 被禁言时可选通知管理员
|
|
21
|
+
- **权限管理**:支持 Koishi authority 或内置群管理员两种权限模式
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 配置说明
|
|
26
|
+
|
|
27
|
+
### 权限管理
|
|
28
|
+
|
|
29
|
+
| 配置项 | 默认值 | 说明 |
|
|
30
|
+
|--------|--------|------|
|
|
31
|
+
| `mode` | `builtin` | `builtin`:使用群管理员/群主权限;`koishi`:使用 Koishi authority |
|
|
32
|
+
| `koishiAuthority` | `3` | Koishi 模式下管理指令所需的最低权限等级 |
|
|
33
|
+
| `protectedCommands` | `[]` | 需要群管理员权限才能使用的自定义指令名列表 |
|
|
34
|
+
|
|
35
|
+
### 基础群组管理
|
|
36
|
+
|
|
37
|
+
**欢迎 & 退群**
|
|
38
|
+
|
|
39
|
+
| 配置项 | 默认值 | 说明 |
|
|
40
|
+
|--------|--------|------|
|
|
41
|
+
| `welcomeMessage` | `你好,我是机器人。` | 加入群聊时发送的欢迎消息 |
|
|
42
|
+
| `quitMessage` | `收到来自{userId}的指令,即将退出群聊。` | quit 指令触发后的群内提示,支持 `{userId}` |
|
|
43
|
+
| `quitCommandEnabled` | `true` | 是否启用 quit 指令 |
|
|
44
|
+
|
|
45
|
+
**黑名单**
|
|
46
|
+
|
|
47
|
+
| 配置项 | 默认值 | 说明 |
|
|
48
|
+
|--------|--------|------|
|
|
49
|
+
| `enableBlacklist` | `true` | 启用被踢出自动拉黑 |
|
|
50
|
+
| `blacklistMessage` | *(见配置)* | 被拉入黑名单群后的提示 |
|
|
51
|
+
|
|
52
|
+
**踢出通知**
|
|
53
|
+
|
|
54
|
+
| 配置项 | 默认值 | 说明 |
|
|
55
|
+
|--------|--------|------|
|
|
56
|
+
| `notifyAdminOnKick` | `true` | 被踢出时通知管理员 |
|
|
57
|
+
| `kickNotificationMessage` | *(见配置)* | 通知消息模板,支持 `{groupId}`, `{groupName}` |
|
|
58
|
+
|
|
59
|
+
**小群自动退群**
|
|
60
|
+
|
|
61
|
+
| 配置项 | 默认值 | 说明 |
|
|
62
|
+
|--------|--------|------|
|
|
63
|
+
| `smallGroupAutoQuit` | `false` | 启用小群自动退群 |
|
|
64
|
+
| `smallGroupThreshold` | `30` | 人数阈值,低于等于此值时自动退出 |
|
|
65
|
+
| `smallGroupQuitMessage` | *(见配置)* | 退群提示,支持 `{memberCount}`, `{threshold}`, `{groupName}`, `{groupId}` |
|
|
66
|
+
| `smallGroupNotifyAdmin` | `true` | 自动退群时通知管理员 |
|
|
67
|
+
| `smallGroupCheckDelay` | `3000` | 加入后延迟检测的时间(毫秒) |
|
|
68
|
+
|
|
69
|
+
**合格小群通知**
|
|
70
|
+
|
|
71
|
+
| 配置项 | 默认值 | 说明 |
|
|
72
|
+
|--------|--------|------|
|
|
73
|
+
| `smallGroupQualifiedNotifyAdmin` | `true` | 未经审核被拉入人数达标的群时通知管理员 |
|
|
74
|
+
| `smallGroupQualifiedMessage` | *(见配置)* | 通知消息模板,支持 `{groupName}`, `{groupId}`, `{memberCount}`, `{threshold}` |
|
|
75
|
+
|
|
76
|
+
**被禁言通知**
|
|
77
|
+
|
|
78
|
+
| 配置项 | 默认值 | 说明 |
|
|
79
|
+
|--------|--------|------|
|
|
80
|
+
| `notifyAdminOnMute` | `false` | bot 被禁言时通知管理员 |
|
|
81
|
+
| `muteNotificationMessage` | *(见配置)* | 通知消息模板,支持 `{groupId}`, `{groupName}`, `{operatorId}`, `{duration}` |
|
|
82
|
+
|
|
83
|
+
### 频率控制
|
|
84
|
+
|
|
85
|
+
**群聊**
|
|
86
|
+
|
|
87
|
+
| 配置项 | 默认值 | 说明 |
|
|
88
|
+
|--------|--------|------|
|
|
89
|
+
| `enabled` | `false` | 启用群聊频率控制(指令及 @ 对话均受限) |
|
|
90
|
+
| `limit` | `5` | 时间窗口内允许的最大触发次数 |
|
|
91
|
+
| `window` | `60` | 时间窗口(秒) |
|
|
92
|
+
| `warnDelay` | `30` | 警告后再次触发的时间阈值(秒),超出则进入屏蔽 |
|
|
93
|
+
| `blockDur` | `300` | 首次屏蔽的基础时长(秒) |
|
|
94
|
+
| `whitelist` | `[]` | 不受频率限制的群号列表 |
|
|
95
|
+
|
|
96
|
+
**私聊**
|
|
97
|
+
|
|
98
|
+
| 配置项 | 默认值 | 说明 |
|
|
99
|
+
|--------|--------|------|
|
|
100
|
+
| `privateEnabled` | `false` | 启用私聊频率控制 |
|
|
101
|
+
| `privateLimit` | `10` | 私聊时间窗口内允许的最大触发次数 |
|
|
102
|
+
| `privateWindow` | `60` | 私聊时间窗口(秒) |
|
|
103
|
+
| `privateWarnDelay` | `30` | 私聊警告后再次触发的时间阈值(秒) |
|
|
104
|
+
| `privateBlockDur` | `300` | 私聊首次屏蔽的基础时长(秒) |
|
|
105
|
+
| `privateWhitelist` | `[]` | 不受私聊频率限制的用户ID列表 |
|
|
106
|
+
|
|
107
|
+
**指数增长(群聊和私聊共用)**
|
|
108
|
+
|
|
109
|
+
| 配置项 | 默认值 | 说明 |
|
|
110
|
+
|--------|--------|------|
|
|
111
|
+
| `blockExpBase` | `2` | 指数增长底数,每次屏蔽时长 = `blockDur × base^(次数-1)`,设为 `1` 禁用 |
|
|
112
|
+
| `blockExpWindow` | `3600` | 指数增长重置窗口(秒),从最后一次屏蔽结束计算,超出则重置屏蔽次数 |
|
|
113
|
+
|
|
114
|
+
**提示消息**
|
|
115
|
+
|
|
116
|
+
| 配置项 | 默认值 | 说明 |
|
|
117
|
+
|--------|--------|------|
|
|
118
|
+
| `blockNotifyCooldown` | `60` | 屏蔽期间重复触发时提示消息的冷却时间(秒),避免刷屏 |
|
|
119
|
+
| `warnMsg` | `发言频率过高,请慢一点~` | 首次超限警告消息 |
|
|
120
|
+
| `blockMsg` | `发言频率过高,已被禁用 {duration} 秒。` | 进入屏蔽时的通知,支持 `{duration}` |
|
|
121
|
+
| `blockedMsg` | `暂时被禁用,还有 {time} 秒解禁。` | 屏蔽期间再次触发时的提示,支持 `{time}` |
|
|
122
|
+
|
|
123
|
+
### 好友申请管理
|
|
124
|
+
|
|
125
|
+
| 配置项 | 默认值 | 说明 |
|
|
126
|
+
|--------|--------|------|
|
|
127
|
+
| `enabled` | `false` | 启用好友申请管理 |
|
|
128
|
+
| `autoApprove` | `false` | 自动通过好友申请,否则通知管理员手动处理 |
|
|
129
|
+
| `notifyAdminOnApprove` | `true` | 自动通过时是否仍通知管理员 |
|
|
130
|
+
| `requestExpireDays` | `7` | 待处理申请的过期天数 |
|
|
131
|
+
| `requestMessage` | *(见配置)* | 通知管理员的消息模板,支持 `{userId}`, `{nickname}`, `{comment}` |
|
|
132
|
+
| `approveNotificationMessage` | *(见配置)* | 自动通过时的通知消息模板,支持 `{userId}`, `{nickname}`, `{comment}` |
|
|
133
|
+
| `approvedUserMessage` | `你的好友申请已通过审核。` | 同意申请后发给对方的消息 |
|
|
134
|
+
|
|
135
|
+
### 群聊邀请审核
|
|
136
|
+
|
|
137
|
+
| 配置项 | 默认值 | 说明 |
|
|
138
|
+
|--------|--------|------|
|
|
139
|
+
| `enabled` | `false` | 启用邀请审核 |
|
|
140
|
+
| `adminQQs` | `[]` | 管理员QQ号列表,用于权限验证及私聊通知 |
|
|
141
|
+
| `notificationGroupId` | *(空)* | 通知群号,填写后邀请请求发到此群,否则私聊管理员 |
|
|
142
|
+
| `inviteWaitMessage` | *(见配置)* | 发给邀请者的等待提示 |
|
|
143
|
+
| `inviteRequestMessage` | *(见配置)* | 发给管理员的请求消息,支持 `{groupName}`, `{groupId}`, `{userName}`, `{userId}` |
|
|
144
|
+
| `autoApprove` | `false` | 未配置管理员时自动同意邀请 |
|
|
145
|
+
| `showDetailedLog` | `false` | 显示详细日志 |
|
|
146
|
+
| `inviteExpireDays` | `3` | 邀请记录过期天数 |
|
|
147
|
+
|
|
148
|
+
### 机器人开关
|
|
149
|
+
|
|
150
|
+
| 配置项 | 默认值 | 说明 |
|
|
151
|
+
|--------|--------|------|
|
|
152
|
+
| `enabled` | `true` | 启用群聊 bot 开关功能 |
|
|
153
|
+
| `defaultState` | `true` | 默认开启状态 |
|
|
154
|
+
| `disabledMessage` | *(见配置)* | 关闭状态下被 @ 时的提示 |
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 指令列表
|
|
159
|
+
|
|
160
|
+
### 黑名单管理(需全局管理员)
|
|
161
|
+
|
|
162
|
+
| 指令 | 说明 |
|
|
163
|
+
|------|------|
|
|
164
|
+
| `banlist` | 查看黑名单列表 |
|
|
165
|
+
| `ban <群号>` | 手动添加群到黑名单 |
|
|
166
|
+
| `unban <群号>` | 从黑名单移除群 |
|
|
167
|
+
| `clearban` | 清空黑名单 |
|
|
168
|
+
|
|
169
|
+
### 小群白名单(需全局管理员)
|
|
170
|
+
|
|
171
|
+
| 指令 | 说明 |
|
|
172
|
+
|------|------|
|
|
173
|
+
| `sg-add <群号>` | 将群加入小群白名单,不受人数限制 |
|
|
174
|
+
| `sg-rm <群号>` | 从小群白名单移除群 |
|
|
175
|
+
| `sg-list` | 查看小群白名单 |
|
|
176
|
+
|
|
177
|
+
### 好友申请管理(需在 adminQQs 中)
|
|
178
|
+
|
|
179
|
+
| 指令 | 说明 |
|
|
180
|
+
|------|------|
|
|
181
|
+
| `friend-pending` | 查看待处理的好友申请列表 |
|
|
182
|
+
| `friend-approve <QQ号>` | 同意指定用户的好友申请 |
|
|
183
|
+
| `friend-reject <QQ号>` | 拒绝指定用户的好友申请 |
|
|
184
|
+
|
|
185
|
+
### 邀请审核(需在 adminQQs 中)
|
|
186
|
+
|
|
187
|
+
| 指令 | 说明 |
|
|
188
|
+
|------|------|
|
|
189
|
+
| `pending` | 查看待处理的邀请列表 |
|
|
190
|
+
| `approve <群号>` | 同意加入指定群 |
|
|
191
|
+
| `reject <群号>` | 拒绝加入指定群 |
|
|
192
|
+
|
|
193
|
+
### 群聊管理
|
|
194
|
+
|
|
195
|
+
| 指令 | 说明 | 权限 |
|
|
196
|
+
|------|------|------|
|
|
197
|
+
| `quit` | 让 bot 退出当前群聊 | 群管理员 |
|
|
198
|
+
| `bot-on` | 开启当前群的 bot | 群管理员 |
|
|
199
|
+
| `bot-off` | 关闭当前群的 bot | 群管理员 |
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 注意事项
|
|
204
|
+
|
|
205
|
+
- 本插件仅支持 **OneBot 适配器**(如 go-cqhttp、LLOneBot 等)
|
|
206
|
+
- 管理员通知依赖 `invite.adminQQs` 或 `invite.notificationGroupId` 的配置,未配置则无法收到通知
|
|
207
|
+
- 频率控制的非指令拦截(@ 对话、私聊)不影响入群欢迎等系统事件
|
|
208
|
+
- 小群合格通知仅在启用了 `smallGroupAutoQuit` 且未经 `approve` 审核通过的情况下触发
|
package/README.md
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# koishi-plugin-group-control
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/koishi-plugin-group-control)
|
|
4
|
-
|
|
5
|
-
Koishi 插件,一个多功能的群聊自管理工具。支持被踢出自动拉黑、刷屏自动屏蔽、开关控制等功能。(仅支持 OneBot 适配器)
|
|
6
|
-
|
|
7
|
-
#### 仅支持 OneBot 协议
|
|
8
|
-
|
|
9
|
-
> 使用 Qwen3-Coder & Gemini-3.1-Pro-Preview 协助完成
|