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 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",
@@ -85,9 +293,25 @@ function apply(ctx, config) {
85
293
  autoInc: true
86
294
  });
87
295
  ctx.on("guild-member-request", async (session) => {
88
- const guildId = session.guildId;
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
- logger.info(`==================`);
275
- const targetGroupId = options.groupId || session.guildId;
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
- try {
278
- if (config.enableStrictGroupCheck) {
279
- await session.bot.getGuild(targetGroupId);
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
- if (config.enableStrictGroupCheck) {
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 (options.message !== void 0 && options.disableMessage) {
553
+ if ((hasRealMessageParam || hasRealEnableMessageParam) && hasRealDisableMessageParam) {
299
554
  return "参数冲突:不能同时使用 -msg 和 -nomsg";
300
555
  }
301
556
  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");
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 (options.remove) {
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 (options.query) {
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
- 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;
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
- const existingConfig2 = await ctx.database.get("group_verification_config", { groupId: targetGroupId });
467
- if (existingConfig2.length === 0) {
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 = 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");
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
- let existingConfig = null;
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 (options.method !== void 0) {
505
- const methodNum = parseInt(options.method);
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 (options.threshold !== void 0) {
520
- const thresholdNum = parseInt(options.threshold);
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 (existingConfigs.length > 0) {
569
- await ctx.database.set("group_verification_config", { id: existingConfigs[0].id }, dbData);
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
- feedbackMessage += `关键词: ${decodedKeywords.map((k) => `"${k}"`).join(", ")}
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
- name
1111
+ mergeReminder,
1112
+ name,
1113
+ parseConfigArgs,
1114
+ tokenize
948
1115
  });