koishi-plugin-group-control 0.2.5 → 0.2.7-beta.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/README.md +3 -3
- package/lib/config.d.ts +48 -0
- package/lib/database.d.ts +40 -0
- package/lib/index.d.ts +3 -73
- package/lib/index.js +569 -311
- package/lib/modules/basic.d.ts +4 -0
- package/lib/modules/commands.d.ts +4 -0
- package/lib/modules/frequency.d.ts +4 -0
- package/lib/modules/invite.d.ts +4 -0
- package/lib/modules/switch.d.ts +4 -0
- package/lib/utils.d.ts +5 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/koishi-plugin-group-control)
|
|
4
4
|
|
|
5
|
-
Koishi
|
|
5
|
+
Koishi 插件,一个多功能的群聊自管理工具。支持被踢出自动拉黑、刷屏自动屏蔽、开关控制等功能。(仅支持 OneBot 适配器)
|
|
6
6
|
|
|
7
|
-
#### 仅支持
|
|
7
|
+
#### 仅支持 OneBot 协议
|
|
8
8
|
|
|
9
|
-
> 使用 Qwen3-Coder & Gemini-3-Pro-Preview 协助完成
|
|
9
|
+
> 使用 Qwen3-Coder & Gemini-3.1-Pro-Preview 协助完成
|
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Schema } from 'koishi';
|
|
2
|
+
export interface GroupConfig {
|
|
3
|
+
welcomeMessage: string;
|
|
4
|
+
blacklistMessage: string;
|
|
5
|
+
quitMessage: string;
|
|
6
|
+
enableBlacklist: boolean;
|
|
7
|
+
quitCommandEnabled: boolean;
|
|
8
|
+
quitCommandAuthority: number;
|
|
9
|
+
notifyAdminOnKick: boolean;
|
|
10
|
+
kickNotificationMessage: string;
|
|
11
|
+
smallGroupAutoQuit: boolean;
|
|
12
|
+
smallGroupThreshold: number;
|
|
13
|
+
smallGroupQuitMessage: string;
|
|
14
|
+
smallGroupNotifyAdmin: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface GroupInviteConfig {
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
adminQQs: string[];
|
|
19
|
+
notificationGroupId: string;
|
|
20
|
+
inviteWaitMessage: string;
|
|
21
|
+
inviteRequestMessage: string;
|
|
22
|
+
autoApprove: boolean;
|
|
23
|
+
showDetailedLog: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface FrequencyConfig {
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
limit: number;
|
|
28
|
+
window: number;
|
|
29
|
+
warnDelay: number;
|
|
30
|
+
blockDur: number;
|
|
31
|
+
warnMsg: string;
|
|
32
|
+
blockMsg: string;
|
|
33
|
+
blockedMsg: string;
|
|
34
|
+
whitelist: string[];
|
|
35
|
+
}
|
|
36
|
+
export interface BotSwitchConfig {
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
defaultState: boolean;
|
|
39
|
+
disabledMessage: string;
|
|
40
|
+
toggleAuthority: number;
|
|
41
|
+
}
|
|
42
|
+
export interface Config {
|
|
43
|
+
basic: GroupConfig;
|
|
44
|
+
frequency: FrequencyConfig;
|
|
45
|
+
invite: GroupInviteConfig;
|
|
46
|
+
botSwitch: BotSwitchConfig;
|
|
47
|
+
}
|
|
48
|
+
export declare const Config: Schema<Config>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
export interface BlacklistedGuild {
|
|
3
|
+
platform: string;
|
|
4
|
+
guildId: string;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
reason: string;
|
|
7
|
+
}
|
|
8
|
+
export interface CommandFrequencyRecord {
|
|
9
|
+
platform: string;
|
|
10
|
+
guildId: string;
|
|
11
|
+
commandCount: number;
|
|
12
|
+
lastCommandTime: number;
|
|
13
|
+
warningSent: boolean;
|
|
14
|
+
blockExpiryTime: number;
|
|
15
|
+
firstWarningTime: number;
|
|
16
|
+
}
|
|
17
|
+
export interface GroupBotStatus {
|
|
18
|
+
platform: string;
|
|
19
|
+
guildId: string;
|
|
20
|
+
botEnabled: boolean;
|
|
21
|
+
}
|
|
22
|
+
declare module 'koishi' {
|
|
23
|
+
interface Tables {
|
|
24
|
+
blacklisted_guild: BlacklistedGuild;
|
|
25
|
+
command_frequency_record: CommandFrequencyRecord;
|
|
26
|
+
group_bot_status: GroupBotStatus;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export declare const name = "group-control-database";
|
|
30
|
+
export declare function apply(ctx: Context): void;
|
|
31
|
+
export declare const BLACKLIST_PLATFORM = "onebot";
|
|
32
|
+
export declare function getBlacklistedGuild(ctx: Context, guildId: string): Promise<BlacklistedGuild[]>;
|
|
33
|
+
export declare function removeBlacklistedGuild(ctx: Context, guildId: string): Promise<import("minato").Driver.WriteResult>;
|
|
34
|
+
export declare function createBlacklistedGuild(ctx: Context, guildId: string, reason: string): Promise<BlacklistedGuild>;
|
|
35
|
+
export declare function getAllBlacklistedGuilds(ctx: Context): Promise<BlacklistedGuild[]>;
|
|
36
|
+
export declare function clearBlacklistedGuilds(ctx: Context): Promise<import("minato").Driver.WriteResult>;
|
|
37
|
+
export declare function getCommandFrequencyRecord(ctx: Context, platform: string, guildId: string): Promise<CommandFrequencyRecord>;
|
|
38
|
+
export declare function updateCommandFrequencyRecord(ctx: Context, platform: string, guildId: string, data: Partial<CommandFrequencyRecord>): Promise<void>;
|
|
39
|
+
export declare function getGroupBotStatus(ctx: Context, platform: string, guildId: string): Promise<GroupBotStatus | null>;
|
|
40
|
+
export declare function setGroupBotStatus(ctx: Context, platform: string, guildId: string, botEnabled: boolean): Promise<void>;
|
package/lib/index.d.ts
CHANGED
|
@@ -1,75 +1,5 @@
|
|
|
1
|
-
import { Context
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
import { Config } from './config';
|
|
3
|
+
export * from './config';
|
|
2
4
|
export declare const name = "group-control";
|
|
3
|
-
export interface Config {
|
|
4
|
-
welcomeMessage: string;
|
|
5
|
-
blacklistMessage: string;
|
|
6
|
-
quitMessage: string;
|
|
7
|
-
enableBlacklist: boolean;
|
|
8
|
-
quitCommandEnabled: boolean;
|
|
9
|
-
quitCommandAuthority: number;
|
|
10
|
-
enabled: boolean;
|
|
11
|
-
limit: number;
|
|
12
|
-
window: number;
|
|
13
|
-
warnDelay: number;
|
|
14
|
-
blockDur: number;
|
|
15
|
-
warnMsg: string;
|
|
16
|
-
blockMsg: string;
|
|
17
|
-
blockedMsg: string;
|
|
18
|
-
whitelist: string[];
|
|
19
|
-
}
|
|
20
|
-
export interface BlacklistedGuild {
|
|
21
|
-
platform: string;
|
|
22
|
-
guildId: string;
|
|
23
|
-
timestamp: number;
|
|
24
|
-
reason: string;
|
|
25
|
-
}
|
|
26
|
-
export interface CommandFrequencyRecord {
|
|
27
|
-
platform: string;
|
|
28
|
-
guildId: string;
|
|
29
|
-
commandCount: number;
|
|
30
|
-
lastCommandTime: number;
|
|
31
|
-
warningSent: boolean;
|
|
32
|
-
blockExpiryTime: number;
|
|
33
|
-
firstWarningTime: number;
|
|
34
|
-
}
|
|
35
|
-
declare module 'koishi' {
|
|
36
|
-
interface Tables {
|
|
37
|
-
blacklisted_guild: BlacklistedGuild;
|
|
38
|
-
command_frequency_record: CommandFrequencyRecord;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
export interface GroupConfig {
|
|
42
|
-
welcomeMessage: string;
|
|
43
|
-
blacklistMessage: string;
|
|
44
|
-
quitMessage: string;
|
|
45
|
-
enableBlacklist: boolean;
|
|
46
|
-
quitCommandEnabled: boolean;
|
|
47
|
-
quitCommandAuthority: number;
|
|
48
|
-
}
|
|
49
|
-
export interface GroupInviteConfig {
|
|
50
|
-
enabled: boolean;
|
|
51
|
-
adminQQs: string[];
|
|
52
|
-
notificationGroupId: string;
|
|
53
|
-
inviteWaitMessage: string;
|
|
54
|
-
inviteRequestMessage: string;
|
|
55
|
-
autoApprove: boolean;
|
|
56
|
-
showDetailedLog: boolean;
|
|
57
|
-
}
|
|
58
|
-
export interface FrequencyConfig {
|
|
59
|
-
enabled: boolean;
|
|
60
|
-
limit: number;
|
|
61
|
-
window: number;
|
|
62
|
-
warnDelay: number;
|
|
63
|
-
blockDur: number;
|
|
64
|
-
warnMsg: string;
|
|
65
|
-
blockMsg: string;
|
|
66
|
-
blockedMsg: string;
|
|
67
|
-
whitelist: string[];
|
|
68
|
-
}
|
|
69
|
-
export interface Config {
|
|
70
|
-
basic: GroupConfig;
|
|
71
|
-
frequency: FrequencyConfig;
|
|
72
|
-
invite: GroupInviteConfig;
|
|
73
|
-
}
|
|
74
|
-
export declare const Config: Schema<Config>;
|
|
75
5
|
export declare function apply(ctx: Context, config: Config): void;
|
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 name8 in all)
|
|
8
|
+
__defProp(target, name8, { get: all[name8], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
var __copyProps = (to, from, except, desc) => {
|
|
11
11
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -21,58 +21,51 @@ 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: () => apply7,
|
|
25
|
+
name: () => name7
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(src_exports);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
var
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (!config.enableBlacklist) return "黑名单功能未启用。";
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
__name(isBlacklistEnabled, "isBlacklistEnabled");
|
|
71
|
-
function parseGuildId(input) {
|
|
72
|
-
const match = input.trim().match(/^onebot:(\d+)$/);
|
|
73
|
-
return match ? match[1] : /^\d+$/.test(input.trim()) ? input.trim() : null;
|
|
28
|
+
|
|
29
|
+
// src/database.ts
|
|
30
|
+
var database_exports = {};
|
|
31
|
+
__export(database_exports, {
|
|
32
|
+
BLACKLIST_PLATFORM: () => BLACKLIST_PLATFORM,
|
|
33
|
+
apply: () => apply,
|
|
34
|
+
clearBlacklistedGuilds: () => clearBlacklistedGuilds,
|
|
35
|
+
createBlacklistedGuild: () => createBlacklistedGuild,
|
|
36
|
+
getAllBlacklistedGuilds: () => getAllBlacklistedGuilds,
|
|
37
|
+
getBlacklistedGuild: () => getBlacklistedGuild,
|
|
38
|
+
getCommandFrequencyRecord: () => getCommandFrequencyRecord,
|
|
39
|
+
getGroupBotStatus: () => getGroupBotStatus,
|
|
40
|
+
name: () => name,
|
|
41
|
+
removeBlacklistedGuild: () => removeBlacklistedGuild,
|
|
42
|
+
setGroupBotStatus: () => setGroupBotStatus,
|
|
43
|
+
updateCommandFrequencyRecord: () => updateCommandFrequencyRecord
|
|
44
|
+
});
|
|
45
|
+
var name = "group-control-database";
|
|
46
|
+
function apply(ctx) {
|
|
47
|
+
ctx.model.extend("blacklisted_guild", {
|
|
48
|
+
platform: "string",
|
|
49
|
+
guildId: "string",
|
|
50
|
+
timestamp: "integer",
|
|
51
|
+
reason: "string"
|
|
52
|
+
}, { primary: ["platform", "guildId"] });
|
|
53
|
+
ctx.model.extend("command_frequency_record", {
|
|
54
|
+
platform: "string",
|
|
55
|
+
guildId: "string",
|
|
56
|
+
commandCount: "integer",
|
|
57
|
+
lastCommandTime: "integer",
|
|
58
|
+
warningSent: "boolean",
|
|
59
|
+
blockExpiryTime: "integer",
|
|
60
|
+
firstWarningTime: "integer"
|
|
61
|
+
}, { primary: ["platform", "guildId"] });
|
|
62
|
+
ctx.model.extend("group_bot_status", {
|
|
63
|
+
platform: "string",
|
|
64
|
+
guildId: "string",
|
|
65
|
+
botEnabled: "boolean"
|
|
66
|
+
}, { primary: ["platform", "guildId"] });
|
|
74
67
|
}
|
|
75
|
-
__name(
|
|
68
|
+
__name(apply, "apply");
|
|
76
69
|
var BLACKLIST_PLATFORM = "onebot";
|
|
77
70
|
async function getBlacklistedGuild(ctx, guildId) {
|
|
78
71
|
return await ctx.model.get("blacklisted_guild", { platform: BLACKLIST_PLATFORM, guildId });
|
|
@@ -99,10 +92,6 @@ async function clearBlacklistedGuilds(ctx) {
|
|
|
99
92
|
return await ctx.model.remove("blacklisted_guild", { platform: BLACKLIST_PLATFORM });
|
|
100
93
|
}
|
|
101
94
|
__name(clearBlacklistedGuilds, "clearBlacklistedGuilds");
|
|
102
|
-
function formatDate(timestamp) {
|
|
103
|
-
return new Date(timestamp * 1e3).toLocaleString();
|
|
104
|
-
}
|
|
105
|
-
__name(formatDate, "formatDate");
|
|
106
95
|
async function getCommandFrequencyRecord(ctx, platform, guildId) {
|
|
107
96
|
const records = await ctx.model.get("command_frequency_record", { platform, guildId });
|
|
108
97
|
return records.length > 0 ? records[0] : null;
|
|
@@ -116,311 +105,452 @@ async function updateCommandFrequencyRecord(ctx, platform, guildId, data) {
|
|
|
116
105
|
}]);
|
|
117
106
|
}
|
|
118
107
|
__name(updateCommandFrequencyRecord, "updateCommandFrequencyRecord");
|
|
119
|
-
function
|
|
120
|
-
|
|
121
|
-
return
|
|
108
|
+
async function getGroupBotStatus(ctx, platform, guildId) {
|
|
109
|
+
const records = await ctx.model.get("group_bot_status", { platform, guildId });
|
|
110
|
+
return records.length > 0 ? records[0] : null;
|
|
122
111
|
}
|
|
123
|
-
__name(
|
|
124
|
-
function
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
112
|
+
__name(getGroupBotStatus, "getGroupBotStatus");
|
|
113
|
+
async function setGroupBotStatus(ctx, platform, guildId, botEnabled) {
|
|
114
|
+
await ctx.model.upsert("group_bot_status", [{ platform, guildId, botEnabled }]);
|
|
115
|
+
}
|
|
116
|
+
__name(setGroupBotStatus, "setGroupBotStatus");
|
|
117
|
+
|
|
118
|
+
// src/modules/basic.ts
|
|
119
|
+
var basic_exports = {};
|
|
120
|
+
__export(basic_exports, {
|
|
121
|
+
apply: () => apply2,
|
|
122
|
+
name: () => name2
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// src/utils.ts
|
|
126
|
+
function isBlacklistEnabled(config) {
|
|
127
|
+
if (!config.enableBlacklist) return "黑名单功能未启用。";
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
__name(isBlacklistEnabled, "isBlacklistEnabled");
|
|
131
|
+
function parseGuildId(input) {
|
|
132
|
+
const match = input.trim().match(/^onebot:(\d+)$/);
|
|
133
|
+
return match ? match[1] : /^\d+$/.test(input.trim()) ? input.trim() : null;
|
|
134
|
+
}
|
|
135
|
+
__name(parseGuildId, "parseGuildId");
|
|
136
|
+
function formatDate(timestamp) {
|
|
137
|
+
return new Date(timestamp * 1e3).toLocaleString();
|
|
138
|
+
}
|
|
139
|
+
__name(formatDate, "formatDate");
|
|
140
|
+
async function notifyAdmins(bot, config, message) {
|
|
141
|
+
if (config.invite.notificationGroupId) {
|
|
142
|
+
try {
|
|
143
|
+
await bot.sendMessage(config.invite.notificationGroupId, message);
|
|
144
|
+
return;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error(`发送通知到通知群 ${config.invite.notificationGroupId} 失败:`, error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (config.invite.adminQQs?.length > 0) {
|
|
150
|
+
for (const adminQQ of config.invite.adminQQs) {
|
|
151
|
+
try {
|
|
152
|
+
await bot.sendPrivateMessage(adminQQ, message);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error(`发送通知给管理员 ${adminQQ} 失败:`, error);
|
|
151
155
|
}
|
|
152
|
-
|
|
153
|
-
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
__name(notifyAdmins, "notifyAdmins");
|
|
160
|
+
|
|
161
|
+
// src/modules/basic.ts
|
|
162
|
+
var name2 = "group-control-basic";
|
|
163
|
+
function apply2(ctx, config) {
|
|
164
|
+
const quittingGuilds = /* @__PURE__ */ new Set();
|
|
165
|
+
ctx.on("guild-added", async (session) => {
|
|
166
|
+
const { guildId, platform } = session;
|
|
167
|
+
if (config.basic.enableBlacklist) {
|
|
168
|
+
const [blacklisted] = await ctx.model.get("blacklisted_guild", { platform, guildId });
|
|
169
|
+
if (blacklisted) {
|
|
170
|
+
try {
|
|
171
|
+
await session.bot.sendMessage(guildId, config.basic.blacklistMessage, platform);
|
|
172
|
+
} catch (e) {
|
|
173
|
+
}
|
|
174
|
+
quittingGuilds.add(`${platform}:${guildId}`);
|
|
175
|
+
try {
|
|
176
|
+
await session.bot.internal.setGroupLeave(parseInt(guildId));
|
|
177
|
+
} catch (e) {
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
154
180
|
}
|
|
155
|
-
|
|
181
|
+
}
|
|
182
|
+
if (config.basic.smallGroupAutoQuit) {
|
|
156
183
|
try {
|
|
157
|
-
const
|
|
158
|
-
|
|
184
|
+
const guildInfo = await session.bot.getGuild(guildId);
|
|
185
|
+
const memberCount = guildInfo?.member_count || guildInfo?.memberCount || 0;
|
|
186
|
+
if (memberCount > 0 && memberCount <= config.basic.smallGroupThreshold) {
|
|
187
|
+
const quitMsg = config.basic.smallGroupQuitMessage.replace("{memberCount}", memberCount.toString()).replace("{threshold}", config.basic.smallGroupThreshold.toString());
|
|
188
|
+
try {
|
|
189
|
+
await session.bot.sendMessage(guildId, quitMsg, platform);
|
|
190
|
+
} catch (e) {
|
|
191
|
+
}
|
|
192
|
+
if (config.basic.smallGroupNotifyAdmin) {
|
|
193
|
+
const adminMsg = `小群自动退群
|
|
194
|
+
群号:${guildId}
|
|
195
|
+
群成员数:${memberCount}人(阈值:${config.basic.smallGroupThreshold}人)
|
|
196
|
+
机器人已自动退出该群。`;
|
|
197
|
+
await notifyAdmins(session.bot, config, adminMsg);
|
|
198
|
+
}
|
|
199
|
+
quittingGuilds.add(`${platform}:${guildId}`);
|
|
200
|
+
try {
|
|
201
|
+
await session.bot.internal.setGroupLeave(parseInt(guildId));
|
|
202
|
+
} catch (e) {
|
|
203
|
+
console.error(`小群自动退群失败 (群号: ${guildId}):`, e);
|
|
204
|
+
quittingGuilds.delete(`${platform}:${guildId}`);
|
|
205
|
+
}
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
159
208
|
} catch (error) {
|
|
160
|
-
console.error(
|
|
209
|
+
console.error(`获取群信息失败 (群号: ${guildId}):`, error);
|
|
161
210
|
}
|
|
162
|
-
|
|
211
|
+
}
|
|
212
|
+
if (config.basic.welcomeMessage) {
|
|
163
213
|
try {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
214
|
+
await session.bot.sendMessage(guildId, config.basic.welcomeMessage, platform);
|
|
215
|
+
} catch (e) {
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
ctx.on("guild-removed", async (session) => {
|
|
220
|
+
const { guildId, platform } = session;
|
|
221
|
+
const quittingKey = `${platform}:${guildId}`;
|
|
222
|
+
if (quittingGuilds.has(quittingKey)) {
|
|
223
|
+
quittingGuilds.delete(quittingKey);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (config.basic.enableBlacklist) {
|
|
227
|
+
await ctx.model.upsert("blacklisted_guild", [{
|
|
228
|
+
platform,
|
|
229
|
+
guildId,
|
|
230
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
231
|
+
reason: "kicked"
|
|
232
|
+
}]);
|
|
233
|
+
}
|
|
234
|
+
if (config.basic.notifyAdminOnKick) {
|
|
235
|
+
const kickMsg = config.basic.kickNotificationMessage.replace("{groupId}", guildId);
|
|
236
|
+
await notifyAdmins(session.bot, config, kickMsg);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
if (config.basic.quitCommandEnabled) {
|
|
240
|
+
ctx.command("quit", "让机器人主动退出当前群聊", { authority: config.basic.quitCommandAuthority }).action(async ({ session }) => {
|
|
241
|
+
if (!session.guildId) return "quit 指令只能在群聊中使用。";
|
|
242
|
+
const { guildId, platform, userId } = session;
|
|
243
|
+
quittingGuilds.add(`${platform}:${guildId}`);
|
|
244
|
+
try {
|
|
245
|
+
await session.bot.sendMessage(session.guildId, config.basic.quitMessage.replace("{userId}", userId), platform);
|
|
246
|
+
} catch (e) {
|
|
168
247
|
}
|
|
169
248
|
try {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
249
|
+
await session.bot.internal.setGroupLeave(parseInt(guildId));
|
|
250
|
+
} catch (e) {
|
|
251
|
+
quittingGuilds.delete(`${platform}:${guildId}`);
|
|
252
|
+
return `退出失败: ${e.message}`;
|
|
174
253
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
254
|
+
return "";
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
__name(apply2, "apply");
|
|
259
|
+
|
|
260
|
+
// src/modules/invite.ts
|
|
261
|
+
var invite_exports = {};
|
|
262
|
+
__export(invite_exports, {
|
|
263
|
+
apply: () => apply3,
|
|
264
|
+
name: () => name3
|
|
265
|
+
});
|
|
266
|
+
var name3 = "group-control-invite";
|
|
267
|
+
function apply3(ctx, config) {
|
|
268
|
+
if (!config.invite.enabled) return;
|
|
269
|
+
const pendingInvites = /* @__PURE__ */ new Map();
|
|
270
|
+
ctx.on("guild-request", async (session) => {
|
|
271
|
+
const raw = session.original || session.raw || session.event?._data || {};
|
|
272
|
+
const flag = raw.flag || session.flag || session.messageId;
|
|
273
|
+
const rawUserId = raw.user_id ? String(raw.user_id) : session.userId;
|
|
274
|
+
const rawGroupId = raw.group_id ? String(raw.group_id) : session.guildId;
|
|
275
|
+
const { platform } = session;
|
|
276
|
+
if (!flag && config.invite.showDetailedLog) {
|
|
277
|
+
console.warn("未能提取到邀请 flag,可能导致无法处理邀请。Raw event:", JSON.stringify(raw));
|
|
278
|
+
}
|
|
279
|
+
if (config.invite.showDetailedLog) {
|
|
280
|
+
console.log(`收到群邀请事件 - 原始数据: UserID=${raw.user_id}, GroupID=${raw.group_id}, Flag=${flag}`);
|
|
281
|
+
}
|
|
282
|
+
let userName = rawUserId;
|
|
283
|
+
try {
|
|
284
|
+
const userInfo = await session.bot.getUser(rawUserId);
|
|
285
|
+
userName = userInfo?.nickname || userInfo?.name || rawUserId;
|
|
286
|
+
} catch (error) {
|
|
287
|
+
console.error("获取用户信息失败:", error);
|
|
288
|
+
}
|
|
289
|
+
let groupName = rawGroupId;
|
|
290
|
+
try {
|
|
291
|
+
const guildInfo = await session.bot.getGuild(rawGroupId);
|
|
292
|
+
groupName = guildInfo?.name || guildInfo?.group_name || rawGroupId;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.error("获取群信息失败:", error);
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
const waitMessage = config.invite.inviteWaitMessage.replace("{groupName}", groupName).replace("{groupId}", rawGroupId).replace("{userName}", userName).replace("{userId}", rawUserId);
|
|
298
|
+
await session.bot.sendPrivateMessage(rawUserId, waitMessage);
|
|
299
|
+
} catch (error) {
|
|
300
|
+
console.error(`发送等待审核提示给 ${rawUserId} 失败:`, error);
|
|
301
|
+
}
|
|
302
|
+
if (!config.invite.adminQQs || config.invite.adminQQs.length === 0) {
|
|
303
|
+
if (config.invite.autoApprove) {
|
|
304
|
+
try {
|
|
305
|
+
await session.bot.internal.setGroupAddRequest({
|
|
306
|
+
flag,
|
|
307
|
+
sub_type: "invite",
|
|
308
|
+
approve: true,
|
|
309
|
+
reason: ""
|
|
310
|
+
});
|
|
311
|
+
if (config.invite.showDetailedLog) {
|
|
312
|
+
console.log(`自动同意群聊邀请: 群号 ${rawGroupId}, 邀请者 ${rawUserId}`);
|
|
189
313
|
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.error("自动同意群聊邀请失败:", error);
|
|
190
316
|
}
|
|
191
|
-
return;
|
|
192
317
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const inviteId = `${rawGroupId}_${rawUserId}_${Date.now()}`;
|
|
321
|
+
pendingInvites.set(inviteId, {
|
|
322
|
+
groupId: rawGroupId,
|
|
323
|
+
userId: rawUserId,
|
|
324
|
+
userName,
|
|
325
|
+
time: Date.now(),
|
|
326
|
+
flag
|
|
327
|
+
});
|
|
328
|
+
const requestMessage = config.invite.inviteRequestMessage.replace("{groupName}", groupName).replace("{groupId}", rawGroupId).replace("{userName}", userName).replace("{userId}", rawUserId);
|
|
329
|
+
let requestSent = false;
|
|
330
|
+
if (config.invite.notificationGroupId) {
|
|
331
|
+
try {
|
|
332
|
+
await session.bot.sendMessage(config.invite.notificationGroupId, requestMessage);
|
|
333
|
+
requestSent = true;
|
|
334
|
+
if (config.invite.showDetailedLog) {
|
|
335
|
+
console.log(`发送邀请请求到通知群 ${config.invite.notificationGroupId}`);
|
|
336
|
+
}
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.error(`发送邀请请求到通知群 ${config.invite.notificationGroupId} 失败:`, error);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (!config.invite.notificationGroupId) {
|
|
342
|
+
for (const adminQQ of config.invite.adminQQs) {
|
|
204
343
|
try {
|
|
205
|
-
await session.bot.
|
|
344
|
+
await session.bot.sendPrivateMessage(adminQQ, requestMessage);
|
|
206
345
|
requestSent = true;
|
|
207
346
|
if (config.invite.showDetailedLog) {
|
|
208
|
-
console.log(
|
|
347
|
+
console.log(`发送邀请请求给管理员 ${adminQQ}`);
|
|
209
348
|
}
|
|
210
349
|
} catch (error) {
|
|
211
|
-
console.error(
|
|
350
|
+
console.error(`发送邀请请求给管理员 ${adminQQ} 失败:`, error);
|
|
212
351
|
}
|
|
213
352
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
353
|
+
}
|
|
354
|
+
if (!requestSent && config.invite.showDetailedLog) {
|
|
355
|
+
console.warn("群邀请请求发送失败:未配置通知群且管理员私聊发送失败");
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
ctx.on("message", async (session) => {
|
|
359
|
+
const { userId, guildId } = session;
|
|
360
|
+
if (!config.invite.adminQQs.includes(userId)) return;
|
|
361
|
+
const isNotificationGroup = config.invite.notificationGroupId && guildId === config.invite.notificationGroupId;
|
|
362
|
+
const isPrivate = !guildId;
|
|
363
|
+
if (!isNotificationGroup && !isPrivate && config.invite.notificationGroupId) return;
|
|
364
|
+
const hasQuote = session.elements.some((element) => element.type === "quote");
|
|
365
|
+
if (!hasQuote) return;
|
|
366
|
+
const textContent = session.elements.filter((element) => element.type === "text").map((element) => element.attrs?.content || "").join("").trim();
|
|
367
|
+
if (config.invite.showDetailedLog) {
|
|
368
|
+
console.log(`管理员审核回复 - 原始content: "${session.content}", 提取文本: "${textContent}"`);
|
|
369
|
+
}
|
|
370
|
+
if (!["同意", "拒绝", "accept", "reject"].includes(textContent)) return;
|
|
371
|
+
const quoteElement = session.elements.find((element) => element.type === "quote");
|
|
372
|
+
if (!quoteElement) return;
|
|
373
|
+
let quoteMessageContent = "";
|
|
374
|
+
if (session.quote?.content) {
|
|
375
|
+
quoteMessageContent = session.quote.content;
|
|
376
|
+
}
|
|
377
|
+
if (!quoteMessageContent) {
|
|
378
|
+
quoteMessageContent = quoteElement.attrs?.content || quoteElement.attrs?.text || "";
|
|
379
|
+
}
|
|
380
|
+
if (!quoteMessageContent && quoteElement.children?.length > 0) {
|
|
381
|
+
quoteMessageContent = quoteElement.children.filter((child) => child.type === "text").map((child) => child.attrs?.content || "").join("");
|
|
382
|
+
}
|
|
383
|
+
if (!quoteMessageContent) {
|
|
384
|
+
const quoteId = quoteElement.attrs?.id || session.quote?.id;
|
|
385
|
+
if (quoteId) {
|
|
386
|
+
try {
|
|
387
|
+
const channelId = guildId || session.channelId;
|
|
388
|
+
if (channelId) {
|
|
389
|
+
const originalMsg = await session.bot.getMessage(channelId, quoteId);
|
|
390
|
+
if (originalMsg?.content) {
|
|
391
|
+
quoteMessageContent = originalMsg.content;
|
|
221
392
|
}
|
|
222
|
-
}
|
|
223
|
-
|
|
393
|
+
}
|
|
394
|
+
} catch (error) {
|
|
395
|
+
if (config.invite.showDetailedLog) {
|
|
396
|
+
console.error("通过消息ID获取引用消息内容失败:", error);
|
|
224
397
|
}
|
|
225
398
|
}
|
|
226
399
|
}
|
|
227
|
-
|
|
228
|
-
|
|
400
|
+
}
|
|
401
|
+
if (config.invite.showDetailedLog) {
|
|
402
|
+
console.log(`引用消息内容: "${quoteMessageContent}"`);
|
|
403
|
+
}
|
|
404
|
+
const groupIdMatch = quoteMessageContent.match(/群号[::]\s*(\d+)/i);
|
|
405
|
+
const userIdMatch = quoteMessageContent.match(/QQ[::]\s*(\d+)/i);
|
|
406
|
+
if (groupIdMatch && userIdMatch) {
|
|
407
|
+
const extractedGroupId = groupIdMatch[1];
|
|
408
|
+
const extractedUserId = userIdMatch[1];
|
|
409
|
+
if (config.invite.showDetailedLog) {
|
|
410
|
+
console.log(`提取到群号: ${extractedGroupId}, QQ: ${extractedUserId}`);
|
|
229
411
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const isPrivate = !guildId;
|
|
236
|
-
if (!isNotificationGroup && !isPrivate && config.invite.notificationGroupId) return;
|
|
237
|
-
const hasQuote = session.elements.some((element) => element.type === "quote");
|
|
238
|
-
if (!hasQuote) return;
|
|
239
|
-
const trimmedContent = content.trim();
|
|
240
|
-
if (!["同意", "拒绝", "accept", "reject"].includes(trimmedContent)) return;
|
|
241
|
-
const quoteElement = session.elements.find((element) => element.type === "quote");
|
|
242
|
-
if (!quoteElement) return;
|
|
243
|
-
const quoteMessageContent = quoteElement.attrs.content || quoteElement.attrs.text || "";
|
|
244
|
-
const groupIdMatch = quoteMessageContent.match(/群号:(\d+)/i);
|
|
245
|
-
const userIdMatch = quoteMessageContent.match(/QQ:\s*(\d+)/i);
|
|
246
|
-
if (groupIdMatch && userIdMatch) {
|
|
247
|
-
const extractedGroupId = groupIdMatch[1];
|
|
248
|
-
const extractedUserId = userIdMatch[1];
|
|
249
|
-
let targetInviteId = null;
|
|
250
|
-
for (const [inviteId, inviteData] of pendingInvites) {
|
|
251
|
-
if (inviteData.groupId === extractedGroupId && inviteData.userId === extractedUserId) {
|
|
252
|
-
targetInviteId = inviteId;
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
412
|
+
let targetInviteId = null;
|
|
413
|
+
for (const [inviteId, inviteData] of pendingInvites) {
|
|
414
|
+
if (inviteData.groupId === extractedGroupId && inviteData.userId === extractedUserId) {
|
|
415
|
+
targetInviteId = inviteId;
|
|
416
|
+
break;
|
|
255
417
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
418
|
+
}
|
|
419
|
+
if (targetInviteId) {
|
|
420
|
+
const inviteData = pendingInvites.get(targetInviteId);
|
|
421
|
+
if (inviteData) {
|
|
422
|
+
if (textContent === "同意" || textContent === "accept") {
|
|
423
|
+
try {
|
|
424
|
+
await session.bot.internal.setGroupAddRequest({
|
|
425
|
+
flag: inviteData.flag,
|
|
426
|
+
sub_type: "invite",
|
|
427
|
+
approve: true,
|
|
428
|
+
reason: ""
|
|
429
|
+
});
|
|
430
|
+
await session.send(`已同意加入群 ${inviteData.groupId}`);
|
|
260
431
|
try {
|
|
261
|
-
await session.bot.
|
|
262
|
-
flag: inviteData.flag,
|
|
263
|
-
sub_type: "invite",
|
|
264
|
-
approve: true,
|
|
265
|
-
reason: ""
|
|
266
|
-
});
|
|
267
|
-
await session.send(`已同意加入群 ${inviteData.groupId}`);
|
|
268
|
-
try {
|
|
269
|
-
await session.bot.sendPrivateMessage(inviteData.userId, `您的群聊邀请已通过管理员审核,机器人已加入群聊。`);
|
|
270
|
-
} catch (error) {
|
|
271
|
-
console.error("通知邀请者失败:", error);
|
|
272
|
-
}
|
|
432
|
+
await session.bot.sendPrivateMessage(inviteData.userId, `您的群聊邀请已通过管理员审核,机器人已加入群聊。`);
|
|
273
433
|
} catch (error) {
|
|
274
|
-
console.error("
|
|
275
|
-
await session.send(`处理同意邀请失败: ${error.message}`);
|
|
434
|
+
console.error("通知邀请者失败:", error);
|
|
276
435
|
}
|
|
277
|
-
}
|
|
436
|
+
} catch (error) {
|
|
437
|
+
console.error("处理同意邀请失败:", error);
|
|
438
|
+
await session.send(`处理同意邀请失败: ${error.message}`);
|
|
439
|
+
}
|
|
440
|
+
} else {
|
|
441
|
+
try {
|
|
442
|
+
await session.bot.internal.setGroupAddRequest({
|
|
443
|
+
flag: inviteData.flag,
|
|
444
|
+
sub_type: "invite",
|
|
445
|
+
approve: false,
|
|
446
|
+
reason: "已拒绝"
|
|
447
|
+
});
|
|
448
|
+
await session.send(`已拒绝加入群 ${inviteData.groupId}`);
|
|
278
449
|
try {
|
|
279
|
-
await session.bot.
|
|
280
|
-
flag: inviteData.flag,
|
|
281
|
-
sub_type: "invite",
|
|
282
|
-
approve: false,
|
|
283
|
-
reason: "已拒绝"
|
|
284
|
-
});
|
|
285
|
-
await session.send(`已拒绝加入群 ${inviteData.groupId}`);
|
|
286
|
-
try {
|
|
287
|
-
await session.bot.sendPrivateMessage(inviteData.userId, `您的群聊邀请未通过管理员审核,机器人将不会加入该群聊。`);
|
|
288
|
-
} catch (error) {
|
|
289
|
-
console.error("通知邀请者失败:", error);
|
|
290
|
-
}
|
|
450
|
+
await session.bot.sendPrivateMessage(inviteData.userId, `您的群聊邀请未通过管理员审核,机器人将不会加入该群聊。`);
|
|
291
451
|
} catch (error) {
|
|
292
|
-
console.error("
|
|
293
|
-
await session.send(`处理拒绝邀请失败: ${error.message}`);
|
|
452
|
+
console.error("通知邀请者失败:", error);
|
|
294
453
|
}
|
|
454
|
+
} catch (error) {
|
|
455
|
+
console.error("处理拒绝邀请失败:", error);
|
|
456
|
+
await session.send(`处理拒绝邀请失败: ${error.message}`);
|
|
295
457
|
}
|
|
296
|
-
pendingInvites.delete(targetInviteId);
|
|
297
458
|
}
|
|
459
|
+
pendingInvites.delete(targetInviteId);
|
|
298
460
|
}
|
|
461
|
+
} else if (config.invite.showDetailedLog) {
|
|
462
|
+
console.log(`未找到匹配的待处理邀请: 群号=${extractedGroupId}, QQ=${extractedUserId}`);
|
|
463
|
+
console.log(`当前待处理邀请列表:`, Array.from(pendingInvites.entries()));
|
|
299
464
|
}
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
ctx.on("guild-added", async (session) => {
|
|
303
|
-
const { guildId, platform } = session;
|
|
304
|
-
if (!config.basic.enableBlacklist) {
|
|
305
|
-
if (config.basic.welcomeMessage) {
|
|
306
|
-
try {
|
|
307
|
-
await session.bot.sendMessage(guildId, config.basic.welcomeMessage, platform);
|
|
308
|
-
} catch (e) {
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
return;
|
|
465
|
+
} else if (config.invite.showDetailedLog) {
|
|
466
|
+
console.log(`无法从引用消息中提取群号或QQ号,引用内容: "${quoteMessageContent}"`);
|
|
312
467
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
__name(apply3, "apply");
|
|
471
|
+
|
|
472
|
+
// src/modules/frequency.ts
|
|
473
|
+
var frequency_exports = {};
|
|
474
|
+
__export(frequency_exports, {
|
|
475
|
+
apply: () => apply4,
|
|
476
|
+
name: () => name4
|
|
477
|
+
});
|
|
478
|
+
var name4 = "group-control-frequency";
|
|
479
|
+
function isCurrentlyBlocked(record) {
|
|
480
|
+
if (!record || !record.blockExpiryTime) return false;
|
|
481
|
+
return Date.now() < record.blockExpiryTime * 1e3;
|
|
482
|
+
}
|
|
483
|
+
__name(isCurrentlyBlocked, "isCurrentlyBlocked");
|
|
484
|
+
function apply4(ctx, config) {
|
|
485
|
+
if (!config.frequency.enabled) return;
|
|
486
|
+
ctx.on("command/before-execute", async (argv) => {
|
|
487
|
+
const session = argv.session;
|
|
488
|
+
if (!session.guildId || !config.frequency.enabled) return;
|
|
489
|
+
const { guildId, platform } = session;
|
|
490
|
+
if (config.frequency.whitelist && config.frequency.whitelist.includes(guildId)) return;
|
|
491
|
+
let record = await getCommandFrequencyRecord(ctx, platform, guildId);
|
|
492
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
493
|
+
const windowStart = now - config.frequency.window;
|
|
494
|
+
if (record && record.lastCommandTime < windowStart) {
|
|
495
|
+
if (record.warningSent && record.firstWarningTime > 0 && now - record.firstWarningTime <= config.frequency.warnDelay) {
|
|
496
|
+
record.commandCount = 1;
|
|
497
|
+
record.lastCommandTime = now;
|
|
498
|
+
} else if (isCurrentlyBlocked(record) && Date.now() < record.blockExpiryTime * 1e3) {
|
|
499
|
+
} else {
|
|
500
|
+
record = { platform, guildId, commandCount: 1, lastCommandTime: now, warningSent: false, blockExpiryTime: 0, firstWarningTime: 0 };
|
|
322
501
|
}
|
|
323
|
-
|
|
502
|
+
} else if (!record) {
|
|
503
|
+
record = { platform, guildId, commandCount: 1, lastCommandTime: now, warningSent: false, blockExpiryTime: 0, firstWarningTime: 0 };
|
|
504
|
+
} else {
|
|
505
|
+
record.commandCount += 1;
|
|
506
|
+
record.lastCommandTime = now;
|
|
324
507
|
}
|
|
325
|
-
if (
|
|
508
|
+
if (isCurrentlyBlocked(record)) {
|
|
326
509
|
try {
|
|
327
|
-
|
|
510
|
+
const remainingTime = Math.ceil((record.blockExpiryTime * 1e3 - Date.now()) / 1e3);
|
|
511
|
+
await session.bot.sendMessage(guildId, config.frequency.blockedMsg.replace("{time}", remainingTime.toString()), platform);
|
|
328
512
|
} catch (e) {
|
|
329
513
|
}
|
|
514
|
+
throw new Error("Blocked");
|
|
330
515
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if (quittingGuilds.has(quittingKey)) {
|
|
337
|
-
quittingGuilds.delete(quittingKey);
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
await ctx.model.upsert("blacklisted_guild", [{
|
|
341
|
-
platform,
|
|
342
|
-
guildId,
|
|
343
|
-
timestamp: Math.floor(Date.now() / 1e3),
|
|
344
|
-
reason: "kicked"
|
|
345
|
-
}]);
|
|
346
|
-
});
|
|
347
|
-
if (config.frequency.enabled) {
|
|
348
|
-
ctx.on("command/before-execute", async (argv) => {
|
|
349
|
-
const session = argv.session;
|
|
350
|
-
if (!session.guildId || !config.frequency.enabled) return;
|
|
351
|
-
const { guildId, platform } = session;
|
|
352
|
-
if (config.frequency.whitelist && config.frequency.whitelist.includes(guildId)) return;
|
|
353
|
-
let record = await getCommandFrequencyRecord(ctx, platform, guildId);
|
|
354
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
355
|
-
const windowStart = now - config.frequency.window;
|
|
356
|
-
if (record && record.lastCommandTime < windowStart) {
|
|
357
|
-
if (record.warningSent && record.firstWarningTime > 0 && now - record.firstWarningTime <= config.frequency.warnDelay) {
|
|
358
|
-
record.commandCount = 1;
|
|
359
|
-
record.lastCommandTime = now;
|
|
360
|
-
} else if (isCurrentlyBlocked(record) && Date.now() < record.blockExpiryTime * 1e3) {
|
|
361
|
-
} else {
|
|
362
|
-
record = { platform, guildId, commandCount: 1, lastCommandTime: now, warningSent: false, blockExpiryTime: 0, firstWarningTime: 0 };
|
|
516
|
+
if (record.commandCount > config.frequency.limit) {
|
|
517
|
+
if (!record.warningSent) {
|
|
518
|
+
try {
|
|
519
|
+
await session.bot.sendMessage(guildId, config.frequency.warnMsg, platform);
|
|
520
|
+
} catch (e) {
|
|
363
521
|
}
|
|
364
|
-
|
|
365
|
-
record =
|
|
366
|
-
} else {
|
|
367
|
-
record.commandCount += 1;
|
|
522
|
+
record.warningSent = true;
|
|
523
|
+
record.commandCount = 1;
|
|
368
524
|
record.lastCommandTime = now;
|
|
369
|
-
|
|
370
|
-
|
|
525
|
+
record.firstWarningTime = now;
|
|
526
|
+
await updateCommandFrequencyRecord(ctx, platform, guildId, record);
|
|
527
|
+
throw new Error("Warning");
|
|
528
|
+
} else {
|
|
529
|
+
record.blockExpiryTime = now + config.frequency.blockDur;
|
|
530
|
+
record.warningSent = false;
|
|
531
|
+
record.commandCount = 0;
|
|
532
|
+
record.firstWarningTime = 0;
|
|
533
|
+
await updateCommandFrequencyRecord(ctx, platform, guildId, record);
|
|
371
534
|
try {
|
|
372
|
-
|
|
373
|
-
await session.bot.sendMessage(guildId, config.frequency.blockedMsg.replace("{time}", remainingTime.toString()), platform);
|
|
535
|
+
await session.bot.sendMessage(guildId, config.frequency.blockMsg.replace("{duration}", config.frequency.blockDur.toString()), platform);
|
|
374
536
|
} catch (e) {
|
|
375
537
|
}
|
|
376
538
|
throw new Error("Blocked");
|
|
377
539
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
record.warningSent = false;
|
|
393
|
-
record.commandCount = 0;
|
|
394
|
-
record.firstWarningTime = 0;
|
|
395
|
-
await updateCommandFrequencyRecord(ctx, platform, guildId, record);
|
|
396
|
-
try {
|
|
397
|
-
await session.bot.sendMessage(guildId, config.frequency.blockMsg.replace("{duration}", config.frequency.blockDur.toString()), platform);
|
|
398
|
-
} catch (e) {
|
|
399
|
-
}
|
|
400
|
-
throw new Error("Blocked");
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
await updateCommandFrequencyRecord(ctx, platform, guildId, record);
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
if (config.basic.quitCommandEnabled) {
|
|
407
|
-
ctx.command("quit", "让机器人主动退出当前群聊", { authority: config.basic.quitCommandAuthority }).action(async ({ session }) => {
|
|
408
|
-
if (!session.guildId) return "quit 指令只能在群聊中使用。";
|
|
409
|
-
const { guildId, platform, userId } = session;
|
|
410
|
-
quittingGuilds.add(`${platform}:${guildId}`);
|
|
411
|
-
try {
|
|
412
|
-
await session.bot.sendMessage(session.guildId, config.basic.quitMessage.replace("{userId}", userId), platform);
|
|
413
|
-
} catch (e) {
|
|
414
|
-
}
|
|
415
|
-
try {
|
|
416
|
-
await session.bot.internal.setGroupLeave(parseInt(guildId));
|
|
417
|
-
} catch (e) {
|
|
418
|
-
quittingGuilds.delete(`${platform}:${guildId}`);
|
|
419
|
-
return `退出失败: ${e.message}`;
|
|
420
|
-
}
|
|
421
|
-
return "";
|
|
422
|
-
});
|
|
423
|
-
}
|
|
540
|
+
}
|
|
541
|
+
await updateCommandFrequencyRecord(ctx, platform, guildId, record);
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
__name(apply4, "apply");
|
|
545
|
+
|
|
546
|
+
// src/modules/commands.ts
|
|
547
|
+
var commands_exports = {};
|
|
548
|
+
__export(commands_exports, {
|
|
549
|
+
apply: () => apply5,
|
|
550
|
+
name: () => name5
|
|
551
|
+
});
|
|
552
|
+
var name5 = "group-control-commands";
|
|
553
|
+
function apply5(ctx, config) {
|
|
424
554
|
async function viewBlacklist() {
|
|
425
555
|
const errorMsg = isBlacklistEnabled(config.basic);
|
|
426
556
|
if (errorMsg) return errorMsg;
|
|
@@ -463,7 +593,135 @@ function apply(ctx, config) {
|
|
|
463
593
|
__name(clearBlacklist, "clearBlacklist");
|
|
464
594
|
ctx.command("clear-blacklist", "清空黑名单", { authority: 4 }).action(clearBlacklist);
|
|
465
595
|
}
|
|
466
|
-
__name(
|
|
596
|
+
__name(apply5, "apply");
|
|
597
|
+
|
|
598
|
+
// src/modules/switch.ts
|
|
599
|
+
var switch_exports = {};
|
|
600
|
+
__export(switch_exports, {
|
|
601
|
+
apply: () => apply6,
|
|
602
|
+
name: () => name6
|
|
603
|
+
});
|
|
604
|
+
var name6 = "group-control-switch";
|
|
605
|
+
function apply6(ctx, config) {
|
|
606
|
+
if (!config.botSwitch?.enabled) return;
|
|
607
|
+
ctx.command("bot-on", "开启机器人", { authority: config.botSwitch.toggleAuthority }).action(async ({ session }) => {
|
|
608
|
+
if (!session.guildId) return "该指令只能在群聊中使用。";
|
|
609
|
+
await setGroupBotStatus(ctx, session.platform, session.guildId, true);
|
|
610
|
+
return "机器人已在此群开启。";
|
|
611
|
+
});
|
|
612
|
+
ctx.command("bot-off", "关闭机器人", { authority: config.botSwitch.toggleAuthority }).action(async ({ session }) => {
|
|
613
|
+
if (!session.guildId) return "该指令只能在群聊中使用。";
|
|
614
|
+
await setGroupBotStatus(ctx, session.platform, session.guildId, false);
|
|
615
|
+
return "机器人已在此群关闭。";
|
|
616
|
+
});
|
|
617
|
+
ctx.on(
|
|
618
|
+
"command/before-execute",
|
|
619
|
+
async (argv) => {
|
|
620
|
+
const session = argv.session;
|
|
621
|
+
if (!session.guildId) return;
|
|
622
|
+
if (argv.command.name === "bot-on" || argv.command.name === "bot-off") {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const status = await getGroupBotStatus(ctx, session.platform, session.guildId);
|
|
626
|
+
const isBotEnabled = status ? status.botEnabled : config.botSwitch.defaultState;
|
|
627
|
+
if (!isBotEnabled) {
|
|
628
|
+
const isMentioned = session.elements?.some((e) => e.type === "at" && e.attrs.id === session.bot.userId);
|
|
629
|
+
if (isMentioned && config.botSwitch.disabledMessage) {
|
|
630
|
+
try {
|
|
631
|
+
await session.send(config.botSwitch.disabledMessage);
|
|
632
|
+
} catch (e) {
|
|
633
|
+
ctx.logger("group-control-switch").warn("发送关闭提示失败", e);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return "";
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
true
|
|
640
|
+
/* append,在其他验证之后执行 */
|
|
641
|
+
);
|
|
642
|
+
ctx.middleware(async (session, next) => {
|
|
643
|
+
if (!session.guildId) return next();
|
|
644
|
+
const status = await getGroupBotStatus(ctx, session.platform, session.guildId);
|
|
645
|
+
const isBotEnabled = status ? status.botEnabled : config.botSwitch.defaultState;
|
|
646
|
+
if (isBotEnabled) {
|
|
647
|
+
return next();
|
|
648
|
+
}
|
|
649
|
+
const isMentioned = session.elements?.some((e) => e.type === "at" && e.attrs.id === session.bot.userId);
|
|
650
|
+
if (isMentioned && config.botSwitch.disabledMessage) {
|
|
651
|
+
try {
|
|
652
|
+
await session.send(config.botSwitch.disabledMessage);
|
|
653
|
+
} catch (e) {
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return;
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
__name(apply6, "apply");
|
|
660
|
+
|
|
661
|
+
// src/config.ts
|
|
662
|
+
var import_koishi = require("koishi");
|
|
663
|
+
var Config = import_koishi.Schema.intersect([
|
|
664
|
+
import_koishi.Schema.object({
|
|
665
|
+
basic: import_koishi.Schema.object({
|
|
666
|
+
welcomeMessage: import_koishi.Schema.string().default("你好,我是机器人。").description("机器人加入群聊时发送的欢迎消息"),
|
|
667
|
+
blacklistMessage: import_koishi.Schema.string().default("此群聊已被拉黑,机器人将自动退出,请联系管理员移出黑名单。").description("被拉入黑名单群后在群内发送的提示"),
|
|
668
|
+
quitMessage: import_koishi.Schema.string().default("收到来自{userId}的指令,即将退出群聊。").description("用户发送quit指令后在群内发送的提示,支持变量{userId}"),
|
|
669
|
+
enableBlacklist: import_koishi.Schema.boolean().default(true).description('启用"被踢出自动拉黑"功能'),
|
|
670
|
+
notifyAdminOnKick: import_koishi.Schema.boolean().default(true).description("被踢出群时通知管理员(需要在群聊邀请审核中配置管理员QQ)"),
|
|
671
|
+
kickNotificationMessage: import_koishi.Schema.string().default("机器人已被踢出群聊\n群号:{groupId}\n该群已被自动加入黑名单。").description("被踢出群通知消息模板,支持变量{groupId}"),
|
|
672
|
+
smallGroupAutoQuit: import_koishi.Schema.boolean().default(false).description("启用小群自动退群功能"),
|
|
673
|
+
smallGroupThreshold: import_koishi.Schema.number().default(30).description("小群人数阈值(群成员数小于等于此值时自动退群)"),
|
|
674
|
+
smallGroupQuitMessage: import_koishi.Schema.string().default("该群人数过少({memberCount}人),不满足最低人数要求({threshold}人),机器人将自动退出。").description("小群自动退群时在群内发送的提示,支持变量{memberCount}, {threshold}"),
|
|
675
|
+
smallGroupNotifyAdmin: import_koishi.Schema.boolean().default(true).description("小群自动退群时通知管理员"),
|
|
676
|
+
quitCommandEnabled: import_koishi.Schema.boolean().default(true).description("启用quit"),
|
|
677
|
+
quitCommandAuthority: import_koishi.Schema.number().default(3).description("quit指令所需权限")
|
|
678
|
+
}).description("基础群组管理")
|
|
679
|
+
}),
|
|
680
|
+
import_koishi.Schema.object({
|
|
681
|
+
frequency: import_koishi.Schema.object({
|
|
682
|
+
enabled: import_koishi.Schema.boolean().default(false).description("启用频率控制(对所有指令生效)"),
|
|
683
|
+
limit: import_koishi.Schema.number().default(5).description("时间窗口内允许的最大指令次数"),
|
|
684
|
+
window: import_koishi.Schema.number().default(60).description("频率检测时间窗口(秒)"),
|
|
685
|
+
warnDelay: import_koishi.Schema.number().default(30).description("发出警告后,再次触发的时间阈值(秒),在此时间内再次触发则进入屏蔽状态"),
|
|
686
|
+
blockDur: import_koishi.Schema.number().default(300).description("触发频率限制后屏蔽的时长(秒)"),
|
|
687
|
+
warnMsg: import_koishi.Schema.string().default("指令频率过高,请慢一点~").description("频率过高时发送的警告消息"),
|
|
688
|
+
blockMsg: import_koishi.Schema.string().default("指令频率过高,本群指令已被禁用 {duration} 秒。").description("触发频率限制后发送的屏蔽通知消息,支持变量{duration}"),
|
|
689
|
+
blockedMsg: import_koishi.Schema.string().default("指令暂时被禁用,还有 {time} 秒解禁。").description("屏蔽期间接收到指令时的提示消息,支持变量{time}"),
|
|
690
|
+
whitelist: import_koishi.Schema.array(String).default([]).description("频率控制白名单群号列表,白名单内的群聊不受频率限制")
|
|
691
|
+
}).description("指令频率控制")
|
|
692
|
+
}),
|
|
693
|
+
import_koishi.Schema.object({
|
|
694
|
+
invite: import_koishi.Schema.object({
|
|
695
|
+
enabled: import_koishi.Schema.boolean().default(false).description("启用群聊邀请审核功能"),
|
|
696
|
+
adminQQs: import_koishi.Schema.array(String).default([]).description("管理员QQ号列表(用于权限验证)"),
|
|
697
|
+
notificationGroupId: import_koishi.Schema.string().description("通知群号(可选:若填写,邀请请求将发送到此群;若不填,则发送私聊给管理员)"),
|
|
698
|
+
inviteWaitMessage: import_koishi.Schema.string().default("已收到您的群聊邀请,正在等待管理员审核,请耐心等待。").description("发送给邀请者的等待审核提示消息"),
|
|
699
|
+
inviteRequestMessage: import_koishi.Schema.string().default('收到新的群聊邀请请求:\n群名称:{groupName}\n群号:{groupId}\n邀请者:{userName} (QQ: {userId})\n\n请管理员引用此消息回复"同意"或"拒绝"。').description("发送给管理员的邀请请求消息模板,支持变量{groupName}, {groupId}, {userName}, {userId}"),
|
|
700
|
+
autoApprove: import_koishi.Schema.boolean().default(false).description("是否自动同意邀请(仅在没有指定管理员时)"),
|
|
701
|
+
showDetailedLog: import_koishi.Schema.boolean().default(false).description("是否显示详细日志")
|
|
702
|
+
}).description("群聊邀请审核")
|
|
703
|
+
}),
|
|
704
|
+
import_koishi.Schema.object({
|
|
705
|
+
botSwitch: import_koishi.Schema.object({
|
|
706
|
+
enabled: import_koishi.Schema.boolean().default(true).description("启用独立的群聊bot开关功能"),
|
|
707
|
+
defaultState: import_koishi.Schema.boolean().default(true).description("群聊中的默认开启状态"),
|
|
708
|
+
disabledMessage: import_koishi.Schema.string().default("机器人当前在此群处于关闭状态,请使用bot-on开启。").description("机器人在关闭状态下被@时的提示消息"),
|
|
709
|
+
toggleAuthority: import_koishi.Schema.number().default(3).description("开关Bot指令(bot-on/bot-off)所需权限")
|
|
710
|
+
}).description("机器人开关控制")
|
|
711
|
+
})
|
|
712
|
+
]);
|
|
713
|
+
|
|
714
|
+
// src/index.ts
|
|
715
|
+
var name7 = "group-control";
|
|
716
|
+
function apply7(ctx, config) {
|
|
717
|
+
ctx.plugin(database_exports);
|
|
718
|
+
ctx.plugin(basic_exports, config);
|
|
719
|
+
ctx.plugin(invite_exports, config);
|
|
720
|
+
ctx.plugin(frequency_exports, config);
|
|
721
|
+
ctx.plugin(commands_exports, config);
|
|
722
|
+
ctx.plugin(switch_exports, config);
|
|
723
|
+
}
|
|
724
|
+
__name(apply7, "apply");
|
|
467
725
|
// Annotate the CommonJS export names for ESM import in node:
|
|
468
726
|
0 && (module.exports = {
|
|
469
727
|
Config,
|
package/lib/utils.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Config } from './config';
|
|
2
|
+
export declare function isBlacklistEnabled(config: Config['basic']): string | null;
|
|
3
|
+
export declare function parseGuildId(input: string): string | null;
|
|
4
|
+
export declare function formatDate(timestamp: number): string;
|
|
5
|
+
export declare function notifyAdmins(bot: any, config: Config, message: string): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-group-control",
|
|
3
|
-
"description": "Koishi
|
|
4
|
-
"version": "0.2.
|
|
3
|
+
"description": "Koishi 插件,一个多功能的群聊自管理工具。支持被踢出自动拉黑、刷屏自动屏蔽、开关控制等功能。(仅支持 OneBot 适配器)",
|
|
4
|
+
"version": "0.2.7-beta.1",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -23,4 +23,4 @@
|
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"koishi": "^4.18.7"
|
|
25
25
|
}
|
|
26
|
-
}
|
|
26
|
+
}
|