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 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
- name: () => name
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
- `用户 {user}({id}) 申请加入群 {gname}({group})
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
- logger.info(`==================`);
275
- const targetGroupId = options.groupId || session.guildId;
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
- try {
278
- if (config.enableStrictGroupCheck) {
279
- await session.bot.getGuild(targetGroupId);
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
- if (config.enableStrictGroupCheck) {
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 (options.message !== void 0 && options.disableMessage) {
525
+ if ((hasRealMessageParam || hasRealEnableMessageParam) && hasRealDisableMessageParam) {
299
526
  return "参数冲突:不能同时使用 -msg 和 -nomsg";
300
527
  }
301
528
  const usedOptions = [];
302
- if (options.method !== void 0) usedOptions.push("-m");
303
- if (options.threshold !== void 0) usedOptions.push("-t");
304
- if (options.groupId !== void 0) usedOptions.push("-i");
305
- if (options.message !== void 0) usedOptions.push("-msg");
306
- if (options.disableMessage) usedOptions.push("-nomsg");
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 (options.remove) {
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 (options.query) {
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
- if (keywords) {
372
- logger.info(`开始处理关键词: "${keywords}"`);
373
- let processedInput = keywords.trim();
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
- const existingConfig2 = await ctx.database.get("group_verification_config", { groupId: targetGroupId });
467
- if (existingConfig2.length === 0) {
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 = existingConfig2[0].keywords;
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
- let existingConfig = null;
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 (options.method !== void 0) {
505
- const methodNum = parseInt(options.method);
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 (options.threshold !== void 0) {
520
- const thresholdNum = parseInt(options.threshold);
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 (existingConfigs.length > 0) {
569
- await ctx.database.set("group_verification_config", { id: existingConfigs[0].id }, dbData);
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
- feedbackMessage += `关键词: ${decodedKeywords.map((k) => `"${k}"`).join(", ")}
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
- name
1083
+ mergeReminder,
1084
+ name,
1085
+ parseConfigArgs,
1086
+ tokenize
948
1087
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-group-verification",
3
3
  "description": "[WIP] Koishi 群组加群验证插件,支持多关键词匹配审核、多种审核方式和详细统计功能(开发中)",
4
- "version": "1.0.16",
4
+ "version": "1.0.17",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
package/readme.md CHANGED
@@ -101,6 +101,8 @@ group-verify.stats total
101
101
 
102
102
  ### 提醒消息配置
103
103
  - `-msg "消息内容"` - 设置自定义提醒消息
104
+ - 消息内部可包含逗号:只要所有逗号前后都没有空格,它们将被视为同一消息内容。
105
+ - 若逗号之后仍需写关键词,请使用引号包裹整个消息或在逗号后加空格以分隔。
104
106
  - `-nomsg` - 禁用提醒消息功能
105
107
  - 不带参数的 `-msg` 会显示帮助信息
106
108