koishi-plugin-group-verification 1.0.16 → 1.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +52 -0
- package/lib/index.js +327 -160
- package/package.json +1 -1
- package/readme.md +2 -0
- package/src/index.ts +460 -248
package/lib/index.d.ts
CHANGED
|
@@ -43,4 +43,56 @@ export interface Config {
|
|
|
43
43
|
}
|
|
44
44
|
export declare const Config: Schema<Config>;
|
|
45
45
|
export declare const inject: string[];
|
|
46
|
+
/**
|
|
47
|
+
* 将输入字符串按照空格和逗号分隔,支持双引号包裹以保留空格/逗号。
|
|
48
|
+
* 返回解码后的令牌数组。
|
|
49
|
+
*/
|
|
50
|
+
export interface TokenizeResult {
|
|
51
|
+
tokens: string[];
|
|
52
|
+
seps: string[];
|
|
53
|
+
error?: string;
|
|
54
|
+
}
|
|
55
|
+
export declare function tokenize(input: string): TokenizeResult;
|
|
56
|
+
export interface ParsedArgs {
|
|
57
|
+
keywords: string[];
|
|
58
|
+
flags: {
|
|
59
|
+
groupId?: string;
|
|
60
|
+
method?: string;
|
|
61
|
+
threshold?: string;
|
|
62
|
+
message?: string;
|
|
63
|
+
enableMessage?: boolean;
|
|
64
|
+
disableMessage?: boolean;
|
|
65
|
+
query?: boolean;
|
|
66
|
+
remove?: boolean;
|
|
67
|
+
};
|
|
68
|
+
error?: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 合并提醒消息设置
|
|
72
|
+
*
|
|
73
|
+
* existingConfig - 数据库中已存在的配置记录(可能为 null)
|
|
74
|
+
* cleanedOptions - 从 flags/options 合并出来的对象,包含 message/enableMessage/disableMessage
|
|
75
|
+
* hasRealMessageParam - 是否由用户通过 -msg 指定了具体内容
|
|
76
|
+
* hasRealEnableMessageParam - 是否仅给了 bare -msg
|
|
77
|
+
* hasRealDisableMessageParam - 是否给了 -nomsg
|
|
78
|
+
* logger - 用于记录调试信息的日志器
|
|
79
|
+
*
|
|
80
|
+
* 返回最终的 reminderEnabled 和 reminderMessage。
|
|
81
|
+
* 对于 -nomsg 不会清除已保存的 message,便于后续再次启用时恢复。
|
|
82
|
+
*/
|
|
83
|
+
export declare function mergeReminder(existingConfig: any | null, cleanedOptions: {
|
|
84
|
+
message?: string;
|
|
85
|
+
enableMessage?: boolean;
|
|
86
|
+
disableMessage?: boolean;
|
|
87
|
+
}, hasRealMessageParam: boolean, hasRealEnableMessageParam: boolean, hasRealDisableMessageParam: boolean, logger: any): {
|
|
88
|
+
reminderEnabled: boolean;
|
|
89
|
+
reminderMessage: string;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* 解析 gvc 配置命令的原始参数。
|
|
93
|
+
*
|
|
94
|
+
* 返回关键词数组和各类 flag 的值,未出现的 flag 保持 undefined。
|
|
95
|
+
* 若检测到格式错误(如纯空格分隔关键词),返回 error 字段。
|
|
96
|
+
*/
|
|
97
|
+
export declare function parseConfigArgs(raw: string): ParsedArgs;
|
|
46
98
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -23,23 +23,230 @@ __export(src_exports, {
|
|
|
23
23
|
Config: () => Config,
|
|
24
24
|
apply: () => apply,
|
|
25
25
|
inject: () => inject,
|
|
26
|
-
|
|
26
|
+
mergeReminder: () => mergeReminder,
|
|
27
|
+
name: () => name,
|
|
28
|
+
parseConfigArgs: () => parseConfigArgs,
|
|
29
|
+
tokenize: () => tokenize
|
|
27
30
|
});
|
|
28
31
|
module.exports = __toCommonJS(src_exports);
|
|
29
32
|
var import_koishi = require("koishi");
|
|
30
33
|
var name = "group-verification";
|
|
31
34
|
var Config = import_koishi.Schema.object({
|
|
32
|
-
defaultReminderMessage: import_koishi.Schema.string().description("默认提醒消息模板").default(
|
|
33
|
-
|
|
34
|
-
申请理由:{question}
|
|
35
|
-
答对情况:{answer}
|
|
36
|
-
阈值要求:{threshold}
|
|
37
|
-
请管理员使用 #同意 或 #拒绝 来处理此申请`
|
|
38
|
-
),
|
|
39
|
-
enableStrictGroupCheck: import_koishi.Schema.boolean().description("是否启用严格的群号检查(验证机器人是否在群中)").default(true),
|
|
35
|
+
defaultReminderMessage: import_koishi.Schema.string().description("默认提醒消息模板").default("{user}({id}) 申请加入群 {gname}({group})\n申请理由:{question}\n匹配情况:{answer}/{threshold}"),
|
|
36
|
+
enableStrictGroupCheck: import_koishi.Schema.boolean().description("是否启用严格的群号检查(检查群号长度)").default(false),
|
|
40
37
|
logLevel: import_koishi.Schema.union(["debug", "info", "warn", "error"]).description("日志级别").default("info")
|
|
41
38
|
}).description("群组验证插件配置");
|
|
42
39
|
var inject = ["database"];
|
|
40
|
+
var ESC_QUOTE = "\0";
|
|
41
|
+
var ESC_BACKSLASH = "";
|
|
42
|
+
function tokenize(input) {
|
|
43
|
+
const tokens = [];
|
|
44
|
+
const seps = [];
|
|
45
|
+
let cur = "";
|
|
46
|
+
let lastSep = "";
|
|
47
|
+
let i = 0;
|
|
48
|
+
const flush = /* @__PURE__ */ __name(() => {
|
|
49
|
+
if (cur !== "") {
|
|
50
|
+
tokens.push(cur);
|
|
51
|
+
seps.push(lastSep);
|
|
52
|
+
cur = "";
|
|
53
|
+
lastSep = "";
|
|
54
|
+
}
|
|
55
|
+
}, "flush");
|
|
56
|
+
while (i < input.length) {
|
|
57
|
+
const ch = input[i];
|
|
58
|
+
if (ch === " " || ch === ",") {
|
|
59
|
+
lastSep = ch;
|
|
60
|
+
flush();
|
|
61
|
+
i++;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (ch === '"') {
|
|
65
|
+
const prev = i > 0 ? input[i - 1] : "";
|
|
66
|
+
if (i === 0 || prev === " " || prev === ",") {
|
|
67
|
+
i++;
|
|
68
|
+
let content = "";
|
|
69
|
+
let closed = false;
|
|
70
|
+
while (i < input.length) {
|
|
71
|
+
const c = input[i];
|
|
72
|
+
if (c === "\\") {
|
|
73
|
+
if (i + 1 < input.length) {
|
|
74
|
+
const nxt = input[i + 1];
|
|
75
|
+
if (nxt === '"') {
|
|
76
|
+
content += ESC_QUOTE;
|
|
77
|
+
i += 2;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (nxt === "\\") {
|
|
81
|
+
content += ESC_BACKSLASH;
|
|
82
|
+
i += 2;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
content += "\\";
|
|
87
|
+
i++;
|
|
88
|
+
} else if (c === '"') {
|
|
89
|
+
closed = true;
|
|
90
|
+
i++;
|
|
91
|
+
break;
|
|
92
|
+
} else {
|
|
93
|
+
content += c;
|
|
94
|
+
i++;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!closed) {
|
|
98
|
+
return { tokens, seps, error: "引号未闭合" };
|
|
99
|
+
}
|
|
100
|
+
tokens.push(content);
|
|
101
|
+
continue;
|
|
102
|
+
} else {
|
|
103
|
+
cur += ch;
|
|
104
|
+
i++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (ch === "\\") {
|
|
109
|
+
if (i + 1 < input.length) {
|
|
110
|
+
const nxt = input[i + 1];
|
|
111
|
+
if (nxt === '"') {
|
|
112
|
+
cur += ESC_QUOTE;
|
|
113
|
+
i += 2;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (nxt === "\\") {
|
|
117
|
+
cur += ESC_BACKSLASH;
|
|
118
|
+
i += 2;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
cur += ch;
|
|
123
|
+
i++;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
cur += ch;
|
|
127
|
+
i++;
|
|
128
|
+
}
|
|
129
|
+
flush();
|
|
130
|
+
return { tokens, seps };
|
|
131
|
+
}
|
|
132
|
+
__name(tokenize, "tokenize");
|
|
133
|
+
function validateKeywordFormat(raw) {
|
|
134
|
+
if (raw.includes(",") || raw.includes('"')) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
if (raw.includes(" ")) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
__name(validateKeywordFormat, "validateKeywordFormat");
|
|
143
|
+
function mergeReminder(existingConfig, cleanedOptions, hasRealMessageParam, hasRealEnableMessageParam, hasRealDisableMessageParam, logger) {
|
|
144
|
+
let reminderEnabled = true;
|
|
145
|
+
let reminderMessage = "{user}({id}) 申请加入群 {gname}({group})\n申请理由:{question}\n匹配情况:{answer}/{threshold}";
|
|
146
|
+
if (existingConfig) {
|
|
147
|
+
reminderEnabled = existingConfig.reminderEnabled;
|
|
148
|
+
reminderMessage = existingConfig.reminderMessage || reminderMessage;
|
|
149
|
+
}
|
|
150
|
+
if (hasRealDisableMessageParam) {
|
|
151
|
+
reminderEnabled = false;
|
|
152
|
+
logger.info("禁用提醒消息功能 (保留现有内容)");
|
|
153
|
+
} else if (hasRealEnableMessageParam) {
|
|
154
|
+
reminderEnabled = true;
|
|
155
|
+
logger.info(`启用提醒消息(保留原消息): ${reminderMessage.substring(0, 50)}...`);
|
|
156
|
+
} else if (hasRealMessageParam) {
|
|
157
|
+
reminderEnabled = true;
|
|
158
|
+
if (cleanedOptions.message !== void 0) {
|
|
159
|
+
reminderMessage = cleanedOptions.message.replace(/\\n/g, "\n");
|
|
160
|
+
logger.info(`设置自定义提醒消息: ${reminderMessage.substring(0, 50)}...`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { reminderEnabled, reminderMessage };
|
|
164
|
+
}
|
|
165
|
+
__name(mergeReminder, "mergeReminder");
|
|
166
|
+
function parseConfigArgs(raw) {
|
|
167
|
+
const res = tokenize(raw);
|
|
168
|
+
if (res.error) {
|
|
169
|
+
return { keywords: [], flags: {}, error: res.error };
|
|
170
|
+
}
|
|
171
|
+
let tokens = res.tokens;
|
|
172
|
+
const seps = res.seps;
|
|
173
|
+
const flags = {};
|
|
174
|
+
const keywords = [];
|
|
175
|
+
let error;
|
|
176
|
+
const isFlag = /* @__PURE__ */ __name((tok) => /^-(?:i|m|t|msg|nomsg|\?|r)$/.test(tok), "isFlag");
|
|
177
|
+
for (const tok of tokens) {
|
|
178
|
+
if (tok.includes('"')) {
|
|
179
|
+
error = "存在未转义的引号";
|
|
180
|
+
return { keywords: [], flags, error };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
tokens = tokens.map(
|
|
184
|
+
(t) => t.replace(new RegExp(ESC_QUOTE, "g"), '"').replace(new RegExp(ESC_BACKSLASH, "g"), "\\")
|
|
185
|
+
);
|
|
186
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
187
|
+
const tok = tokens[i];
|
|
188
|
+
if (tok === "-i") {
|
|
189
|
+
if (tokens[i + 1] && !isFlag(tokens[i + 1])) {
|
|
190
|
+
flags.groupId = tokens[++i];
|
|
191
|
+
} else {
|
|
192
|
+
return { keywords: [], flags, error: "参数 -i 需要指定群号" };
|
|
193
|
+
}
|
|
194
|
+
} else if (tok === "-m") {
|
|
195
|
+
if (tokens[i + 1] && !isFlag(tokens[i + 1])) {
|
|
196
|
+
flags.method = tokens[++i];
|
|
197
|
+
} else {
|
|
198
|
+
return { keywords: [], flags, error: "参数 -m 需要指定审核方式" };
|
|
199
|
+
}
|
|
200
|
+
} else if (tok === "-t") {
|
|
201
|
+
if (tokens[i + 1] && !isFlag(tokens[i + 1])) {
|
|
202
|
+
flags.threshold = tokens[++i];
|
|
203
|
+
} else {
|
|
204
|
+
return { keywords: [], flags, error: "参数 -t 需要指定阈值" };
|
|
205
|
+
}
|
|
206
|
+
} else if (tok === "-msg") {
|
|
207
|
+
const clusters = [];
|
|
208
|
+
let j = i + 1;
|
|
209
|
+
while (j < tokens.length && !isFlag(tokens[j])) {
|
|
210
|
+
const cluster = [tokens[j]];
|
|
211
|
+
while (j < tokens.length - 1 && seps[j] === ",") {
|
|
212
|
+
j++;
|
|
213
|
+
cluster.push(tokens[j]);
|
|
214
|
+
}
|
|
215
|
+
clusters.push(cluster);
|
|
216
|
+
j++;
|
|
217
|
+
}
|
|
218
|
+
i = j - 1;
|
|
219
|
+
if (clusters.length === 0) {
|
|
220
|
+
flags.enableMessage = true;
|
|
221
|
+
} else {
|
|
222
|
+
const msgCluster = clusters[0];
|
|
223
|
+
flags.message = msgCluster.join(",");
|
|
224
|
+
if (clusters.length > 1) {
|
|
225
|
+
for (let k = 1; k < clusters.length; k++) {
|
|
226
|
+
const kws = clusters[k];
|
|
227
|
+
for (const kw of kws) {
|
|
228
|
+
keywords.push(kw);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
} else if (tok === "-nomsg") {
|
|
234
|
+
flags.disableMessage = true;
|
|
235
|
+
} else if (tok === "-?") {
|
|
236
|
+
flags.query = true;
|
|
237
|
+
} else if (tok === "-r") {
|
|
238
|
+
flags.remove = true;
|
|
239
|
+
} else {
|
|
240
|
+
keywords.push(tok);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const keywordSection = raw.split(/(?:^|\s+)-(?:i|m|t|msg|nomsg|\?|r)\b/)[0].trim();
|
|
244
|
+
if (keywordSection && !validateKeywordFormat(keywordSection)) {
|
|
245
|
+
error = '关键词应使用逗号分隔或引号框起(如:k1,k2,k3 或 "k1","k2" 或 "k1,k2",k3)';
|
|
246
|
+
}
|
|
247
|
+
return { keywords, flags, error };
|
|
248
|
+
}
|
|
249
|
+
__name(parseConfigArgs, "parseConfigArgs");
|
|
43
250
|
function apply(ctx, config) {
|
|
44
251
|
ctx.model.extend("group_verification_config", {
|
|
45
252
|
id: "unsigned",
|
|
@@ -61,6 +268,7 @@ function apply(ctx, config) {
|
|
|
61
268
|
const logger = ctx.logger("group-verification");
|
|
62
269
|
logger.info("群组验证插件已启动");
|
|
63
270
|
logger.info(`默认提醒消息: ${config.defaultReminderMessage}`);
|
|
271
|
+
logger.info(`严格群号检查: ${config.enableStrictGroupCheck ? "启用" : "禁用"}`);
|
|
64
272
|
logger.info(`日志级别: ${config.logLevel}`);
|
|
65
273
|
ctx.model.extend("group_verification_stats", {
|
|
66
274
|
id: "unsigned",
|
|
@@ -85,9 +293,25 @@ function apply(ctx, config) {
|
|
|
85
293
|
autoInc: true
|
|
86
294
|
});
|
|
87
295
|
ctx.on("guild-member-request", async (session) => {
|
|
88
|
-
|
|
296
|
+
logger.debug("guild-member-request event", session);
|
|
297
|
+
let guildId = (session.guildId || session.channelId || "").toString().trim();
|
|
89
298
|
const userId = session.userId;
|
|
90
299
|
const message = session.content || "";
|
|
300
|
+
if (!guildId) {
|
|
301
|
+
logger.warn("guild-member-request 没有 guildId,跳过处理");
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const requestId = session.event?.requestId || session.messageId || "";
|
|
305
|
+
if (requestId) {
|
|
306
|
+
try {
|
|
307
|
+
await session.bot.handleGuildMemberRequest(requestId, true);
|
|
308
|
+
logger.info(`自动同意申请 requestId=${requestId}`);
|
|
309
|
+
await updateStats(guildId, "autoApproved");
|
|
310
|
+
return;
|
|
311
|
+
} catch (e) {
|
|
312
|
+
logger.warn("自动同意失败", e);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
91
315
|
const groupConfig = await ctx.database.get("group_verification_config", {
|
|
92
316
|
groupId: guildId
|
|
93
317
|
});
|
|
@@ -97,6 +321,14 @@ function apply(ctx, config) {
|
|
|
97
321
|
const config2 = groupConfig[0];
|
|
98
322
|
const { isValid, matchedCount, requiredThreshold } = await verifyApplication(config2, message, session);
|
|
99
323
|
if (isValid) {
|
|
324
|
+
if (requestId) {
|
|
325
|
+
try {
|
|
326
|
+
await session.bot.handleGuildMemberRequest(requestId, true);
|
|
327
|
+
logger.info(`自动同意申请 requestId=${requestId}`);
|
|
328
|
+
} catch (e) {
|
|
329
|
+
logger.warn("自动同意失败", e);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
100
332
|
await updateStats(guildId, "autoApproved");
|
|
101
333
|
} else {
|
|
102
334
|
await handleFailedVerification(ctx, session, config2);
|
|
@@ -137,6 +369,10 @@ function apply(ctx, config) {
|
|
|
137
369
|
requestMessage: message,
|
|
138
370
|
applyTime: /* @__PURE__ */ new Date()
|
|
139
371
|
});
|
|
372
|
+
if (!guildId) {
|
|
373
|
+
logger.warn("handleFailedVerification 收到无效 guildId,已放弃发送");
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
140
376
|
if (!config2.reminderEnabled || !config2.reminderMessage || config2.reminderMessage === "") {
|
|
141
377
|
logger.info(`群 ${guildId} 的提醒消息已被禁用,跳过发送`);
|
|
142
378
|
return;
|
|
@@ -268,42 +504,62 @@ ${debugInfo}`];
|
|
|
268
504
|
"gvc"
|
|
269
505
|
).option("groupId", "-i <groupId> 指定群号").option("method", "-m <method> 审核方式 (0-3)").option("threshold", "-t <threshold> 阈值参数").option("message", "-msg [message] 自定义提醒消息").option("disableMessage", "-nomsg 禁用提醒消息").option("query", "-? 查询当前配置").option("remove", "-r 删除配置").action(async ({ session, options }, keywords) => {
|
|
270
506
|
logger.info(`=== 命令解析调试 ===`);
|
|
271
|
-
logger.info(`原始keywords: "${keywords}" (类型: ${typeof keywords})`);
|
|
272
|
-
logger.info(`options对象: ${JSON.stringify(options, null, 2)}`);
|
|
273
507
|
logger.info(`session内容: guildId=${session.guildId}, userId=${session.userId}`);
|
|
274
|
-
|
|
275
|
-
|
|
508
|
+
const rawInput = session.content.split(/\s+/).slice(1).join(" ");
|
|
509
|
+
logger.info(`原始命令参数: "${rawInput}"`);
|
|
510
|
+
const parsed = parseConfigArgs(rawInput);
|
|
511
|
+
const { keywords: parsedKeywords, flags, error: parseError } = parsed;
|
|
512
|
+
if (parseError) {
|
|
513
|
+
return parseError;
|
|
514
|
+
}
|
|
515
|
+
logger.info(`解析结果 flags=${JSON.stringify(flags)}, keywords=[${parsedKeywords.join(", ")}]`);
|
|
516
|
+
const cleanedOptions = {
|
|
517
|
+
groupId: flags.groupId || options.groupId,
|
|
518
|
+
method: flags.method || (options.method === "" ? void 0 : options.method),
|
|
519
|
+
threshold: flags.threshold || options.threshold,
|
|
520
|
+
message: flags.message !== void 0 ? flags.message : options.message,
|
|
521
|
+
enableMessage: flags.enableMessage,
|
|
522
|
+
// 新增:-msg 裸调用标记
|
|
523
|
+
disableMessage: flags.disableMessage || options.disableMessage,
|
|
524
|
+
query: flags.query || options.query,
|
|
525
|
+
remove: flags.remove || options.remove
|
|
526
|
+
};
|
|
527
|
+
logger.info(`合并后options: ${JSON.stringify(cleanedOptions, null, 2)}`);
|
|
528
|
+
if ((cleanedOptions.query || cleanedOptions.remove) && (parsedKeywords.length > 0 || cleanedOptions.method !== void 0 || cleanedOptions.threshold !== void 0 || cleanedOptions.message !== void 0 || cleanedOptions.enableMessage || cleanedOptions.disableMessage || cleanedOptions.groupId !== void 0)) {
|
|
529
|
+
return "参数冲突:-? 或 -r 不能与其他参数或关键词一起使用";
|
|
530
|
+
}
|
|
531
|
+
const hasRealMessageParam = cleanedOptions.message !== void 0;
|
|
532
|
+
const hasRealEnableMessageParam = cleanedOptions.enableMessage === true;
|
|
533
|
+
const hasRealDisableMessageParam = cleanedOptions.disableMessage !== void 0;
|
|
534
|
+
const targetGroupId = cleanedOptions.groupId || session.guildId;
|
|
276
535
|
if (targetGroupId) {
|
|
277
|
-
|
|
278
|
-
if (
|
|
279
|
-
|
|
536
|
+
if (config.enableStrictGroupCheck && cleanedOptions.groupId) {
|
|
537
|
+
if (targetGroupId.length < 5 || targetGroupId.length > 15) {
|
|
538
|
+
return `群号 ${targetGroupId} 格式不合法(长度应在5-15位之间)`;
|
|
280
539
|
}
|
|
540
|
+
}
|
|
541
|
+
try {
|
|
542
|
+
await session.bot.getGuild(targetGroupId);
|
|
281
543
|
const [hasPermission, errorMsg] = await checkPermission(session, targetGroupId);
|
|
282
544
|
if (!hasPermission) {
|
|
283
545
|
return errorMsg || "权限不足";
|
|
284
546
|
}
|
|
285
547
|
} catch (error) {
|
|
286
|
-
|
|
287
|
-
return `群号 ${targetGroupId} 无效或机器人不在该群中`;
|
|
288
|
-
} else {
|
|
289
|
-
const [hasPermission, errorMsg] = await checkPermission(session, targetGroupId);
|
|
290
|
-
if (!hasPermission) {
|
|
291
|
-
return errorMsg || "权限不足";
|
|
292
|
-
}
|
|
293
|
-
}
|
|
548
|
+
return `群号 ${targetGroupId} 无效或机器人不在该群中`;
|
|
294
549
|
}
|
|
295
550
|
} else {
|
|
296
551
|
return "请在群聊中使用此命令或指定群号(-i参数)";
|
|
297
552
|
}
|
|
298
|
-
if (
|
|
553
|
+
if ((hasRealMessageParam || hasRealEnableMessageParam) && hasRealDisableMessageParam) {
|
|
299
554
|
return "参数冲突:不能同时使用 -msg 和 -nomsg";
|
|
300
555
|
}
|
|
301
556
|
const usedOptions = [];
|
|
302
|
-
if (
|
|
303
|
-
if (
|
|
304
|
-
if (
|
|
305
|
-
if (
|
|
306
|
-
if (
|
|
557
|
+
if (cleanedOptions.method !== void 0) usedOptions.push("-m");
|
|
558
|
+
if (cleanedOptions.threshold !== void 0) usedOptions.push("-t");
|
|
559
|
+
if (cleanedOptions.groupId !== void 0) usedOptions.push("-i");
|
|
560
|
+
if (cleanedOptions.message !== void 0) usedOptions.push("-msg");
|
|
561
|
+
if (cleanedOptions.enableMessage) usedOptions.push("-msg");
|
|
562
|
+
if (cleanedOptions.disableMessage) usedOptions.push("-nomsg");
|
|
307
563
|
if (usedOptions.length > new Set(usedOptions).size) {
|
|
308
564
|
return `检测到重复参数: ${usedOptions.join(", ")},将使用最后一次出现的值`;
|
|
309
565
|
}
|
|
@@ -313,7 +569,7 @@ ${debugInfo}`];
|
|
|
313
569
|
groupName = guild.name || groupName;
|
|
314
570
|
} catch (error) {
|
|
315
571
|
}
|
|
316
|
-
if (
|
|
572
|
+
if (cleanedOptions.remove) {
|
|
317
573
|
const existingConfig2 = await ctx.database.get("group_verification_config", { groupId: targetGroupId });
|
|
318
574
|
if (existingConfig2.length > 0) {
|
|
319
575
|
await ctx.database.remove("group_verification_config", { id: existingConfig2[0].id });
|
|
@@ -323,7 +579,7 @@ ${debugInfo}`];
|
|
|
323
579
|
return `群 ${targetGroupId} 无验证配置`;
|
|
324
580
|
}
|
|
325
581
|
}
|
|
326
|
-
if (
|
|
582
|
+
if (cleanedOptions.query) {
|
|
327
583
|
const existingConfig2 = await ctx.database.get("group_verification_config", { groupId: targetGroupId });
|
|
328
584
|
if (existingConfig2.length > 0) {
|
|
329
585
|
const config2 = existingConfig2[0];
|
|
@@ -367,93 +623,10 @@ ${debugInfo}`];
|
|
|
367
623
|
return `群 ${targetGroupId} 无验证配置`;
|
|
368
624
|
}
|
|
369
625
|
}
|
|
370
|
-
let keywordList =
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
let keywordSection = processedInput;
|
|
375
|
-
let paramSection = "";
|
|
376
|
-
const paramMatch = processedInput.match(/\s+-(?:m|t|i|msg|nomsg|\?|r)\s+/);
|
|
377
|
-
if (paramMatch) {
|
|
378
|
-
const paramIndex = paramMatch.index;
|
|
379
|
-
keywordSection = processedInput.substring(0, paramIndex).trim();
|
|
380
|
-
paramSection = processedInput.substring(paramIndex).trim();
|
|
381
|
-
logger.info(`参数分离: 关键词="${keywordSection}", 参数="${paramSection}"`);
|
|
382
|
-
}
|
|
383
|
-
if (options.method !== void 0) {
|
|
384
|
-
logger.info(`检测到 -m 参数: ${options.method}`);
|
|
385
|
-
}
|
|
386
|
-
if (options.threshold !== void 0) {
|
|
387
|
-
logger.info(`检测到 -t 参数: ${options.threshold}`);
|
|
388
|
-
}
|
|
389
|
-
if (typeof paramSection !== "undefined" && paramSection) {
|
|
390
|
-
logger.info(`手动解析参数部分: "${paramSection}"`);
|
|
391
|
-
const mMatch = paramSection.match(/-m\s+(\d+)/);
|
|
392
|
-
if (mMatch && options.method === void 0) {
|
|
393
|
-
options.method = mMatch[1];
|
|
394
|
-
logger.info(`手动解析 -m: ${options.method}`);
|
|
395
|
-
}
|
|
396
|
-
const tMatch = paramSection.match(/-t\s+(\d+)/);
|
|
397
|
-
if (tMatch && options.threshold === void 0) {
|
|
398
|
-
options.threshold = tMatch[1];
|
|
399
|
-
logger.info(`手动解析 -t: ${options.threshold}`);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
if (keywordSection) {
|
|
403
|
-
logger.info(`开始处理关键词部分: "${keywordSection}"`);
|
|
404
|
-
const result = [];
|
|
405
|
-
let i = 0;
|
|
406
|
-
while (i < keywordSection.length) {
|
|
407
|
-
const char = keywordSection[i];
|
|
408
|
-
if (char === '"') {
|
|
409
|
-
i++;
|
|
410
|
-
let content = "";
|
|
411
|
-
let foundEndQuote = false;
|
|
412
|
-
while (i < keywordSection.length) {
|
|
413
|
-
if (keywordSection[i] === '"' && (i === 0 || keywordSection[i - 1] !== "\\")) {
|
|
414
|
-
foundEndQuote = true;
|
|
415
|
-
i++;
|
|
416
|
-
break;
|
|
417
|
-
} else {
|
|
418
|
-
content += keywordSection[i];
|
|
419
|
-
i++;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
let hasNextComma = false;
|
|
423
|
-
if (i < keywordSection.length && keywordSection[i] === ",") {
|
|
424
|
-
hasNextComma = true;
|
|
425
|
-
i++;
|
|
426
|
-
}
|
|
427
|
-
result.push(content);
|
|
428
|
-
logger.info(`引号处理: "${content}", 下一个逗号: ${hasNextComma}`);
|
|
429
|
-
if (!foundEndQuote) {
|
|
430
|
-
logger.info("未找到结束引号,前引号作为内容处理");
|
|
431
|
-
}
|
|
432
|
-
} else if (char === "," || char === " ") {
|
|
433
|
-
i++;
|
|
434
|
-
} else {
|
|
435
|
-
let content = "";
|
|
436
|
-
while (i < keywordSection.length && keywordSection[i] !== "," && keywordSection[i] !== " ") {
|
|
437
|
-
content += keywordSection[i];
|
|
438
|
-
i++;
|
|
439
|
-
}
|
|
440
|
-
if (content) {
|
|
441
|
-
result.push(content);
|
|
442
|
-
logger.info(`普通内容处理: "${content}"`);
|
|
443
|
-
}
|
|
444
|
-
if (i < keywordSection.length && (keywordSection[i] === "," || keywordSection[i] === " ")) {
|
|
445
|
-
i++;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
keywordList = result.filter((item) => item.length > 0);
|
|
450
|
-
logger.info(`最终处理结果: [${keywordList.map((k) => `"${k}"`).join(", ")}]`);
|
|
451
|
-
}
|
|
452
|
-
logger.info(`最终关键词列表: [${keywordList.map((k) => `"${k}"`).join(", ")}]`);
|
|
453
|
-
}
|
|
454
|
-
logger.info(`关键词解析结果: [${keywordList.join(", ")}] - 原始输入: "${keywords}"`);
|
|
455
|
-
if (keywordList.length === 0 && !options.query && !options.remove) {
|
|
456
|
-
const hasConfigParams = options.method !== void 0 || options.threshold !== void 0 || options.message !== void 0 || options.disableMessage !== void 0;
|
|
626
|
+
let keywordList = parsedKeywords.slice();
|
|
627
|
+
logger.info(`关键词解析结果: [${keywordList.join(", ")}] - 原始输入: "${rawInput}"`);
|
|
628
|
+
if (keywordList.length === 0 && !cleanedOptions.query && !cleanedOptions.remove) {
|
|
629
|
+
const hasConfigParams = cleanedOptions.method !== void 0 || cleanedOptions.threshold !== void 0 || hasRealMessageParam || hasRealEnableMessageParam || hasRealDisableMessageParam;
|
|
457
630
|
if (!hasConfigParams) {
|
|
458
631
|
return `用法:
|
|
459
632
|
gvc 关键词1,关键词2 -m 1 -t 2 # 创建配置
|
|
@@ -463,36 +636,29 @@ gvc -nomsg # 禁用提醒消息
|
|
|
463
636
|
gvc -? # 查询配置
|
|
464
637
|
gvc -r # 删除配置`;
|
|
465
638
|
}
|
|
466
|
-
|
|
467
|
-
|
|
639
|
+
}
|
|
640
|
+
let existingConfig = null;
|
|
641
|
+
const existingConfigs = await ctx.database.get("group_verification_config", { groupId: targetGroupId });
|
|
642
|
+
if (existingConfigs.length > 0) {
|
|
643
|
+
existingConfig = existingConfigs[0];
|
|
644
|
+
}
|
|
645
|
+
if (keywordList.length === 0 && !cleanedOptions.query && !cleanedOptions.remove) {
|
|
646
|
+
if (!existingConfig) {
|
|
468
647
|
return "请先提供关键词创建配置,或使用 -? 查询配置,-r 删除配置";
|
|
469
648
|
}
|
|
470
|
-
keywordList =
|
|
471
|
-
}
|
|
472
|
-
let reminderEnabled = true;
|
|
473
|
-
let reminderMessage = config.defaultReminderMessage || `用户 {user}({id}) 申请加入群 {gname}({group})
|
|
474
|
-
申请理由:{question}
|
|
475
|
-
答对情况:{answer}
|
|
476
|
-
阈值要求:{threshold}
|
|
477
|
-
请管理员使用 #同意 或 #拒绝 来处理此申请`;
|
|
478
|
-
if (options.disableMessage) {
|
|
479
|
-
reminderEnabled = false;
|
|
480
|
-
reminderMessage = "";
|
|
481
|
-
} else if (options.message !== void 0) {
|
|
482
|
-
reminderEnabled = true;
|
|
483
|
-
if (options.message !== "") {
|
|
484
|
-
reminderMessage = options.message;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
if (reminderEnabled && reminderMessage) {
|
|
488
|
-
reminderMessage = reminderMessage.replace(/\\n/g, "\n");
|
|
649
|
+
keywordList = existingConfig.keywords;
|
|
489
650
|
}
|
|
651
|
+
const { reminderEnabled, reminderMessage } = mergeReminder(
|
|
652
|
+
existingConfig,
|
|
653
|
+
cleanedOptions,
|
|
654
|
+
hasRealMessageParam,
|
|
655
|
+
hasRealEnableMessageParam,
|
|
656
|
+
hasRealDisableMessageParam,
|
|
657
|
+
logger
|
|
658
|
+
);
|
|
490
659
|
let reviewMethod = 0;
|
|
491
660
|
let reviewParameters = 0;
|
|
492
|
-
|
|
493
|
-
const configs = await ctx.database.get("group_verification_config", { groupId: targetGroupId });
|
|
494
|
-
if (configs.length > 0) {
|
|
495
|
-
existingConfig = configs[0];
|
|
661
|
+
if (existingConfig) {
|
|
496
662
|
reviewMethod = existingConfig.reviewMethod;
|
|
497
663
|
if (existingConfig.reviewParameters === void 0 || existingConfig.reviewParameters === null || isNaN(existingConfig.reviewParameters)) {
|
|
498
664
|
reviewParameters = 0;
|
|
@@ -501,23 +667,18 @@ gvc -r # 删除配置`;
|
|
|
501
667
|
reviewParameters = existingConfig.reviewParameters;
|
|
502
668
|
}
|
|
503
669
|
}
|
|
504
|
-
if (
|
|
505
|
-
const methodNum = parseInt(
|
|
670
|
+
if (cleanedOptions.method !== void 0 && cleanedOptions.method !== "") {
|
|
671
|
+
const methodNum = parseInt(cleanedOptions.method);
|
|
506
672
|
if (isNaN(methodNum) || methodNum < 0 || methodNum > 3) {
|
|
507
673
|
return "审核方式参数错误:0-全部同意, 1-按数量同意, 2-按比例同意, 3-全部拒绝";
|
|
508
674
|
}
|
|
509
675
|
reviewMethod = methodNum;
|
|
510
676
|
logger.info(`审核方式明确指定为: ${reviewMethod}`);
|
|
511
|
-
if (reviewMethod === 0 || reviewMethod === 3) {
|
|
512
|
-
reviewParameters = 0;
|
|
513
|
-
} else if (reviewParameters === 0) {
|
|
514
|
-
reviewParameters = reviewMethod === 1 ? keywordList.length : 100;
|
|
515
|
-
}
|
|
516
677
|
} else {
|
|
517
678
|
logger.info(`未指定审核方式,保持原有值: ${reviewMethod}`);
|
|
518
679
|
}
|
|
519
|
-
if (
|
|
520
|
-
const thresholdNum = parseInt(
|
|
680
|
+
if (cleanedOptions.threshold !== void 0) {
|
|
681
|
+
const thresholdNum = parseInt(cleanedOptions.threshold);
|
|
521
682
|
if (isNaN(thresholdNum)) {
|
|
522
683
|
return "阈值参数必须为数字";
|
|
523
684
|
}
|
|
@@ -550,7 +711,6 @@ gvc -r # 删除配置`;
|
|
|
550
711
|
logger.info(`关键词数量未变化(${newKeywordCount}),保持原阈值: ${reviewParameters}`);
|
|
551
712
|
}
|
|
552
713
|
}
|
|
553
|
-
const existingConfigs = await ctx.database.get("group_verification_config", { groupId: targetGroupId });
|
|
554
714
|
const encodedKeywords = keywordList.map((keyword) => {
|
|
555
715
|
return keyword.replace(/,/g, "[[COMMA]]");
|
|
556
716
|
});
|
|
@@ -565,8 +725,8 @@ gvc -r # 删除配置`;
|
|
|
565
725
|
updatedBy: session.username || session.userId,
|
|
566
726
|
updatedAt: /* @__PURE__ */ new Date()
|
|
567
727
|
};
|
|
568
|
-
if (
|
|
569
|
-
await ctx.database.set("group_verification_config", { id:
|
|
728
|
+
if (existingConfig) {
|
|
729
|
+
await ctx.database.set("group_verification_config", { id: existingConfig.id }, dbData);
|
|
570
730
|
logger.info(`更新配置成功 - 审核方式: ${reviewMethod}, 阈值: ${reviewParameters}`);
|
|
571
731
|
} else {
|
|
572
732
|
await ctx.database.create("group_verification_config", {
|
|
@@ -580,7 +740,8 @@ gvc -r # 删除配置`;
|
|
|
580
740
|
const decodedKeywords = keywordList;
|
|
581
741
|
let feedbackMessage = `群 ${targetGroupId} 配置已更新:
|
|
582
742
|
`;
|
|
583
|
-
|
|
743
|
+
const displayKeywords = keywordList.map((k) => k.replace(/\[\[COMMA\]\]/g, ","));
|
|
744
|
+
feedbackMessage += `关键词: ${displayKeywords.map((k) => `"${k}"`).join(", ")}
|
|
584
745
|
`;
|
|
585
746
|
const methodMap = { 0: "全部同意", 1: "按数量", 2: "按比例", 3: "全部拒绝" };
|
|
586
747
|
feedbackMessage += `审核方式: ${methodMap[reviewMethod]}
|
|
@@ -827,7 +988,10 @@ gvc -r # 删除配置`;
|
|
|
827
988
|
-i <群号> 指定群号(私聊时必需)
|
|
828
989
|
-m <方式> 审核方式 (0=全部同意, 1=按数量, 2=按比例, 3=全部拒绝)
|
|
829
990
|
-t <阈值> 阈值参数(方式1:0-关键词数, 方式2:0-100)
|
|
830
|
-
-msg [消息] 自定义提醒消息(支持引号和\\n
|
|
991
|
+
-msg [消息] 自定义提醒消息(支持引号和\\n换行);若不跟内容则仅启用/保留上一次的消息
|
|
992
|
+
空格将把消息与后续关键词分隔。
|
|
993
|
+
若消息本身含逗号且不希望与后续文字混淆,可直接写出(只要所有逗号处均未伴随空格,插件会将它们视为同一消息)。
|
|
994
|
+
如果逗号后还有其他词,则请用引号包裹整个消息或把逗号与空格分开以避免歧义。
|
|
831
995
|
-nomsg 禁用提醒消息
|
|
832
996
|
-? 查询当前配置
|
|
833
997
|
-r 删除配置
|
|
@@ -944,5 +1108,8 @@ __name(apply, "apply");
|
|
|
944
1108
|
Config,
|
|
945
1109
|
apply,
|
|
946
1110
|
inject,
|
|
947
|
-
|
|
1111
|
+
mergeReminder,
|
|
1112
|
+
name,
|
|
1113
|
+
parseConfigArgs,
|
|
1114
|
+
tokenize
|
|
948
1115
|
});
|