koishi-plugin-group-control 0.2.12 → 1.0.0-alpha.1
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 +27 -5
- package/lib/database.d.ts +16 -0
- package/lib/index.js +384 -93
- package/lib/modules/friend.d.ts +4 -0
- package/package.json +1 -1
- package/readme.md +208 -0
- package/README.md +0 -9
package/lib/config.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Schema } from 'koishi';
|
|
2
2
|
export interface GroupConfig {
|
|
3
3
|
welcomeMessage: string;
|
|
4
|
-
blacklistMessage: string;
|
|
5
4
|
quitMessage: string;
|
|
6
|
-
enableBlacklist: boolean;
|
|
7
5
|
quitCommandEnabled: boolean;
|
|
6
|
+
enableBlacklist: boolean;
|
|
7
|
+
blacklistMessage: string;
|
|
8
8
|
notifyAdminOnKick: boolean;
|
|
9
9
|
kickNotificationMessage: string;
|
|
10
10
|
smallGroupAutoQuit: boolean;
|
|
@@ -12,6 +12,10 @@ export interface GroupConfig {
|
|
|
12
12
|
smallGroupQuitMessage: string;
|
|
13
13
|
smallGroupNotifyAdmin: boolean;
|
|
14
14
|
smallGroupCheckDelay: number;
|
|
15
|
+
smallGroupQualifiedNotifyAdmin: boolean;
|
|
16
|
+
smallGroupQualifiedMessage: string;
|
|
17
|
+
notifyAdminOnMute: boolean;
|
|
18
|
+
muteNotificationMessage: string;
|
|
15
19
|
}
|
|
16
20
|
export interface GroupInviteConfig {
|
|
17
21
|
enabled: boolean;
|
|
@@ -29,10 +33,27 @@ export interface FrequencyConfig {
|
|
|
29
33
|
window: number;
|
|
30
34
|
warnDelay: number;
|
|
31
35
|
blockDur: number;
|
|
36
|
+
whitelist: string[];
|
|
37
|
+
privateEnabled: boolean;
|
|
38
|
+
privateLimit: number;
|
|
39
|
+
privateWindow: number;
|
|
40
|
+
privateWarnDelay: number;
|
|
41
|
+
privateBlockDur: number;
|
|
42
|
+
privateWhitelist: string[];
|
|
43
|
+
blockExpBase: number;
|
|
44
|
+
blockExpWindow: number;
|
|
45
|
+
blockNotifyCooldown: number;
|
|
32
46
|
warnMsg: string;
|
|
33
47
|
blockMsg: string;
|
|
34
48
|
blockedMsg: string;
|
|
35
|
-
|
|
49
|
+
}
|
|
50
|
+
export interface FriendConfig {
|
|
51
|
+
enabled: boolean;
|
|
52
|
+
autoApprove: boolean;
|
|
53
|
+
notifyAdminOnApprove: boolean;
|
|
54
|
+
requestExpireDays: number;
|
|
55
|
+
requestMessage: string;
|
|
56
|
+
approveNotificationMessage: string;
|
|
36
57
|
}
|
|
37
58
|
export interface BotSwitchConfig {
|
|
38
59
|
enabled: boolean;
|
|
@@ -45,10 +66,11 @@ export interface PermissionConfig {
|
|
|
45
66
|
protectedCommands: string[];
|
|
46
67
|
}
|
|
47
68
|
export interface Config {
|
|
69
|
+
permission: PermissionConfig;
|
|
48
70
|
basic: GroupConfig;
|
|
49
|
-
frequency: FrequencyConfig;
|
|
50
71
|
invite: GroupInviteConfig;
|
|
72
|
+
friend: FriendConfig;
|
|
73
|
+
frequency: FrequencyConfig;
|
|
51
74
|
botSwitch: BotSwitchConfig;
|
|
52
|
-
permission: PermissionConfig;
|
|
53
75
|
}
|
|
54
76
|
export declare const Config: Schema<Config>;
|
package/lib/database.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface CommandFrequencyRecord {
|
|
|
13
13
|
warningSent: boolean;
|
|
14
14
|
blockExpiryTime: number;
|
|
15
15
|
firstWarningTime: number;
|
|
16
|
+
blockCount: number;
|
|
17
|
+
lastBlockNotifyTime: number;
|
|
16
18
|
}
|
|
17
19
|
export interface GroupBotStatus {
|
|
18
20
|
platform: string;
|
|
@@ -32,6 +34,14 @@ export interface PendingInvite {
|
|
|
32
34
|
time: number;
|
|
33
35
|
flag: string;
|
|
34
36
|
}
|
|
37
|
+
export interface PendingFriendRequest {
|
|
38
|
+
platform: string;
|
|
39
|
+
userId: string;
|
|
40
|
+
nickname: string;
|
|
41
|
+
comment: string;
|
|
42
|
+
flag: string;
|
|
43
|
+
time: number;
|
|
44
|
+
}
|
|
35
45
|
declare module 'koishi' {
|
|
36
46
|
interface Tables {
|
|
37
47
|
blacklisted_guild: BlacklistedGuild;
|
|
@@ -39,6 +49,7 @@ declare module 'koishi' {
|
|
|
39
49
|
group_bot_status: GroupBotStatus;
|
|
40
50
|
small_group_whitelist: SmallGroupWhitelist;
|
|
41
51
|
pending_invite: PendingInvite;
|
|
52
|
+
pending_friend_request: PendingFriendRequest;
|
|
42
53
|
}
|
|
43
54
|
}
|
|
44
55
|
export declare const name = "group-control-database";
|
|
@@ -62,3 +73,8 @@ export declare function addPendingInvite(ctx: Context, inviteUser: Omit<PendingI
|
|
|
62
73
|
export declare function removePendingInvite(ctx: Context, groupId: string): Promise<void>;
|
|
63
74
|
export declare function getAllPendingInvites(ctx: Context): Promise<PendingInvite[]>;
|
|
64
75
|
export declare function clearExpiredPendingInvites(ctx: Context, expireTimeMs: number): Promise<number>;
|
|
76
|
+
export declare function getPendingFriendRequest(ctx: Context, platform: string, userId: string): Promise<PendingFriendRequest>;
|
|
77
|
+
export declare function addPendingFriendRequest(ctx: Context, platform: string, data: Omit<PendingFriendRequest, 'platform'>): Promise<void>;
|
|
78
|
+
export declare function removePendingFriendRequest(ctx: Context, platform: string, userId: string): Promise<void>;
|
|
79
|
+
export declare function getAllPendingFriendRequests(ctx: Context, platform: string): Promise<PendingFriendRequest[]>;
|
|
80
|
+
export declare function clearExpiredPendingFriendRequests(ctx: Context, platform: string, expireTimeMs: number): Promise<number>;
|
package/lib/index.js
CHANGED
|
@@ -4,8 +4,8 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
4
4
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
5
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
6
|
var __export = (target, all) => {
|
|
7
|
-
for (var
|
|
8
|
-
__defProp(target,
|
|
7
|
+
for (var name9 in all)
|
|
8
|
+
__defProp(target, name9, { get: all[name9], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
var __copyProps = (to, from, except, desc) => {
|
|
11
11
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -21,8 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
Config: () => Config,
|
|
24
|
-
apply: () =>
|
|
25
|
-
name: () =>
|
|
24
|
+
apply: () => apply8,
|
|
25
|
+
name: () => name8
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(src_exports);
|
|
28
28
|
|
|
@@ -30,23 +30,28 @@ module.exports = __toCommonJS(src_exports);
|
|
|
30
30
|
var database_exports = {};
|
|
31
31
|
__export(database_exports, {
|
|
32
32
|
BLACKLIST_PLATFORM: () => BLACKLIST_PLATFORM,
|
|
33
|
+
addPendingFriendRequest: () => addPendingFriendRequest,
|
|
33
34
|
addPendingInvite: () => addPendingInvite,
|
|
34
35
|
addToSmallGroupWhitelist: () => addToSmallGroupWhitelist,
|
|
35
36
|
apply: () => apply,
|
|
36
37
|
clearBlacklistedGuilds: () => clearBlacklistedGuilds,
|
|
38
|
+
clearExpiredPendingFriendRequests: () => clearExpiredPendingFriendRequests,
|
|
37
39
|
clearExpiredPendingInvites: () => clearExpiredPendingInvites,
|
|
38
40
|
createBlacklistedGuild: () => createBlacklistedGuild,
|
|
39
41
|
getAllBlacklistedGuilds: () => getAllBlacklistedGuilds,
|
|
42
|
+
getAllPendingFriendRequests: () => getAllPendingFriendRequests,
|
|
40
43
|
getAllPendingInvites: () => getAllPendingInvites,
|
|
41
44
|
getAllSmallGroupWhitelist: () => getAllSmallGroupWhitelist,
|
|
42
45
|
getBlacklistedGuild: () => getBlacklistedGuild,
|
|
43
46
|
getCommandFrequencyRecord: () => getCommandFrequencyRecord,
|
|
44
47
|
getGroupBotStatus: () => getGroupBotStatus,
|
|
48
|
+
getPendingFriendRequest: () => getPendingFriendRequest,
|
|
45
49
|
getPendingInvite: () => getPendingInvite,
|
|
46
50
|
isInSmallGroupWhitelist: () => isInSmallGroupWhitelist,
|
|
47
51
|
name: () => name,
|
|
48
52
|
removeBlacklistedGuild: () => removeBlacklistedGuild,
|
|
49
53
|
removeFromSmallGroupWhitelist: () => removeFromSmallGroupWhitelist,
|
|
54
|
+
removePendingFriendRequest: () => removePendingFriendRequest,
|
|
50
55
|
removePendingInvite: () => removePendingInvite,
|
|
51
56
|
setGroupBotStatus: () => setGroupBotStatus,
|
|
52
57
|
updateCommandFrequencyRecord: () => updateCommandFrequencyRecord
|
|
@@ -66,7 +71,9 @@ function apply(ctx) {
|
|
|
66
71
|
lastCommandTime: "integer",
|
|
67
72
|
warningSent: "boolean",
|
|
68
73
|
blockExpiryTime: "integer",
|
|
69
|
-
firstWarningTime: "integer"
|
|
74
|
+
firstWarningTime: "integer",
|
|
75
|
+
blockCount: "integer",
|
|
76
|
+
lastBlockNotifyTime: "integer"
|
|
70
77
|
}, { primary: ["platform", "guildId"] });
|
|
71
78
|
ctx.model.extend("group_bot_status", {
|
|
72
79
|
platform: "string",
|
|
@@ -86,6 +93,14 @@ function apply(ctx) {
|
|
|
86
93
|
time: "integer",
|
|
87
94
|
flag: "string"
|
|
88
95
|
}, { primary: ["platform", "groupId"] });
|
|
96
|
+
ctx.model.extend("pending_friend_request", {
|
|
97
|
+
platform: "string",
|
|
98
|
+
userId: "string",
|
|
99
|
+
nickname: "string",
|
|
100
|
+
comment: "string",
|
|
101
|
+
flag: "string",
|
|
102
|
+
time: "integer"
|
|
103
|
+
}, { primary: ["platform", "userId"] });
|
|
89
104
|
}
|
|
90
105
|
__name(apply, "apply");
|
|
91
106
|
var BLACKLIST_PLATFORM = "onebot";
|
|
@@ -180,6 +195,33 @@ async function clearExpiredPendingInvites(ctx, expireTimeMs) {
|
|
|
180
195
|
return expired.length;
|
|
181
196
|
}
|
|
182
197
|
__name(clearExpiredPendingInvites, "clearExpiredPendingInvites");
|
|
198
|
+
async function getPendingFriendRequest(ctx, platform, userId) {
|
|
199
|
+
const records = await ctx.model.get("pending_friend_request", { platform, userId });
|
|
200
|
+
return records.length > 0 ? records[0] : null;
|
|
201
|
+
}
|
|
202
|
+
__name(getPendingFriendRequest, "getPendingFriendRequest");
|
|
203
|
+
async function addPendingFriendRequest(ctx, platform, data) {
|
|
204
|
+
await ctx.model.upsert("pending_friend_request", [{ platform, ...data }]);
|
|
205
|
+
}
|
|
206
|
+
__name(addPendingFriendRequest, "addPendingFriendRequest");
|
|
207
|
+
async function removePendingFriendRequest(ctx, platform, userId) {
|
|
208
|
+
await ctx.model.remove("pending_friend_request", { platform, userId });
|
|
209
|
+
}
|
|
210
|
+
__name(removePendingFriendRequest, "removePendingFriendRequest");
|
|
211
|
+
async function getAllPendingFriendRequests(ctx, platform) {
|
|
212
|
+
return await ctx.model.get("pending_friend_request", { platform });
|
|
213
|
+
}
|
|
214
|
+
__name(getAllPendingFriendRequests, "getAllPendingFriendRequests");
|
|
215
|
+
async function clearExpiredPendingFriendRequests(ctx, platform, expireTimeMs) {
|
|
216
|
+
const cutoff = Date.now() - expireTimeMs;
|
|
217
|
+
const all = await ctx.model.get("pending_friend_request", { platform });
|
|
218
|
+
const expired = all.filter((r) => r.time < cutoff);
|
|
219
|
+
for (const record of expired) {
|
|
220
|
+
await ctx.model.remove("pending_friend_request", { platform, userId: record.userId });
|
|
221
|
+
}
|
|
222
|
+
return expired.length;
|
|
223
|
+
}
|
|
224
|
+
__name(clearExpiredPendingFriendRequests, "clearExpiredPendingFriendRequests");
|
|
183
225
|
|
|
184
226
|
// src/modules/basic.ts
|
|
185
227
|
var basic_exports = {};
|
|
@@ -398,6 +440,11 @@ function apply2(ctx, config) {
|
|
|
398
440
|
console.error(`小群自动退群失败 (群号: ${guildId}):`, e);
|
|
399
441
|
quittingGuilds.delete(`${platform}:${guildId}`);
|
|
400
442
|
}
|
|
443
|
+
} else if (memberCount > config.basic.smallGroupThreshold) {
|
|
444
|
+
if (config.basic.smallGroupQualifiedNotifyAdmin) {
|
|
445
|
+
const qualifiedMsg = config.basic.smallGroupQualifiedMessage.replaceAll("{groupName}", groupName).replaceAll("{groupId}", guildId).replaceAll("{memberCount}", memberCount.toString()).replaceAll("{threshold}", config.basic.smallGroupThreshold.toString());
|
|
446
|
+
await notifyAdmins(session.bot, config, qualifiedMsg);
|
|
447
|
+
}
|
|
401
448
|
}
|
|
402
449
|
} catch (error) {
|
|
403
450
|
console.error(`小群自动退群检测失败 (群号: ${guildId}):`, error);
|
|
@@ -436,6 +483,18 @@ function apply2(ctx, config) {
|
|
|
436
483
|
await notifyAdmins(session.bot, config, kickMsg);
|
|
437
484
|
}
|
|
438
485
|
});
|
|
486
|
+
if (config.basic.notifyAdminOnMute) {
|
|
487
|
+
ctx.on("guild-member-mute", async (session) => {
|
|
488
|
+
if (session.userId !== session.bot?.userId) return;
|
|
489
|
+
if (!session.duration) return;
|
|
490
|
+
const { guildId, platform } = session;
|
|
491
|
+
const operatorId = session.operatorId || "未知";
|
|
492
|
+
const duration = session.duration ?? 0;
|
|
493
|
+
const groupName = await getGroupName(session.bot, guildId);
|
|
494
|
+
const msg = config.basic.muteNotificationMessage.replaceAll("{groupId}", guildId).replaceAll("{groupName}", groupName).replaceAll("{operatorId}", operatorId).replaceAll("{duration}", duration.toString());
|
|
495
|
+
await notifyAdmins(session.bot, config, msg);
|
|
496
|
+
});
|
|
497
|
+
}
|
|
439
498
|
if (config.basic.quitCommandEnabled) {
|
|
440
499
|
const cmdOpts = {};
|
|
441
500
|
if (config.permission.mode === "koishi") {
|
|
@@ -651,70 +710,191 @@ __export(frequency_exports, {
|
|
|
651
710
|
name: () => name4
|
|
652
711
|
});
|
|
653
712
|
var name4 = "group-control-frequency";
|
|
654
|
-
|
|
713
|
+
var PRIVATE_GUILD_PREFIX = "__private__:";
|
|
714
|
+
function isBlocked(record) {
|
|
655
715
|
if (!record || !record.blockExpiryTime) return false;
|
|
656
716
|
return Date.now() < record.blockExpiryTime * 1e3;
|
|
657
717
|
}
|
|
658
|
-
__name(
|
|
659
|
-
function
|
|
660
|
-
if (
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
}
|
|
677
|
-
} else if (!record) {
|
|
678
|
-
record = { platform, guildId, commandCount: 1, lastCommandTime: now, warningSent: false, blockExpiryTime: 0, firstWarningTime: 0 };
|
|
718
|
+
__name(isBlocked, "isBlocked");
|
|
719
|
+
function calcBlockDur(baseDur, expBase, blockCount) {
|
|
720
|
+
if (expBase <= 1) return baseDur;
|
|
721
|
+
return Math.round(baseDur * Math.pow(expBase, blockCount - 1));
|
|
722
|
+
}
|
|
723
|
+
__name(calcBlockDur, "calcBlockDur");
|
|
724
|
+
function makeEmptyRecord(platform, guildId, now) {
|
|
725
|
+
return { platform, guildId, commandCount: 1, lastCommandTime: now, warningSent: false, blockExpiryTime: 0, firstWarningTime: 0, blockCount: 0, lastBlockNotifyTime: 0 };
|
|
726
|
+
}
|
|
727
|
+
__name(makeEmptyRecord, "makeEmptyRecord");
|
|
728
|
+
async function handleTrigger(ctx, platform, guildId, limit, window, warnDelay, baseDur, expBase, expWindow, notifyCooldown) {
|
|
729
|
+
let record = await getCommandFrequencyRecord(ctx, platform, guildId);
|
|
730
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
731
|
+
const windowStart = now - window;
|
|
732
|
+
if (!record) {
|
|
733
|
+
record = makeEmptyRecord(platform, guildId, now);
|
|
734
|
+
} else if (record.lastCommandTime < windowStart) {
|
|
735
|
+
if (isBlocked(record)) {
|
|
679
736
|
} else {
|
|
680
|
-
record.
|
|
737
|
+
const blockExpired = record.blockExpiryTime > 0 && now - record.blockExpiryTime > expWindow;
|
|
738
|
+
record.commandCount = 1;
|
|
681
739
|
record.lastCommandTime = now;
|
|
740
|
+
record.warningSent = false;
|
|
741
|
+
record.firstWarningTime = 0;
|
|
742
|
+
if (blockExpired) record.blockCount = 0;
|
|
682
743
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
744
|
+
} else {
|
|
745
|
+
record.commandCount += 1;
|
|
746
|
+
record.lastCommandTime = now;
|
|
747
|
+
}
|
|
748
|
+
if (isBlocked(record)) {
|
|
749
|
+
const remaining = Math.ceil((record.blockExpiryTime * 1e3 - Date.now()) / 1e3);
|
|
750
|
+
const lastNotify = record.lastBlockNotifyTime || 0;
|
|
751
|
+
if (now - lastNotify >= notifyCooldown) {
|
|
752
|
+
record.lastBlockNotifyTime = now;
|
|
753
|
+
await updateCommandFrequencyRecord(ctx, platform, guildId, record);
|
|
754
|
+
return { result: "blocked", remaining };
|
|
755
|
+
} else {
|
|
756
|
+
await updateCommandFrequencyRecord(ctx, platform, guildId, record);
|
|
757
|
+
return { result: "blocked-silent" };
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
if (record.commandCount > limit) {
|
|
761
|
+
if (!record.warningSent) {
|
|
762
|
+
record.warningSent = true;
|
|
763
|
+
record.commandCount = 1;
|
|
764
|
+
record.lastCommandTime = now;
|
|
765
|
+
record.firstWarningTime = now;
|
|
766
|
+
await updateCommandFrequencyRecord(ctx, platform, guildId, record);
|
|
767
|
+
return { result: "warn" };
|
|
768
|
+
} else {
|
|
769
|
+
record.blockCount = (record.blockCount || 0) + 1;
|
|
770
|
+
const dur = calcBlockDur(baseDur, expBase, record.blockCount);
|
|
771
|
+
record.blockExpiryTime = now + dur;
|
|
772
|
+
record.warningSent = false;
|
|
773
|
+
record.commandCount = 0;
|
|
774
|
+
record.firstWarningTime = 0;
|
|
775
|
+
record.lastBlockNotifyTime = now;
|
|
776
|
+
await updateCommandFrequencyRecord(ctx, platform, guildId, record);
|
|
777
|
+
return { result: "new-blocked", dur };
|
|
690
778
|
}
|
|
691
|
-
|
|
692
|
-
|
|
779
|
+
}
|
|
780
|
+
await updateCommandFrequencyRecord(ctx, platform, guildId, record);
|
|
781
|
+
return { result: "ok" };
|
|
782
|
+
}
|
|
783
|
+
__name(handleTrigger, "handleTrigger");
|
|
784
|
+
function isSystemSession(session) {
|
|
785
|
+
if (!session.userId) return true;
|
|
786
|
+
if (session.userId === session.bot?.userId) return true;
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
__name(isSystemSession, "isSystemSession");
|
|
790
|
+
function isUserInitiatedNonCommand(session) {
|
|
791
|
+
if (!session.content) return false;
|
|
792
|
+
const mentioned = session.elements?.some((e) => e.type === "at" && e.attrs?.id === session.bot?.userId);
|
|
793
|
+
if (mentioned) return true;
|
|
794
|
+
if (!session.guildId) return true;
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
__name(isUserInitiatedNonCommand, "isUserInitiatedNonCommand");
|
|
798
|
+
function apply4(ctx, config) {
|
|
799
|
+
const freq = config.frequency;
|
|
800
|
+
if (!freq.enabled && !freq.privateEnabled) return;
|
|
801
|
+
async function checkFrequency(session, isCommand) {
|
|
802
|
+
const isPrivate = !session.guildId;
|
|
803
|
+
const platform = session.platform;
|
|
804
|
+
if (isPrivate) {
|
|
805
|
+
if (!freq.privateEnabled) return true;
|
|
806
|
+
if (freq.privateWhitelist?.includes(session.userId)) return true;
|
|
807
|
+
const guildId = PRIVATE_GUILD_PREFIX + session.userId;
|
|
808
|
+
const r = await handleTrigger(
|
|
809
|
+
ctx,
|
|
810
|
+
platform,
|
|
811
|
+
guildId,
|
|
812
|
+
freq.privateLimit,
|
|
813
|
+
freq.privateWindow,
|
|
814
|
+
freq.privateWarnDelay,
|
|
815
|
+
freq.privateBlockDur,
|
|
816
|
+
freq.blockExpBase,
|
|
817
|
+
freq.blockExpWindow,
|
|
818
|
+
freq.blockNotifyCooldown
|
|
819
|
+
);
|
|
820
|
+
if (r.result === "ok") return true;
|
|
821
|
+
if (r.result === "warn") {
|
|
693
822
|
try {
|
|
694
|
-
await session.
|
|
823
|
+
await session.send(freq.warnMsg);
|
|
695
824
|
} catch (e) {
|
|
696
825
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
if (r.result === "new-blocked") {
|
|
829
|
+
try {
|
|
830
|
+
await session.send(freq.blockMsg.replace("{duration}", r.dur.toString()));
|
|
831
|
+
} catch (e) {
|
|
832
|
+
}
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
if (r.result === "blocked") {
|
|
836
|
+
try {
|
|
837
|
+
await session.send(freq.blockedMsg.replace("{time}", r.remaining.toString()));
|
|
838
|
+
} catch (e) {
|
|
839
|
+
}
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
return false;
|
|
843
|
+
} else {
|
|
844
|
+
if (!freq.enabled) return true;
|
|
845
|
+
if (freq.whitelist?.includes(session.guildId)) return true;
|
|
846
|
+
const { guildId } = session;
|
|
847
|
+
const r = await handleTrigger(
|
|
848
|
+
ctx,
|
|
849
|
+
platform,
|
|
850
|
+
guildId,
|
|
851
|
+
freq.limit,
|
|
852
|
+
freq.window,
|
|
853
|
+
freq.warnDelay,
|
|
854
|
+
freq.blockDur,
|
|
855
|
+
freq.blockExpBase,
|
|
856
|
+
freq.blockExpWindow,
|
|
857
|
+
freq.blockNotifyCooldown
|
|
858
|
+
);
|
|
859
|
+
if (r.result === "ok") return true;
|
|
860
|
+
if (r.result === "warn") {
|
|
861
|
+
try {
|
|
862
|
+
await session.bot.sendMessage(guildId, freq.warnMsg, platform);
|
|
863
|
+
} catch (e) {
|
|
864
|
+
}
|
|
865
|
+
return false;
|
|
866
|
+
}
|
|
867
|
+
if (r.result === "new-blocked") {
|
|
709
868
|
try {
|
|
710
|
-
await session.bot.sendMessage(guildId,
|
|
869
|
+
await session.bot.sendMessage(guildId, freq.blockMsg.replace("{duration}", r.dur.toString()), platform);
|
|
711
870
|
} catch (e) {
|
|
712
871
|
}
|
|
713
|
-
|
|
872
|
+
return false;
|
|
714
873
|
}
|
|
874
|
+
if (r.result === "blocked") {
|
|
875
|
+
try {
|
|
876
|
+
await session.bot.sendMessage(guildId, freq.blockedMsg.replace("{time}", r.remaining.toString()), platform);
|
|
877
|
+
} catch (e) {
|
|
878
|
+
}
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
881
|
+
return false;
|
|
715
882
|
}
|
|
716
|
-
|
|
883
|
+
}
|
|
884
|
+
__name(checkFrequency, "checkFrequency");
|
|
885
|
+
ctx.on("command/before-execute", async (argv) => {
|
|
886
|
+
const session = argv.session;
|
|
887
|
+
if (isSystemSession(session)) return;
|
|
888
|
+
const allowed = await checkFrequency(session, true);
|
|
889
|
+
if (!allowed) throw new Error("Blocked");
|
|
717
890
|
});
|
|
891
|
+
ctx.middleware(async (session, next) => {
|
|
892
|
+
if (isSystemSession(session)) return next();
|
|
893
|
+
if (!isUserInitiatedNonCommand(session)) return next();
|
|
894
|
+
const allowed = await checkFrequency(session, false);
|
|
895
|
+
if (!allowed) return;
|
|
896
|
+
return next();
|
|
897
|
+
}, true);
|
|
718
898
|
}
|
|
719
899
|
__name(apply4, "apply");
|
|
720
900
|
|
|
@@ -881,6 +1061,93 @@ function apply6(ctx, config) {
|
|
|
881
1061
|
}
|
|
882
1062
|
__name(apply6, "apply");
|
|
883
1063
|
|
|
1064
|
+
// src/modules/friend.ts
|
|
1065
|
+
var friend_exports = {};
|
|
1066
|
+
__export(friend_exports, {
|
|
1067
|
+
apply: () => apply7,
|
|
1068
|
+
name: () => name7
|
|
1069
|
+
});
|
|
1070
|
+
var name7 = "group-control-friend";
|
|
1071
|
+
function apply7(ctx, config) {
|
|
1072
|
+
if (!config.friend.enabled) return;
|
|
1073
|
+
setInterval(async () => {
|
|
1074
|
+
const expireMs = config.friend.requestExpireDays * 24 * 60 * 60 * 1e3;
|
|
1075
|
+
try {
|
|
1076
|
+
for (const bot of ctx.bots) {
|
|
1077
|
+
await clearExpiredPendingFriendRequests(ctx, bot.platform, expireMs);
|
|
1078
|
+
}
|
|
1079
|
+
} catch (e) {
|
|
1080
|
+
}
|
|
1081
|
+
}, 60 * 60 * 1e3);
|
|
1082
|
+
ctx.on("friend-request", async (session) => {
|
|
1083
|
+
const raw = session.original || session.raw || session.event?._data || {};
|
|
1084
|
+
const flag = raw.flag || session.flag || session.messageId;
|
|
1085
|
+
const userId = raw.user_id ? String(raw.user_id) : session.userId;
|
|
1086
|
+
const comment = raw.comment || session.comment || "";
|
|
1087
|
+
const { platform } = session;
|
|
1088
|
+
let nickname = userId;
|
|
1089
|
+
try {
|
|
1090
|
+
const info = await session.bot.internal?.getStrangerInfo?.(parseInt(userId));
|
|
1091
|
+
nickname = info?.nickname || nickname;
|
|
1092
|
+
} catch (e) {
|
|
1093
|
+
}
|
|
1094
|
+
if (config.friend.autoApprove) {
|
|
1095
|
+
try {
|
|
1096
|
+
await session.bot.internal?.setFriendAddRequest?.(flag, true, "");
|
|
1097
|
+
if (config.friend.notifyAdminOnApprove) {
|
|
1098
|
+
const msg2 = config.friend.approveNotificationMessage.replaceAll("{userId}", userId).replaceAll("{nickname}", nickname).replaceAll("{comment}", comment);
|
|
1099
|
+
await notifyAdmins(session.bot, config, msg2);
|
|
1100
|
+
}
|
|
1101
|
+
} catch (e) {
|
|
1102
|
+
ctx.logger("group-control-friend").warn("自动通过好友申请失败", e);
|
|
1103
|
+
}
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
await addPendingFriendRequest(ctx, platform, { userId, nickname, comment, flag, time: Date.now() });
|
|
1107
|
+
const msg = config.friend.requestMessage.replaceAll("{userId}", userId).replaceAll("{nickname}", nickname).replaceAll("{comment}", comment);
|
|
1108
|
+
await notifyAdmins(session.bot, config, msg);
|
|
1109
|
+
});
|
|
1110
|
+
ctx.command("friend-pending", "查看待处理的好友申请").action(async ({ session }) => {
|
|
1111
|
+
if (!config.invite.adminQQs.includes(session.userId)) return "权限不足。";
|
|
1112
|
+
const all = await getAllPendingFriendRequests(ctx, session.platform);
|
|
1113
|
+
if (all.length === 0) return "当前没有待处理的好友申请。";
|
|
1114
|
+
const lines = ["待处理好友申请列表:"];
|
|
1115
|
+
for (const r of all) {
|
|
1116
|
+
const elapsed = Math.floor((Date.now() - r.time) / 1e3 / 60);
|
|
1117
|
+
lines.push(`- ${r.nickname}(${r.userId})附言:${r.comment || "无"} · ${elapsed} 分钟前`);
|
|
1118
|
+
lines.push(` 同意:friend-approve ${r.userId} | 拒绝:friend-reject ${r.userId}`);
|
|
1119
|
+
}
|
|
1120
|
+
return lines.join("\n");
|
|
1121
|
+
});
|
|
1122
|
+
ctx.command("friend-approve <userId:string>", "同意好友申请").action(async ({ session }, userId) => {
|
|
1123
|
+
if (!config.invite.adminQQs.includes(session.userId)) return "权限不足。";
|
|
1124
|
+
if (!userId) return "请指定QQ号。用法:friend-approve <QQ号>";
|
|
1125
|
+
const record = await getPendingFriendRequest(ctx, session.platform, userId);
|
|
1126
|
+
if (!record) return `未找到来自 ${userId} 的待处理好友申请。`;
|
|
1127
|
+
try {
|
|
1128
|
+
await session.bot.internal?.setFriendAddRequest?.(record.flag, true, "");
|
|
1129
|
+
await removePendingFriendRequest(ctx, session.platform, userId);
|
|
1130
|
+
return `已同意 ${record.nickname}(${userId})的好友申请。`;
|
|
1131
|
+
} catch (e) {
|
|
1132
|
+
return `处理失败:${e.message}`;
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
ctx.command("friend-reject <userId:string>", "拒绝好友申请").action(async ({ session }, userId) => {
|
|
1136
|
+
if (!config.invite.adminQQs.includes(session.userId)) return "权限不足。";
|
|
1137
|
+
if (!userId) return "请指定QQ号。用法:friend-reject <QQ号>";
|
|
1138
|
+
const record = await getPendingFriendRequest(ctx, session.platform, userId);
|
|
1139
|
+
if (!record) return `未找到来自 ${userId} 的待处理好友申请。`;
|
|
1140
|
+
try {
|
|
1141
|
+
await session.bot.internal?.setFriendAddRequest?.(record.flag, false, "");
|
|
1142
|
+
await removePendingFriendRequest(ctx, session.platform, userId);
|
|
1143
|
+
return `已拒绝 ${record.nickname}(${userId})的好友申请。`;
|
|
1144
|
+
} catch (e) {
|
|
1145
|
+
return `处理失败:${e.message}`;
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
__name(apply7, "apply");
|
|
1150
|
+
|
|
884
1151
|
// src/config.ts
|
|
885
1152
|
var import_koishi = require("koishi");
|
|
886
1153
|
var Config = import_koishi.Schema.intersect([
|
|
@@ -890,71 +1157,95 @@ var Config = import_koishi.Schema.intersect([
|
|
|
890
1157
|
import_koishi.Schema.const("koishi").description("使用 Koishi 自带权限系统 (authority)"),
|
|
891
1158
|
import_koishi.Schema.const("builtin").description("使用插件内置权限管理 (群管理员/群主)")
|
|
892
1159
|
]).default("builtin").description("权限管理模式"),
|
|
893
|
-
koishiAuthority: import_koishi.Schema.number().default(3).description("Koishi
|
|
894
|
-
protectedCommands: import_koishi.Schema.array(String).default([]).description("
|
|
1160
|
+
koishiAuthority: import_koishi.Schema.number().default(3).description("Koishi 模式下管理指令所需的最低权限等级"),
|
|
1161
|
+
protectedCommands: import_koishi.Schema.array(String).default([]).description("需要群管理员权限才能使用的自定义指令名列表")
|
|
895
1162
|
}).description("权限管理")
|
|
896
1163
|
}),
|
|
897
1164
|
import_koishi.Schema.object({
|
|
898
1165
|
basic: import_koishi.Schema.object({
|
|
899
|
-
welcomeMessage: import_koishi.Schema.string().default("你好,我是机器人。").description("
|
|
900
|
-
|
|
901
|
-
quitMessage: import_koishi.Schema.string().default("收到来自{userId}的指令,即将退出群聊。").description("
|
|
902
|
-
enableBlacklist: import_koishi.Schema.boolean().default(true).description(
|
|
903
|
-
|
|
904
|
-
notifyAdminOnKick: import_koishi.Schema.boolean().default(true).description("
|
|
905
|
-
kickNotificationMessage: import_koishi.Schema.string().default("机器人已被踢出群聊\n群名称:{groupName}\n群号:{groupId}\n该群已被自动加入黑名单。").description("
|
|
906
|
-
smallGroupAutoQuit: import_koishi.Schema.boolean().default(false).description("
|
|
907
|
-
smallGroupThreshold: import_koishi.Schema.number().default(30).description("
|
|
908
|
-
|
|
1166
|
+
welcomeMessage: import_koishi.Schema.string().default("你好,我是机器人。").description("加入群聊时发送的欢迎消息"),
|
|
1167
|
+
quitCommandEnabled: import_koishi.Schema.boolean().default(true).description("启用 quit 指令"),
|
|
1168
|
+
quitMessage: import_koishi.Schema.string().default("收到来自{userId}的指令,即将退出群聊。").description("quit 指令触发后的群内提示,支持变量 {userId}"),
|
|
1169
|
+
enableBlacklist: import_koishi.Schema.boolean().default(true).description("启用被踢出自动拉黑"),
|
|
1170
|
+
blacklistMessage: import_koishi.Schema.string().default("此群聊已被拉黑,机器人将自动退出,请联系管理员移出黑名单。").description("被拉入黑名单群后的提示"),
|
|
1171
|
+
notifyAdminOnKick: import_koishi.Schema.boolean().default(true).description("被踢出群时通知管理员"),
|
|
1172
|
+
kickNotificationMessage: import_koishi.Schema.string().default("机器人已被踢出群聊\n群名称:{groupName}\n群号:{groupId}\n该群已被自动加入黑名单。").description("被踢出群通知模板,支持变量 {groupId}, {groupName}"),
|
|
1173
|
+
smallGroupAutoQuit: import_koishi.Schema.boolean().default(false).description("启用小群自动退群"),
|
|
1174
|
+
smallGroupThreshold: import_koishi.Schema.number().default(30).description("小群人数阈值(低于等于此值时自动退群)"),
|
|
1175
|
+
smallGroupCheckDelay: import_koishi.Schema.number().default(3e3).description("加入后延迟检测时间(毫秒)"),
|
|
1176
|
+
smallGroupQuitMessage: import_koishi.Schema.string().default("该群人数过少({memberCount}人),不满足最低人数要求({threshold}人),机器人将自动退出。").description("小群退群提示,支持变量 {memberCount}, {threshold}, {groupName}, {groupId}"),
|
|
909
1177
|
smallGroupNotifyAdmin: import_koishi.Schema.boolean().default(true).description("小群自动退群时通知管理员"),
|
|
910
|
-
|
|
1178
|
+
smallGroupQualifiedNotifyAdmin: import_koishi.Schema.boolean().default(true).description("未经审核被拉入人数达标的群时通知管理员"),
|
|
1179
|
+
smallGroupQualifiedMessage: import_koishi.Schema.string().default("机器人被未经审核地拉入群聊\n群名称:{groupName}\n群号:{groupId}\n当前人数:{memberCount}人(阈值:{threshold}人)\n请确认是否保留。").description("合格小群通知模板,支持变量 {groupName}, {groupId}, {memberCount}, {threshold}"),
|
|
1180
|
+
notifyAdminOnMute: import_koishi.Schema.boolean().default(false).description("机器人被禁言时通知管理员"),
|
|
1181
|
+
muteNotificationMessage: import_koishi.Schema.string().default("机器人在群聊中被禁言\n群名称:{groupName}\n群号:{groupId}\n操作者:{operatorId}\n禁言时长:{duration}秒").description("被禁言通知模板,支持变量 {groupId}, {groupName}, {operatorId}, {duration}")
|
|
911
1182
|
}).description("基础群组管理")
|
|
912
1183
|
}),
|
|
913
|
-
import_koishi.Schema.object({
|
|
914
|
-
frequency: import_koishi.Schema.object({
|
|
915
|
-
enabled: import_koishi.Schema.boolean().default(false).description("启用频率控制(对所有指令生效)"),
|
|
916
|
-
limit: import_koishi.Schema.number().default(5).description("时间窗口内允许的最大指令次数"),
|
|
917
|
-
window: import_koishi.Schema.number().default(60).description("频率检测时间窗口(秒)"),
|
|
918
|
-
warnDelay: import_koishi.Schema.number().default(30).description("发出警告后,再次触发的时间阈值(秒),在此时间内再次触发则进入屏蔽状态"),
|
|
919
|
-
blockDur: import_koishi.Schema.number().default(300).description("触发频率限制后屏蔽的时长(秒)"),
|
|
920
|
-
warnMsg: import_koishi.Schema.string().default("指令频率过高,请慢一点~").description("频率过高时发送的警告消息"),
|
|
921
|
-
blockMsg: import_koishi.Schema.string().default("指令频率过高,本群指令已被禁用 {duration} 秒。").description("触发频率限制后发送的屏蔽通知消息,支持变量{duration}"),
|
|
922
|
-
blockedMsg: import_koishi.Schema.string().default("指令暂时被禁用,还有 {time} 秒解禁。").description("屏蔽期间接收到指令时的提示消息,支持变量{time}"),
|
|
923
|
-
whitelist: import_koishi.Schema.array(String).default([]).description("频率控制白名单群号列表,白名单内的群聊不受频率限制")
|
|
924
|
-
}).description("指令频率控制")
|
|
925
|
-
}),
|
|
926
1184
|
import_koishi.Schema.object({
|
|
927
1185
|
invite: import_koishi.Schema.object({
|
|
928
|
-
enabled: import_koishi.Schema.boolean().default(false).description("
|
|
929
|
-
adminQQs: import_koishi.Schema.array(String).default([]).description("管理员QQ
|
|
930
|
-
notificationGroupId: import_koishi.Schema.string().description("
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1186
|
+
enabled: import_koishi.Schema.boolean().default(false).description("启用群聊邀请审核"),
|
|
1187
|
+
adminQQs: import_koishi.Schema.array(String).default([]).description("管理员QQ号列表(权限验证及通知)"),
|
|
1188
|
+
notificationGroupId: import_koishi.Schema.string().description("通知群号(填写后发到此群,否则私聊管理员)"),
|
|
1189
|
+
autoApprove: import_koishi.Schema.boolean().default(false).description("自动同意邀请(仅在未指定管理员时生效)"),
|
|
1190
|
+
inviteWaitMessage: import_koishi.Schema.string().default("已收到您的群聊邀请,正在等待管理员审核,请耐心等待。").description("发给邀请者的等待提示"),
|
|
1191
|
+
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}"),
|
|
1192
|
+
inviteExpireDays: import_koishi.Schema.number().default(3).description("邀请记录过期天数"),
|
|
1193
|
+
showDetailedLog: import_koishi.Schema.boolean().default(false).description("显示详细日志")
|
|
936
1194
|
}).description("群聊邀请审核")
|
|
937
1195
|
}),
|
|
1196
|
+
import_koishi.Schema.object({
|
|
1197
|
+
friend: import_koishi.Schema.object({
|
|
1198
|
+
enabled: import_koishi.Schema.boolean().default(false).description("启用好友申请管理"),
|
|
1199
|
+
autoApprove: import_koishi.Schema.boolean().default(false).description("自动通过好友申请(否则通知管理员手动处理)"),
|
|
1200
|
+
notifyAdminOnApprove: import_koishi.Schema.boolean().default(true).description("自动通过时是否仍通知管理员"),
|
|
1201
|
+
requestExpireDays: import_koishi.Schema.number().default(7).description("待处理申请的过期天数"),
|
|
1202
|
+
requestMessage: import_koishi.Schema.string().default("收到新的好友申请\nQQ:{userId}\n昵称:{nickname}\n附言:{comment}\n\n使用 friend-approve {userId} 同意或 friend-reject {userId} 拒绝。").description("通知管理员的消息模板,支持变量 {userId}, {nickname}, {comment}"),
|
|
1203
|
+
approveNotificationMessage: import_koishi.Schema.string().default("已自动通过好友申请\nQQ:{userId}\n昵称:{nickname}\n附言:{comment}").description("自动通过时的通知模板,支持变量 {userId}, {nickname}, {comment}")
|
|
1204
|
+
}).description("好友申请管理")
|
|
1205
|
+
}),
|
|
1206
|
+
import_koishi.Schema.object({
|
|
1207
|
+
frequency: import_koishi.Schema.object({
|
|
1208
|
+
enabled: import_koishi.Schema.boolean().default(false).description("启用群聊频率控制(指令及 @ 对话均受限)"),
|
|
1209
|
+
limit: import_koishi.Schema.number().default(5).description("群聊:时间窗口内允许的最大触发次数"),
|
|
1210
|
+
window: import_koishi.Schema.number().default(60).description("群聊:时间窗口(秒)"),
|
|
1211
|
+
warnDelay: import_koishi.Schema.number().default(30).description("群聊:警告后再次触发的时间阈值(秒),超出则进入屏蔽"),
|
|
1212
|
+
blockDur: import_koishi.Schema.number().default(300).description("群聊:首次屏蔽的基础时长(秒)"),
|
|
1213
|
+
whitelist: import_koishi.Schema.array(String).default([]).description("群聊:不受频率限制的群号列表"),
|
|
1214
|
+
privateEnabled: import_koishi.Schema.boolean().default(false).description("启用私聊频率控制"),
|
|
1215
|
+
privateLimit: import_koishi.Schema.number().default(10).description("私聊:时间窗口内允许的最大触发次数"),
|
|
1216
|
+
privateWindow: import_koishi.Schema.number().default(60).description("私聊:时间窗口(秒)"),
|
|
1217
|
+
privateWarnDelay: import_koishi.Schema.number().default(30).description("私聊:警告后再次触发的时间阈值(秒)"),
|
|
1218
|
+
privateBlockDur: import_koishi.Schema.number().default(300).description("私聊:首次屏蔽的基础时长(秒)"),
|
|
1219
|
+
privateWhitelist: import_koishi.Schema.array(String).default([]).description("私聊:不受频率限制的用户ID列表"),
|
|
1220
|
+
blockExpBase: import_koishi.Schema.number().default(2).description("屏蔽时长指数增长底数(时长 = blockDur × base^(次数-1)),设为 1 禁用"),
|
|
1221
|
+
blockExpWindow: import_koishi.Schema.number().default(3600).description("指数增长重置窗口(秒),从最后一次屏蔽结束计算,超出则重置次数"),
|
|
1222
|
+
blockNotifyCooldown: import_koishi.Schema.number().default(60).description("屏蔽期间提示消息的冷却时间(秒),避免刷屏"),
|
|
1223
|
+
warnMsg: import_koishi.Schema.string().default("发言频率过高,请慢一点~").description("首次超限警告消息"),
|
|
1224
|
+
blockMsg: import_koishi.Schema.string().default("发言频率过高,已被禁用 {duration} 秒。").description("进入屏蔽时的通知,支持变量 {duration}"),
|
|
1225
|
+
blockedMsg: import_koishi.Schema.string().default("暂时被禁用,还有 {time} 秒解禁。").description("屏蔽期间再次触发时的提示,支持变量 {time}")
|
|
1226
|
+
}).description("频率控制")
|
|
1227
|
+
}),
|
|
938
1228
|
import_koishi.Schema.object({
|
|
939
1229
|
botSwitch: import_koishi.Schema.object({
|
|
940
|
-
enabled: import_koishi.Schema.boolean().default(true).description("
|
|
941
|
-
defaultState: import_koishi.Schema.boolean().default(true).description("
|
|
942
|
-
disabledMessage: import_koishi.Schema.string().default("机器人当前在此群处于关闭状态,请使用bot-on开启。").description("
|
|
1230
|
+
enabled: import_koishi.Schema.boolean().default(true).description("启用群聊 bot 开关"),
|
|
1231
|
+
defaultState: import_koishi.Schema.boolean().default(true).description("默认开启状态"),
|
|
1232
|
+
disabledMessage: import_koishi.Schema.string().default("机器人当前在此群处于关闭状态,请使用 bot-on 开启。").description("关闭状态下被 @ 时的提示")
|
|
943
1233
|
}).description("机器人开关控制")
|
|
944
1234
|
})
|
|
945
1235
|
]);
|
|
946
1236
|
|
|
947
1237
|
// src/index.ts
|
|
948
|
-
var
|
|
949
|
-
function
|
|
1238
|
+
var name8 = "group-control";
|
|
1239
|
+
function apply8(ctx, config) {
|
|
950
1240
|
ctx.plugin(database_exports);
|
|
951
1241
|
ctx.plugin(basic_exports, config);
|
|
952
1242
|
ctx.plugin(invite_exports, config);
|
|
953
1243
|
ctx.plugin(frequency_exports, config);
|
|
954
1244
|
ctx.plugin(commands_exports, config);
|
|
955
1245
|
ctx.plugin(switch_exports, config);
|
|
1246
|
+
ctx.plugin(friend_exports, config);
|
|
956
1247
|
}
|
|
957
|
-
__name(
|
|
1248
|
+
__name(apply8, "apply");
|
|
958
1249
|
// Annotate the CommonJS export names for ESM import in node:
|
|
959
1250
|
0 && (module.exports = {
|
|
960
1251
|
Config,
|
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 协助完成
|