koishi-plugin-group-verification 1.0.16 → 1.0.17
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 +298 -159
- package/package.json +1 -1
- package/readme.md +2 -0
- package/src/index.ts +415 -236
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",
|
|
@@ -268,42 +476,62 @@ ${debugInfo}`];
|
|
|
268
476
|
"gvc"
|
|
269
477
|
).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
478
|
logger.info(`=== 命令解析调试 ===`);
|
|
271
|
-
logger.info(`原始keywords: "${keywords}" (类型: ${typeof keywords})`);
|
|
272
|
-
logger.info(`options对象: ${JSON.stringify(options, null, 2)}`);
|
|
273
479
|
logger.info(`session内容: guildId=${session.guildId}, userId=${session.userId}`);
|
|
274
|
-
|
|
275
|
-
|
|
480
|
+
const rawInput = session.content.split(/\s+/).slice(1).join(" ");
|
|
481
|
+
logger.info(`原始命令参数: "${rawInput}"`);
|
|
482
|
+
const parsed = parseConfigArgs(rawInput);
|
|
483
|
+
const { keywords: parsedKeywords, flags, error: parseError } = parsed;
|
|
484
|
+
if (parseError) {
|
|
485
|
+
return parseError;
|
|
486
|
+
}
|
|
487
|
+
logger.info(`解析结果 flags=${JSON.stringify(flags)}, keywords=[${parsedKeywords.join(", ")}]`);
|
|
488
|
+
const cleanedOptions = {
|
|
489
|
+
groupId: flags.groupId || options.groupId,
|
|
490
|
+
method: flags.method || (options.method === "" ? void 0 : options.method),
|
|
491
|
+
threshold: flags.threshold || options.threshold,
|
|
492
|
+
message: flags.message !== void 0 ? flags.message : options.message,
|
|
493
|
+
enableMessage: flags.enableMessage,
|
|
494
|
+
// 新增:-msg 裸调用标记
|
|
495
|
+
disableMessage: flags.disableMessage || options.disableMessage,
|
|
496
|
+
query: flags.query || options.query,
|
|
497
|
+
remove: flags.remove || options.remove
|
|
498
|
+
};
|
|
499
|
+
logger.info(`合并后options: ${JSON.stringify(cleanedOptions, null, 2)}`);
|
|
500
|
+
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)) {
|
|
501
|
+
return "参数冲突:-? 或 -r 不能与其他参数或关键词一起使用";
|
|
502
|
+
}
|
|
503
|
+
const hasRealMessageParam = cleanedOptions.message !== void 0;
|
|
504
|
+
const hasRealEnableMessageParam = cleanedOptions.enableMessage === true;
|
|
505
|
+
const hasRealDisableMessageParam = cleanedOptions.disableMessage !== void 0;
|
|
506
|
+
const targetGroupId = cleanedOptions.groupId || session.guildId;
|
|
276
507
|
if (targetGroupId) {
|
|
277
|
-
|
|
278
|
-
if (
|
|
279
|
-
|
|
508
|
+
if (config.enableStrictGroupCheck && cleanedOptions.groupId) {
|
|
509
|
+
if (targetGroupId.length < 5 || targetGroupId.length > 15) {
|
|
510
|
+
return `群号 ${targetGroupId} 格式不合法(长度应在5-15位之间)`;
|
|
280
511
|
}
|
|
512
|
+
}
|
|
513
|
+
try {
|
|
514
|
+
await session.bot.getGuild(targetGroupId);
|
|
281
515
|
const [hasPermission, errorMsg] = await checkPermission(session, targetGroupId);
|
|
282
516
|
if (!hasPermission) {
|
|
283
517
|
return errorMsg || "权限不足";
|
|
284
518
|
}
|
|
285
519
|
} catch (error) {
|
|
286
|
-
|
|
287
|
-
return `群号 ${targetGroupId} 无效或机器人不在该群中`;
|
|
288
|
-
} else {
|
|
289
|
-
const [hasPermission, errorMsg] = await checkPermission(session, targetGroupId);
|
|
290
|
-
if (!hasPermission) {
|
|
291
|
-
return errorMsg || "权限不足";
|
|
292
|
-
}
|
|
293
|
-
}
|
|
520
|
+
return `群号 ${targetGroupId} 无效或机器人不在该群中`;
|
|
294
521
|
}
|
|
295
522
|
} else {
|
|
296
523
|
return "请在群聊中使用此命令或指定群号(-i参数)";
|
|
297
524
|
}
|
|
298
|
-
if (
|
|
525
|
+
if ((hasRealMessageParam || hasRealEnableMessageParam) && hasRealDisableMessageParam) {
|
|
299
526
|
return "参数冲突:不能同时使用 -msg 和 -nomsg";
|
|
300
527
|
}
|
|
301
528
|
const usedOptions = [];
|
|
302
|
-
if (
|
|
303
|
-
if (
|
|
304
|
-
if (
|
|
305
|
-
if (
|
|
306
|
-
if (
|
|
529
|
+
if (cleanedOptions.method !== void 0) usedOptions.push("-m");
|
|
530
|
+
if (cleanedOptions.threshold !== void 0) usedOptions.push("-t");
|
|
531
|
+
if (cleanedOptions.groupId !== void 0) usedOptions.push("-i");
|
|
532
|
+
if (cleanedOptions.message !== void 0) usedOptions.push("-msg");
|
|
533
|
+
if (cleanedOptions.enableMessage) usedOptions.push("-msg");
|
|
534
|
+
if (cleanedOptions.disableMessage) usedOptions.push("-nomsg");
|
|
307
535
|
if (usedOptions.length > new Set(usedOptions).size) {
|
|
308
536
|
return `检测到重复参数: ${usedOptions.join(", ")},将使用最后一次出现的值`;
|
|
309
537
|
}
|
|
@@ -313,7 +541,7 @@ ${debugInfo}`];
|
|
|
313
541
|
groupName = guild.name || groupName;
|
|
314
542
|
} catch (error) {
|
|
315
543
|
}
|
|
316
|
-
if (
|
|
544
|
+
if (cleanedOptions.remove) {
|
|
317
545
|
const existingConfig2 = await ctx.database.get("group_verification_config", { groupId: targetGroupId });
|
|
318
546
|
if (existingConfig2.length > 0) {
|
|
319
547
|
await ctx.database.remove("group_verification_config", { id: existingConfig2[0].id });
|
|
@@ -323,7 +551,7 @@ ${debugInfo}`];
|
|
|
323
551
|
return `群 ${targetGroupId} 无验证配置`;
|
|
324
552
|
}
|
|
325
553
|
}
|
|
326
|
-
if (
|
|
554
|
+
if (cleanedOptions.query) {
|
|
327
555
|
const existingConfig2 = await ctx.database.get("group_verification_config", { groupId: targetGroupId });
|
|
328
556
|
if (existingConfig2.length > 0) {
|
|
329
557
|
const config2 = existingConfig2[0];
|
|
@@ -367,93 +595,10 @@ ${debugInfo}`];
|
|
|
367
595
|
return `群 ${targetGroupId} 无验证配置`;
|
|
368
596
|
}
|
|
369
597
|
}
|
|
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;
|
|
598
|
+
let keywordList = parsedKeywords.slice();
|
|
599
|
+
logger.info(`关键词解析结果: [${keywordList.join(", ")}] - 原始输入: "${rawInput}"`);
|
|
600
|
+
if (keywordList.length === 0 && !cleanedOptions.query && !cleanedOptions.remove) {
|
|
601
|
+
const hasConfigParams = cleanedOptions.method !== void 0 || cleanedOptions.threshold !== void 0 || hasRealMessageParam || hasRealEnableMessageParam || hasRealDisableMessageParam;
|
|
457
602
|
if (!hasConfigParams) {
|
|
458
603
|
return `用法:
|
|
459
604
|
gvc 关键词1,关键词2 -m 1 -t 2 # 创建配置
|
|
@@ -463,36 +608,29 @@ gvc -nomsg # 禁用提醒消息
|
|
|
463
608
|
gvc -? # 查询配置
|
|
464
609
|
gvc -r # 删除配置`;
|
|
465
610
|
}
|
|
466
|
-
|
|
467
|
-
|
|
611
|
+
}
|
|
612
|
+
let existingConfig = null;
|
|
613
|
+
const existingConfigs = await ctx.database.get("group_verification_config", { groupId: targetGroupId });
|
|
614
|
+
if (existingConfigs.length > 0) {
|
|
615
|
+
existingConfig = existingConfigs[0];
|
|
616
|
+
}
|
|
617
|
+
if (keywordList.length === 0 && !cleanedOptions.query && !cleanedOptions.remove) {
|
|
618
|
+
if (!existingConfig) {
|
|
468
619
|
return "请先提供关键词创建配置,或使用 -? 查询配置,-r 删除配置";
|
|
469
620
|
}
|
|
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");
|
|
621
|
+
keywordList = existingConfig.keywords;
|
|
489
622
|
}
|
|
623
|
+
const { reminderEnabled, reminderMessage } = mergeReminder(
|
|
624
|
+
existingConfig,
|
|
625
|
+
cleanedOptions,
|
|
626
|
+
hasRealMessageParam,
|
|
627
|
+
hasRealEnableMessageParam,
|
|
628
|
+
hasRealDisableMessageParam,
|
|
629
|
+
logger
|
|
630
|
+
);
|
|
490
631
|
let reviewMethod = 0;
|
|
491
632
|
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];
|
|
633
|
+
if (existingConfig) {
|
|
496
634
|
reviewMethod = existingConfig.reviewMethod;
|
|
497
635
|
if (existingConfig.reviewParameters === void 0 || existingConfig.reviewParameters === null || isNaN(existingConfig.reviewParameters)) {
|
|
498
636
|
reviewParameters = 0;
|
|
@@ -501,23 +639,18 @@ gvc -r # 删除配置`;
|
|
|
501
639
|
reviewParameters = existingConfig.reviewParameters;
|
|
502
640
|
}
|
|
503
641
|
}
|
|
504
|
-
if (
|
|
505
|
-
const methodNum = parseInt(
|
|
642
|
+
if (cleanedOptions.method !== void 0 && cleanedOptions.method !== "") {
|
|
643
|
+
const methodNum = parseInt(cleanedOptions.method);
|
|
506
644
|
if (isNaN(methodNum) || methodNum < 0 || methodNum > 3) {
|
|
507
645
|
return "审核方式参数错误:0-全部同意, 1-按数量同意, 2-按比例同意, 3-全部拒绝";
|
|
508
646
|
}
|
|
509
647
|
reviewMethod = methodNum;
|
|
510
648
|
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
649
|
} else {
|
|
517
650
|
logger.info(`未指定审核方式,保持原有值: ${reviewMethod}`);
|
|
518
651
|
}
|
|
519
|
-
if (
|
|
520
|
-
const thresholdNum = parseInt(
|
|
652
|
+
if (cleanedOptions.threshold !== void 0) {
|
|
653
|
+
const thresholdNum = parseInt(cleanedOptions.threshold);
|
|
521
654
|
if (isNaN(thresholdNum)) {
|
|
522
655
|
return "阈值参数必须为数字";
|
|
523
656
|
}
|
|
@@ -550,7 +683,6 @@ gvc -r # 删除配置`;
|
|
|
550
683
|
logger.info(`关键词数量未变化(${newKeywordCount}),保持原阈值: ${reviewParameters}`);
|
|
551
684
|
}
|
|
552
685
|
}
|
|
553
|
-
const existingConfigs = await ctx.database.get("group_verification_config", { groupId: targetGroupId });
|
|
554
686
|
const encodedKeywords = keywordList.map((keyword) => {
|
|
555
687
|
return keyword.replace(/,/g, "[[COMMA]]");
|
|
556
688
|
});
|
|
@@ -565,8 +697,8 @@ gvc -r # 删除配置`;
|
|
|
565
697
|
updatedBy: session.username || session.userId,
|
|
566
698
|
updatedAt: /* @__PURE__ */ new Date()
|
|
567
699
|
};
|
|
568
|
-
if (
|
|
569
|
-
await ctx.database.set("group_verification_config", { id:
|
|
700
|
+
if (existingConfig) {
|
|
701
|
+
await ctx.database.set("group_verification_config", { id: existingConfig.id }, dbData);
|
|
570
702
|
logger.info(`更新配置成功 - 审核方式: ${reviewMethod}, 阈值: ${reviewParameters}`);
|
|
571
703
|
} else {
|
|
572
704
|
await ctx.database.create("group_verification_config", {
|
|
@@ -580,7 +712,8 @@ gvc -r # 删除配置`;
|
|
|
580
712
|
const decodedKeywords = keywordList;
|
|
581
713
|
let feedbackMessage = `群 ${targetGroupId} 配置已更新:
|
|
582
714
|
`;
|
|
583
|
-
|
|
715
|
+
const displayKeywords = keywordList.map((k) => k.replace(/\[\[COMMA\]\]/g, ","));
|
|
716
|
+
feedbackMessage += `关键词: ${displayKeywords.map((k) => `"${k}"`).join(", ")}
|
|
584
717
|
`;
|
|
585
718
|
const methodMap = { 0: "全部同意", 1: "按数量", 2: "按比例", 3: "全部拒绝" };
|
|
586
719
|
feedbackMessage += `审核方式: ${methodMap[reviewMethod]}
|
|
@@ -827,7 +960,10 @@ gvc -r # 删除配置`;
|
|
|
827
960
|
-i <群号> 指定群号(私聊时必需)
|
|
828
961
|
-m <方式> 审核方式 (0=全部同意, 1=按数量, 2=按比例, 3=全部拒绝)
|
|
829
962
|
-t <阈值> 阈值参数(方式1:0-关键词数, 方式2:0-100)
|
|
830
|
-
-msg [消息] 自定义提醒消息(支持引号和\\n
|
|
963
|
+
-msg [消息] 自定义提醒消息(支持引号和\\n换行);若不跟内容则仅启用/保留上一次的消息
|
|
964
|
+
空格将把消息与后续关键词分隔。
|
|
965
|
+
若消息本身含逗号且不希望与后续文字混淆,可直接写出(只要所有逗号处均未伴随空格,插件会将它们视为同一消息)。
|
|
966
|
+
如果逗号后还有其他词,则请用引号包裹整个消息或把逗号与空格分开以避免歧义。
|
|
831
967
|
-nomsg 禁用提醒消息
|
|
832
968
|
-? 查询当前配置
|
|
833
969
|
-r 删除配置
|
|
@@ -944,5 +1080,8 @@ __name(apply, "apply");
|
|
|
944
1080
|
Config,
|
|
945
1081
|
apply,
|
|
946
1082
|
inject,
|
|
947
|
-
|
|
1083
|
+
mergeReminder,
|
|
1084
|
+
name,
|
|
1085
|
+
parseConfigArgs,
|
|
1086
|
+
tokenize
|
|
948
1087
|
});
|
package/package.json
CHANGED