koishi-plugin-group-verification 1.0.32 → 1.0.34
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.js +62 -56
- package/package.json +2 -2
- package/readme.md +16 -0
- package/src/index.ts +96 -89
package/lib/index.js
CHANGED
|
@@ -45,12 +45,12 @@ var import_koishi = require("koishi");
|
|
|
45
45
|
var name = "group-verification";
|
|
46
46
|
var logger = console;
|
|
47
47
|
var Config = import_koishi.Schema.object({
|
|
48
|
-
defaultReminderMessage: import_koishi.Schema.string().description("默认提醒消息模板(使用 \\n 表示换行,可包含下方变量)").default("{user}({id}) 申请加入群 {gname}({group})\n
|
|
48
|
+
defaultReminderMessage: import_koishi.Schema.string().description("默认提醒消息模板(使用 \\n 表示换行,可包含下方变量)").default("{user}({id}) 申请加入群 {gname}({group})\n申请理由: {question}\n匹配情况: {answer}/{threshold}\n使用 gva 同意或 gvr 拒绝申请"),
|
|
49
49
|
enableStrictGroupCheck: import_koishi.Schema.boolean().description("是否启用严格的群号检查(检查群号长度)").default(false),
|
|
50
50
|
logLevel: import_koishi.Schema.union(["debug", "info", "warn", "error"]).description("日志级别").default("info"),
|
|
51
|
-
permissionDeniedMessage: import_koishi.Schema.string().description("权限不足时返回给调用者的提示").default("
|
|
51
|
+
permissionDeniedMessage: import_koishi.Schema.string().description("权限不足时返回给调用者的提示").default("权限不足: 需要群主/管理员权限或koishi三级以上权限"),
|
|
52
52
|
invalidGroupMessage: import_koishi.Schema.string().description("无效群号或机器人未在该群时的提示").default("群号 {group} 格式不合法或机器人不在该群中"),
|
|
53
|
-
parameterConflictMessage: import_koishi.Schema.string().description("参数冲突时提示").default("
|
|
53
|
+
parameterConflictMessage: import_koishi.Schema.string().description("参数冲突时提示").default("参数冲突: -? 或 -r 不能与其他参数或关键词一起使用(仅可搭配 -i)"),
|
|
54
54
|
noKeywordsMessage: import_koishi.Schema.string().description("未提供关键词且无法从现有配置继承时的提示").default("请先提供关键词创建配置,或使用 -? 查询配置,-r 删除配置"),
|
|
55
55
|
blacklistAddSuccess: import_koishi.Schema.string().description("将用户加入黑名单后的提示,可使用 {user},{group},{reason}").default("已将用户 {user} 加入群 {group} 黑名单{reason}"),
|
|
56
56
|
blacklistRemoveSuccess: import_koishi.Schema.string().description("从黑名单移除用户后的提示,可使用 {user},{group}").default("已从群 {group} 的黑名单中移除用户 {user}"),
|
|
@@ -162,25 +162,31 @@ function validateKeywordFormat(raw) {
|
|
|
162
162
|
}
|
|
163
163
|
__name(validateKeywordFormat, "validateKeywordFormat");
|
|
164
164
|
function usageString() {
|
|
165
|
-
return
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
165
|
+
return `用法:
|
|
166
|
+
-i 群号
|
|
167
|
+
-m (0|1|2|3)审核方式
|
|
168
|
+
-t 阈值
|
|
169
|
+
-msg [可选,消息内容] 并打开提醒消息
|
|
170
|
+
-nomsg 取消提醒消息(与-msg冲突)
|
|
171
|
+
-r 删除配置
|
|
172
|
+
-? 查询配置
|
|
169
173
|
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
对于存在空格的关键词/提醒消息用双引号""包裹
|
|
175
|
+
|
|
176
|
+
# 例
|
|
177
|
+
gvc 关键词1,关键词2 -m 1 -t 2 # 创建配置
|
|
178
|
+
gvc -msg "消息内容" # 修改提醒消息
|
|
172
179
|
gvc -nomsg # 禁用提醒消息
|
|
173
|
-
# 查询/删除
|
|
174
180
|
gvc -? # 查询配置
|
|
175
181
|
gvc -r # 删除配置
|
|
176
182
|
|
|
177
|
-
审核方式说明(使用 -m
|
|
183
|
+
审核方式说明(使用 -m 参数):
|
|
178
184
|
0 全部同意(默认)
|
|
179
|
-
1 按数量同意,需要 -t 指定数量
|
|
185
|
+
1 按数量同意,需要 -t 指定数量
|
|
180
186
|
2 按比例同意,需要 -t 指定百分比
|
|
181
|
-
3
|
|
187
|
+
3 全部拒绝
|
|
182
188
|
|
|
183
|
-
|
|
189
|
+
提醒消息可用变量: {user} 用户名 {id} 用户ID
|
|
184
190
|
{group} 群号 {gname} 群名称
|
|
185
191
|
{question} 申请理由 {answer} 匹配情况 {threshold} 阈值
|
|
186
192
|
使用 \\n 换行`;
|
|
@@ -188,7 +194,7 @@ function usageString() {
|
|
|
188
194
|
__name(usageString, "usageString");
|
|
189
195
|
function mergeReminder(existingConfig, cleanedOptions, hasRealMessageParam, hasRealEnableMessageParam, hasRealDisableMessageParam, logger2, defaultMessage) {
|
|
190
196
|
let reminderEnabled = true;
|
|
191
|
-
let reminderMessage = defaultMessage || "{user}({id}) 申请加入群 {gname}({group})\n
|
|
197
|
+
let reminderMessage = defaultMessage || "{user}({id}) 申请加入群 {gname}({group})\n申请理由: {question}\n匹配情况: {answer}/{threshold}";
|
|
192
198
|
if (existingConfig) {
|
|
193
199
|
reminderEnabled = existingConfig.reminderEnabled;
|
|
194
200
|
reminderMessage = existingConfig.reminderMessage || reminderMessage;
|
|
@@ -516,7 +522,7 @@ function parseConfigArgs(raw) {
|
|
|
516
522
|
}
|
|
517
523
|
const keywordSection = raw.split(/(?:^|\s+)-(?:i|m|t|msg|nomsg|\?|r)\b/)[0].trim();
|
|
518
524
|
if (keywordSection && !validateKeywordFormat(keywordSection)) {
|
|
519
|
-
error = '
|
|
525
|
+
error = '关键词应使用逗号分隔或引号框起(如: k1,k2,k3 或 "k1","k2" 或 "k1,k2",k3)';
|
|
520
526
|
}
|
|
521
527
|
return { keywords, flags, error };
|
|
522
528
|
}
|
|
@@ -645,7 +651,7 @@ async function processBlacklistCommand(ctx, session, rawArgs, config) {
|
|
|
645
651
|
const op = parts[0]?.toLowerCase();
|
|
646
652
|
if (!op || !["a", "r", "l", "i"].includes(op)) {
|
|
647
653
|
return [
|
|
648
|
-
"
|
|
654
|
+
"用法: ",
|
|
649
655
|
" gvb a id [reason] [group] 将用户加入黑名单",
|
|
650
656
|
" gvb r id [group] 将用户移出黑名单",
|
|
651
657
|
" gvb l [group] 查询群黑名单",
|
|
@@ -693,7 +699,7 @@ async function processBlacklistCommand(ctx, session, rawArgs, config) {
|
|
|
693
699
|
const row = rows[0];
|
|
694
700
|
const entries = row.entries || {};
|
|
695
701
|
if (entries[targetUser] !== void 0) {
|
|
696
|
-
return `用户 ${targetUser} 已在群 ${group}
|
|
702
|
+
return `用户 ${targetUser} 已在群 ${group} 的黑名单中: ${entries[targetUser]}`;
|
|
697
703
|
}
|
|
698
704
|
entries[targetUser] = storedReason;
|
|
699
705
|
await ctx.database.set("group_verification_blacklist", { id: row.id }, { entries });
|
|
@@ -711,7 +717,7 @@ async function processBlacklistCommand(ctx, session, rawArgs, config) {
|
|
|
711
717
|
}
|
|
712
718
|
}
|
|
713
719
|
const tmpl = config && config.blacklistAddSuccess || "已将用户 {user} 加入群 {group} 黑名单{reason}";
|
|
714
|
-
return tmpl.replace("{user}", targetUser).replace("{group}", group).replace("{reason}", reason ?
|
|
720
|
+
return tmpl.replace("{user}", targetUser).replace("{group}", group).replace("{reason}", reason ? `,原因: ${reason}` : "");
|
|
715
721
|
}
|
|
716
722
|
if (op === "r") {
|
|
717
723
|
targetUser = parts[1];
|
|
@@ -768,7 +774,7 @@ async function processBlacklistCommand(ctx, session, rawArgs, config) {
|
|
|
768
774
|
`;
|
|
769
775
|
let msg = prefix;
|
|
770
776
|
for (const uid in entries) {
|
|
771
|
-
msg += `${uid}${entries[uid] ?
|
|
777
|
+
msg += `${uid}${entries[uid] ? `: ${entries[uid]}` : ""}
|
|
772
778
|
`;
|
|
773
779
|
}
|
|
774
780
|
return msg;
|
|
@@ -802,7 +808,7 @@ async function processBlacklistCommand(ctx, session, rawArgs, config) {
|
|
|
802
808
|
}
|
|
803
809
|
if (groupArg.toLowerCase() === "all") {
|
|
804
810
|
const auth = session.author?.authority || session.user?.authority;
|
|
805
|
-
if (!(auth && auth >= 3)) return "
|
|
811
|
+
if (!(auth && auth >= 3)) return "权限不足: 查看全局/所有群黑名单需要 koishi 3 级以上权限";
|
|
806
812
|
const rows2 = await ctx.database.get("group_verification_blacklist", {});
|
|
807
813
|
const globalReason2 = globalHit ? globalRows[0].entries[targetUser] : "无";
|
|
808
814
|
const lines = [];
|
|
@@ -882,7 +888,7 @@ function apply(ctx, config) {
|
|
|
882
888
|
userId: "string",
|
|
883
889
|
userName: "string",
|
|
884
890
|
requestMessage: "string",
|
|
885
|
-
//
|
|
891
|
+
// 保存 OneBot 事件提供的原始 requestId;用于同意/拒绝操作
|
|
886
892
|
requestId: "string",
|
|
887
893
|
// record full timestamp as string to keep time component
|
|
888
894
|
applyTime: "string"
|
|
@@ -987,7 +993,7 @@ function apply(ctx, config) {
|
|
|
987
993
|
}
|
|
988
994
|
logger.debug(`权限检查 - 权限不足`);
|
|
989
995
|
const debugInfo = `调试信息 - 用户ID:${session.userId}, 群号:${groupId}, 权限等级:${koishiAuthority || "未知"}`;
|
|
990
|
-
return [false, (config.permissionDeniedMessage || "
|
|
996
|
+
return [false, (config.permissionDeniedMessage || "权限不足: 需要群主/管理员权限或koishi三级以上权限") + `
|
|
991
997
|
${debugInfo}`];
|
|
992
998
|
}
|
|
993
999
|
__name(checkPermission2, "checkPermission");
|
|
@@ -1017,14 +1023,14 @@ ${debugInfo}`];
|
|
|
1017
1023
|
threshold: flags.threshold || options.threshold,
|
|
1018
1024
|
message: flags.message !== void 0 ? flags.message : options.message,
|
|
1019
1025
|
enableMessage: flags.enableMessage,
|
|
1020
|
-
//
|
|
1026
|
+
// 新增: -msg 裸调用标记
|
|
1021
1027
|
disableMessage: flags.disableMessage || options.disableMessage,
|
|
1022
1028
|
query: flags.query || options.query,
|
|
1023
1029
|
remove: flags.remove || options.remove
|
|
1024
1030
|
};
|
|
1025
1031
|
logger.info(`合并后options: ${JSON.stringify(cleanedOptions, null, 2)}`);
|
|
1026
1032
|
if ((cleanedOptions.query || cleanedOptions.remove) && (parsedKeywords.length > 0 || cleanedOptions.method !== void 0 || cleanedOptions.threshold !== void 0 || cleanedOptions.message !== void 0 || cleanedOptions.enableMessage || cleanedOptions.disableMessage)) {
|
|
1027
|
-
return config.parameterConflictMessage || "
|
|
1033
|
+
return config.parameterConflictMessage || "参数冲突: -? 或 -r 不能与其他参数或关键词一起使用(仅可搭配 -i)";
|
|
1028
1034
|
}
|
|
1029
1035
|
const hasRealMessageParam = cleanedOptions.message !== void 0;
|
|
1030
1036
|
const hasRealEnableMessageParam = cleanedOptions.enableMessage === true;
|
|
@@ -1049,7 +1055,7 @@ ${debugInfo}`];
|
|
|
1049
1055
|
return "请在群聊中使用此命令或指定群号(-i参数)";
|
|
1050
1056
|
}
|
|
1051
1057
|
if ((hasRealMessageParam || hasRealEnableMessageParam) && hasRealDisableMessageParam) {
|
|
1052
|
-
return "
|
|
1058
|
+
return "参数冲突: 不能同时使用 -msg 和 -nomsg";
|
|
1053
1059
|
}
|
|
1054
1060
|
const usedOptions = [];
|
|
1055
1061
|
if (cleanedOptions.method !== void 0) usedOptions.push("-m");
|
|
@@ -1107,7 +1113,7 @@ ${debugInfo}`];
|
|
|
1107
1113
|
const createTime = new Date(config2.createdAt).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
|
|
1108
1114
|
const updateTime = new Date(config2.updatedAt).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
|
|
1109
1115
|
const reminderStatus = config2.reminderEnabled ? "启用" : "禁用";
|
|
1110
|
-
return `群 ${targetGroupId}
|
|
1116
|
+
return `群 ${targetGroupId} 配置:
|
|
1111
1117
|
关键词: ${decodedKeywords2.join(", ")}
|
|
1112
1118
|
审核方式: ${methodDesc}
|
|
1113
1119
|
阈值: ${thresholdInfo}
|
|
@@ -1164,7 +1170,7 @@ ${config2.reminderMessage || "无"}
|
|
|
1164
1170
|
if (cleanedOptions.method !== void 0 && cleanedOptions.method !== "") {
|
|
1165
1171
|
const methodNum = parseInt(cleanedOptions.method);
|
|
1166
1172
|
if (isNaN(methodNum) || methodNum < 0 || methodNum > 3) {
|
|
1167
|
-
return "
|
|
1173
|
+
return "审核方式参数错误: 0-全部同意, 1-按数量同意, 2-按比例同意, 3-全部拒绝";
|
|
1168
1174
|
}
|
|
1169
1175
|
const oldMethod = reviewMethod;
|
|
1170
1176
|
reviewMethod = methodNum;
|
|
@@ -1455,7 +1461,7 @@ ${reminderMessage}
|
|
|
1455
1461
|
const isGroupId = target && /^\d+$/.test(target);
|
|
1456
1462
|
const isSpecialTarget = target && validTargets.includes(target.toLowerCase());
|
|
1457
1463
|
if (target && !isGroupId && !isSpecialTarget) {
|
|
1458
|
-
return "
|
|
1464
|
+
return "参数错误: 只能指定群号、all、total或留空";
|
|
1459
1465
|
}
|
|
1460
1466
|
if (target?.toLowerCase() === "total" || target?.toLowerCase() === "all") {
|
|
1461
1467
|
const koishiAuthority = session.author?.authority || session.user?.authority;
|
|
@@ -1496,11 +1502,11 @@ ${reminderMessage}
|
|
|
1496
1502
|
if (pendingApplications.length === 0) {
|
|
1497
1503
|
return "当前没有待审核的加群申请";
|
|
1498
1504
|
}
|
|
1499
|
-
let result = "
|
|
1505
|
+
let result = "待审核申请列表: \n";
|
|
1500
1506
|
pendingApplications.forEach((app, index) => {
|
|
1501
1507
|
result += `${index + 1}. ${app.userName}(${app.userId})
|
|
1502
|
-
|
|
1503
|
-
|
|
1508
|
+
申请时间: ${app.applyTime.toLocaleString()}
|
|
1509
|
+
申请理由: ${app.requestMessage}
|
|
1504
1510
|
|
|
1505
1511
|
`;
|
|
1506
1512
|
});
|
|
@@ -1518,19 +1524,19 @@ ${reminderMessage}
|
|
|
1518
1524
|
return await processBlacklistCommand(ctx, session, args || "", config);
|
|
1519
1525
|
});
|
|
1520
1526
|
groupVerify.subcommand(".help", "显示帮助信息").alias("gv.帮助", "gverify.帮助", "group-verify.帮助", "帮助", "hlp", "帮助信息").action(() => {
|
|
1521
|
-
return
|
|
1522
|
-
|
|
1527
|
+
return `群组验证命令帮助:
|
|
1528
|
+
主指令别名: gv, gverify
|
|
1523
1529
|
|
|
1524
1530
|
配置命令 (.config/.cfg):
|
|
1525
|
-
|
|
1526
|
-
1.
|
|
1527
|
-
2.
|
|
1528
|
-
3.
|
|
1529
|
-
4.
|
|
1530
|
-
5.
|
|
1531
|
-
6.
|
|
1531
|
+
用法:
|
|
1532
|
+
1. 创建新配置: gv.cfg 关键词1,关键词2 -m 1 -t 2
|
|
1533
|
+
2. 修改现有配置: gv.cfg -m 1 -t 2 (不提供关键词)
|
|
1534
|
+
3. 启用提醒消息: gv.cfg -msg "消息内容"
|
|
1535
|
+
4. 禁用提醒消息: gv.cfg -nomsg
|
|
1536
|
+
5. 查询配置: gv.cfg -?
|
|
1537
|
+
6. 删除配置: gv.cfg -r
|
|
1532
1538
|
|
|
1533
|
-
|
|
1539
|
+
参数说明:
|
|
1534
1540
|
-i <群号> 指定群号(私聊时必需)
|
|
1535
1541
|
-m <方式> 审核方式 (0=全部同意, 1=按数量, 2=按比例, 3=全部拒绝)
|
|
1536
1542
|
-t <阈值> 阈值参数(方式1:0-关键词数, 方式2:0-100)
|
|
@@ -1542,19 +1548,19 @@ ${reminderMessage}
|
|
|
1542
1548
|
-? 查询当前配置
|
|
1543
1549
|
-r 删除配置
|
|
1544
1550
|
|
|
1545
|
-
|
|
1546
|
-
•
|
|
1547
|
-
•
|
|
1548
|
-
•
|
|
1549
|
-
•
|
|
1551
|
+
引号使用规则:
|
|
1552
|
+
• 关键词包含空格: gv.cfg "关键词1,关键词 2,关键词3"
|
|
1553
|
+
• 提醒消息包含空格: gv.cfg -msg "这是包含空格的消息"
|
|
1554
|
+
• 内部引号转义: gv.cfg -msg "包含\\"引号\\"的内容"
|
|
1555
|
+
• 换行符: gv.cfg -msg "第一行\\n第二行"
|
|
1550
1556
|
|
|
1551
|
-
|
|
1557
|
+
特殊说明:
|
|
1552
1558
|
• 阈值可设为0表示全部同意
|
|
1553
1559
|
• 关键词数量变化时阈值会自动调整
|
|
1554
1560
|
• 重复参数会使用最后出现的值并提醒
|
|
1555
1561
|
• 群号检查可在插件配置中开关
|
|
1556
1562
|
|
|
1557
|
-
|
|
1563
|
+
提醒消息变量:
|
|
1558
1564
|
{user} - 用户名
|
|
1559
1565
|
{id} - 用户ID
|
|
1560
1566
|
{group} - 群号
|
|
@@ -1563,9 +1569,9 @@ ${reminderMessage}
|
|
|
1563
1569
|
{answer} - 答对数量/比例
|
|
1564
1570
|
{threshold} - 阈值要求
|
|
1565
1571
|
|
|
1566
|
-
|
|
1572
|
+
使用示例:
|
|
1567
1573
|
gv.cfg "关键词1,关键词2" -m 1 -t 2
|
|
1568
|
-
gv.cfg -msg "用户 {user}\\n
|
|
1574
|
+
gv.cfg -msg "用户 {user}\\n申请理由: {question}"
|
|
1569
1575
|
gv.cfg -m 2 -t 80
|
|
1570
1576
|
gv.cfg -nomsg
|
|
1571
1577
|
|
|
@@ -1575,21 +1581,21 @@ ${reminderMessage}
|
|
|
1575
1581
|
l [group] 查询群黑名单;传入 all 时查看全局黑名单
|
|
1576
1582
|
i id [group] 查询账号黑名单;不带参数时查询本群与全局,
|
|
1577
1583
|
指定群号查询该群与全局,传入 all 则列出所有群及全局(需Koishi 3级)
|
|
1578
|
-
|
|
1584
|
+
使用示例:
|
|
1579
1585
|
gvb a 12345 作弊记录
|
|
1580
1586
|
gvb r 12345 67890
|
|
1581
1587
|
gvb l
|
|
1582
1588
|
gvb l all
|
|
1583
1589
|
gvb i 12345
|
|
1584
1590
|
|
|
1585
|
-
|
|
1591
|
+
快捷命令:
|
|
1586
1592
|
gvc - 配置命令快捷方式
|
|
1587
1593
|
gva - 同意申请快捷命令
|
|
1588
1594
|
gvr - 拒绝申请快捷命令
|
|
1589
1595
|
gvp - 查看待审核列表快捷命令
|
|
1590
1596
|
gvs - 查看统计信息快捷命令
|
|
1591
1597
|
|
|
1592
|
-
|
|
1598
|
+
权限说明:
|
|
1593
1599
|
- 群主/管理员权限
|
|
1594
1600
|
- koishi三级以上权限
|
|
1595
1601
|
- 私聊时必须指定群号(-i参数)`;
|
|
@@ -1649,7 +1655,7 @@ ${reminderMessage}
|
|
|
1649
1655
|
}
|
|
1650
1656
|
const stat = stats[0];
|
|
1651
1657
|
const lastUpdated = new Date(stat.lastUpdated).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
|
|
1652
|
-
return `群 ${groupId}
|
|
1658
|
+
return `群 ${groupId} 验证统计:
|
|
1653
1659
|
自动批准: ${stat.autoApproved}
|
|
1654
1660
|
手动批准: ${stat.manuallyApproved}
|
|
1655
1661
|
拒绝: ${stat.rejected}
|
|
@@ -1663,7 +1669,7 @@ ${reminderMessage}
|
|
|
1663
1669
|
}
|
|
1664
1670
|
const stat = stats[0];
|
|
1665
1671
|
const lastUpdated = new Date(stat.lastUpdated).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
|
|
1666
|
-
return
|
|
1672
|
+
return `总计验证统计:
|
|
1667
1673
|
自动批准: ${stat.autoApproved}
|
|
1668
1674
|
手动批准: ${stat.manuallyApproved}
|
|
1669
1675
|
拒绝: ${stat.rejected}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-group-verification",
|
|
3
|
-
"description": "Koishi
|
|
3
|
+
"description": "Koishi 群组加群验证插件,支持多关键词匹配审核、多种审核方式、黑名单和详细统计功能",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/LHDyx/koishi-plugin-group-verification.git"
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"bugs": {
|
|
9
9
|
"url": "https://github.com/LHDyx/koishi-plugin-group-verification/issues"
|
|
10
10
|
},
|
|
11
|
-
"version": "1.0.
|
|
11
|
+
"version": "1.0.34",
|
|
12
12
|
"main": "lib/index.js",
|
|
13
13
|
"typings": "lib/index.d.ts",
|
|
14
14
|
"files": [
|
package/readme.md
CHANGED
|
@@ -134,6 +134,22 @@ group-verify.blacklist i <用户ID> [群号|all]
|
|
|
134
134
|
|
|
135
135
|
## ⚙️ 参数说明
|
|
136
136
|
|
|
137
|
+
### 日志级别配置
|
|
138
|
+
|
|
139
|
+
插件启动时会输出运行状态,并根据 `logLevel` 调整输出量。
|
|
140
|
+
- `debug`:打印所有调试细节,包括权限检查、命令解析等。
|
|
141
|
+
- `info`:默认值,记录关键事件(插件启动、配置修改、自动拒绝、黑名单踢人等)。
|
|
142
|
+
- `warn`:记录可恢复的异常,例如尝试踢出用户失败、数据库操作问题。
|
|
143
|
+
- `error`:仅在遇到严重错误时输出。
|
|
144
|
+
|
|
145
|
+
添加黑名单时会尝试在对应群踢出该用户,成功记为 `info`,失败记为 `warn`。
|
|
146
|
+
|
|
147
|
+
### 严格群号检查
|
|
148
|
+
|
|
149
|
+
`enableStrictGroupCheck` 可开启简单群号格式验证(长度 5‑15 位),
|
|
150
|
+
影响所有需要群号的命令。
|
|
151
|
+
|
|
152
|
+
|
|
137
153
|
### 审核方式 (-m)
|
|
138
154
|
|
|
139
155
|
*如果改变审核方式而未提供 `-t`,阈值会自动设置为最大值(方式1为关键词数量,方式2为100)。*
|
package/src/index.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface GroupVerificationConfig {
|
|
|
21
21
|
groupId: string
|
|
22
22
|
keywords: string[]
|
|
23
23
|
reviewMethod: 0 | 1 | 2 | 3 // 0:全部同意, 1:按数量同意, 2:按比例同意, 3:全部拒绝
|
|
24
|
-
reviewParameters: number //
|
|
24
|
+
reviewParameters: number // 直接存储数字: 0表示无阈值,其他表示具体阈值
|
|
25
25
|
reminderEnabled: boolean // 是否启用提醒消息
|
|
26
26
|
reminderMessage: string
|
|
27
27
|
createdBy: string
|
|
@@ -37,12 +37,12 @@ export interface GroupVerificationStats {
|
|
|
37
37
|
autoApproved: number
|
|
38
38
|
manuallyApproved: number
|
|
39
39
|
rejected: number
|
|
40
|
-
//
|
|
40
|
+
// 新增: 总入群人数(不论方式,只要检测到成员加入则增加)
|
|
41
41
|
totalJoined: number
|
|
42
42
|
lastUpdated: string | Date
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
//
|
|
45
|
+
// 黑名单行: 每个群/全局对应一条记录,entries 保存 userId->reason 对象
|
|
46
46
|
export interface GroupBlacklistEntry {
|
|
47
47
|
id: number
|
|
48
48
|
groupId: string // 群号或 "all" 表示全局
|
|
@@ -57,7 +57,7 @@ export interface PendingVerification {
|
|
|
57
57
|
userId: string
|
|
58
58
|
userName: string
|
|
59
59
|
requestMessage: string
|
|
60
|
-
//
|
|
60
|
+
// 原始 OneBot requestId(字符串可能为空)
|
|
61
61
|
requestId?: string
|
|
62
62
|
applyTime: string | Date
|
|
63
63
|
}
|
|
@@ -81,12 +81,12 @@ export interface Config {
|
|
|
81
81
|
export const Config: Schema<Config> = Schema.object({
|
|
82
82
|
defaultReminderMessage: Schema.string()
|
|
83
83
|
.description('默认提醒消息模板(使用 \\n 表示换行,可包含下方变量)')
|
|
84
|
-
.default('{user}({id}) 申请加入群 {gname}({group})\n
|
|
84
|
+
.default('{user}({id}) 申请加入群 {gname}({group})\n申请理由: {question}\n匹配情况: {answer}/{threshold}\n使用 gva 同意或 gvr 拒绝申请'),
|
|
85
85
|
enableStrictGroupCheck: Schema.boolean().description('是否启用严格的群号检查(检查群号长度)').default(false),
|
|
86
86
|
logLevel: Schema.union(['debug', 'info', 'warn', 'error']).description('日志级别').default('info'),
|
|
87
|
-
permissionDeniedMessage: Schema.string().description('权限不足时返回给调用者的提示').default('
|
|
87
|
+
permissionDeniedMessage: Schema.string().description('权限不足时返回给调用者的提示').default('权限不足: 需要群主/管理员权限或koishi三级以上权限'),
|
|
88
88
|
invalidGroupMessage: Schema.string().description('无效群号或机器人未在该群时的提示').default('群号 {group} 格式不合法或机器人不在该群中'),
|
|
89
|
-
parameterConflictMessage: Schema.string().description('参数冲突时提示').default('
|
|
89
|
+
parameterConflictMessage: Schema.string().description('参数冲突时提示').default('参数冲突: -? 或 -r 不能与其他参数或关键词一起使用(仅可搭配 -i)'),
|
|
90
90
|
noKeywordsMessage: Schema.string().description('未提供关键词且无法从现有配置继承时的提示').default('请先提供关键词创建配置,或使用 -? 查询配置,-r 删除配置'),
|
|
91
91
|
blacklistAddSuccess: Schema.string().description('将用户加入黑名单后的提示,可使用 {user},{group},{reason}').default('已将用户 {user} 加入群 {group} 黑名单{reason}'),
|
|
92
92
|
blacklistRemoveSuccess: Schema.string().description('从黑名单移除用户后的提示,可使用 {user},{group}').default('已从群 {group} 的黑名单中移除用户 {user}'),
|
|
@@ -104,11 +104,11 @@ export const inject = ['database']
|
|
|
104
104
|
*/
|
|
105
105
|
export interface TokenizeResult {
|
|
106
106
|
tokens: string[];
|
|
107
|
-
seps: string[]; //
|
|
107
|
+
seps: string[]; // 每个令牌前面的分隔符: ' ' 或 ',' 或 ''(表示开始)
|
|
108
108
|
error?: string;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
//
|
|
111
|
+
// 内部哨兵字符,用于区分转义的引号或反斜杠
|
|
112
112
|
const ESC_QUOTE = '\u0000';
|
|
113
113
|
const ESC_BACKSLASH = '\u0001';
|
|
114
114
|
|
|
@@ -116,7 +116,7 @@ export function tokenize(input: string): TokenizeResult {
|
|
|
116
116
|
const tokens: string[] = [];
|
|
117
117
|
const seps: string[] = [];
|
|
118
118
|
let cur = '';
|
|
119
|
-
let lastSep = ''; //
|
|
119
|
+
let lastSep = ''; // 当前令牌之前的分隔符
|
|
120
120
|
let i = 0;
|
|
121
121
|
|
|
122
122
|
const flush = () => {
|
|
@@ -131,7 +131,7 @@ export function tokenize(input: string): TokenizeResult {
|
|
|
131
131
|
while (i < input.length) {
|
|
132
132
|
const ch = input[i];
|
|
133
133
|
if (ch === ' ' || ch === ',') {
|
|
134
|
-
//
|
|
134
|
+
// 记录下一个令牌的分隔符
|
|
135
135
|
lastSep = ch;
|
|
136
136
|
flush();
|
|
137
137
|
i++;
|
|
@@ -224,9 +224,9 @@ export interface ParsedArgs {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
/**
|
|
227
|
-
*
|
|
227
|
+
* 验证关键词格式: 仅允许用逗号和引号分隔,禁止纯空格分隔
|
|
228
228
|
*/
|
|
229
|
-
//
|
|
229
|
+
// 解析过程中使用的辅助函数
|
|
230
230
|
function validateKeywordFormat(raw: string): boolean {
|
|
231
231
|
if (raw.includes(',') || raw.includes('"')) {
|
|
232
232
|
return true;
|
|
@@ -251,25 +251,31 @@ function validateKeywordFormat(raw: string): boolean {
|
|
|
251
251
|
* 对于 -nomsg 不会清除已保存的 message,便于后续再次启用时恢复。
|
|
252
252
|
*/
|
|
253
253
|
export function usageString(): string {
|
|
254
|
-
return
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
254
|
+
return `用法:
|
|
255
|
+
-i 群号
|
|
256
|
+
-m (0|1|2|3)审核方式
|
|
257
|
+
-t 阈值
|
|
258
|
+
-msg [可选,消息内容] 并打开提醒消息
|
|
259
|
+
-nomsg 取消提醒消息(与-msg冲突)
|
|
260
|
+
-r 删除配置
|
|
261
|
+
-? 查询配置
|
|
262
|
+
|
|
263
|
+
对于存在空格的关键词/提醒消息用双引号""包裹
|
|
264
|
+
|
|
265
|
+
# 例
|
|
266
|
+
gvc 关键词1,关键词2 -m 1 -t 2 # 创建配置
|
|
267
|
+
gvc -msg "消息内容" # 修改提醒消息
|
|
261
268
|
gvc -nomsg # 禁用提醒消息
|
|
262
|
-
# 查询/删除
|
|
263
269
|
gvc -? # 查询配置
|
|
264
270
|
gvc -r # 删除配置
|
|
265
271
|
|
|
266
|
-
审核方式说明(使用 -m
|
|
272
|
+
审核方式说明(使用 -m 参数):
|
|
267
273
|
0 全部同意(默认)
|
|
268
|
-
1 按数量同意,需要 -t 指定数量
|
|
274
|
+
1 按数量同意,需要 -t 指定数量
|
|
269
275
|
2 按比例同意,需要 -t 指定百分比
|
|
270
|
-
3
|
|
276
|
+
3 全部拒绝
|
|
271
277
|
|
|
272
|
-
|
|
278
|
+
提醒消息可用变量: {user} 用户名 {id} 用户ID
|
|
273
279
|
{group} 群号 {gname} 群名称
|
|
274
280
|
{question} 申请理由 {answer} 匹配情况 {threshold} 阈值
|
|
275
281
|
使用 \\n 换行`;
|
|
@@ -290,7 +296,7 @@ export function mergeReminder(
|
|
|
290
296
|
) {
|
|
291
297
|
let reminderEnabled = true;
|
|
292
298
|
// 优先使用传入的默认模板,其次使用已有配置,再 fallback 到老写死的样式
|
|
293
|
-
let reminderMessage = defaultMessage || '{user}({id}) 申请加入群 {gname}({group})\n
|
|
299
|
+
let reminderMessage = defaultMessage || '{user}({id}) 申请加入群 {gname}({group})\n申请理由: {question}\n匹配情况: {answer}/{threshold}';
|
|
294
300
|
|
|
295
301
|
if (existingConfig) {
|
|
296
302
|
reminderEnabled = existingConfig.reminderEnabled;
|
|
@@ -298,7 +304,7 @@ export function mergeReminder(
|
|
|
298
304
|
reminderMessage = existingConfig.reminderMessage || reminderMessage;
|
|
299
305
|
}
|
|
300
306
|
|
|
301
|
-
//
|
|
307
|
+
// 优先级: disable > bare enable > new message content
|
|
302
308
|
if (hasRealDisableMessageParam) {
|
|
303
309
|
reminderEnabled = false;
|
|
304
310
|
logger.debug('禁用提醒消息功能 (保留现有内容)');
|
|
@@ -321,7 +327,7 @@ export function mergeReminder(
|
|
|
321
327
|
* 返回关键词数组和各类 flag 的值,未出现的 flag 保持 undefined。
|
|
322
328
|
* 若检测到格式错误(如纯空格分隔关键词),返回 error 字段。
|
|
323
329
|
*/
|
|
324
|
-
//
|
|
330
|
+
// 全局缓存: 记录通过机器人自动批准的用户,供 guild-member-added 事件使用
|
|
325
331
|
const autoQueue = new Map<string, Set<string>>();
|
|
326
332
|
|
|
327
333
|
// 更新统计信息函数,提取到模块层供多个位置调用
|
|
@@ -381,7 +387,7 @@ export async function updateStats(ctx: Context, groupId: string, action: 'autoAp
|
|
|
381
387
|
await syncTotalStats(ctx)
|
|
382
388
|
}
|
|
383
389
|
|
|
384
|
-
//
|
|
390
|
+
// 提取成独立函数: 增加总入群计数,供事件统一调用
|
|
385
391
|
export async function incrementTotal(ctx: Context, groupId: string) {
|
|
386
392
|
const existingStats = await ctx.database.get('group_verification_stats', { groupId })
|
|
387
393
|
if (existingStats.length > 0) {
|
|
@@ -403,7 +409,8 @@ export async function incrementTotal(ctx: Context, groupId: string) {
|
|
|
403
409
|
await syncTotalStats(ctx)
|
|
404
410
|
}
|
|
405
411
|
|
|
406
|
-
//
|
|
412
|
+
// 根据已有配置、关键词列表及用户输入确定阈值参数的辅助函数,
|
|
413
|
+
// 并处理审核方式更改所致的自动调整
|
|
407
414
|
// and whether the audit method has been changed by the command.
|
|
408
415
|
export interface ThresholdResult {
|
|
409
416
|
reviewParameters: number
|
|
@@ -525,7 +532,7 @@ export async function checkPermission(session: any, targetGroupId?: string): Pro
|
|
|
525
532
|
return [false, '权限不足']
|
|
526
533
|
}
|
|
527
534
|
|
|
528
|
-
//
|
|
535
|
+
// 提供给测试的辅助函数: 处理 guild-member-request 事件的逻辑
|
|
529
536
|
export async function handleGuildMemberRequestEvent(ctx: Context, session: any) {
|
|
530
537
|
logger.debug('guild-member-request event', session)
|
|
531
538
|
let guildId = (session.guildId || session.channelId || '').toString().trim();
|
|
@@ -603,7 +610,7 @@ export function parseConfigArgs(raw: string): ParsedArgs {
|
|
|
603
610
|
|
|
604
611
|
const isFlag = (tok: string) => /^-(?:i|m|t|msg|nomsg|\?|r)$/.test(tok);
|
|
605
612
|
|
|
606
|
-
//
|
|
613
|
+
// 在还原哨兵字符之前处理未转义的杂散引号
|
|
607
614
|
for (const tok of tokens) {
|
|
608
615
|
if (tok.includes('"')) {
|
|
609
616
|
error = '存在未转义的引号';
|
|
@@ -611,7 +618,7 @@ export function parseConfigArgs(raw: string): ParsedArgs {
|
|
|
611
618
|
}
|
|
612
619
|
}
|
|
613
620
|
|
|
614
|
-
//
|
|
621
|
+
// 将哨兵占位符还原为真实字符
|
|
615
622
|
tokens = tokens.map(t =>
|
|
616
623
|
t.replace(new RegExp(ESC_QUOTE, 'g'), '"').replace(new RegExp(ESC_BACKSLASH, 'g'), '\\')
|
|
617
624
|
);
|
|
@@ -686,7 +693,7 @@ export function parseConfigArgs(raw: string): ParsedArgs {
|
|
|
686
693
|
// remove everything from the first flag onward, including when flag sits at start
|
|
687
694
|
const keywordSection = raw.split(/(?:^|\s+)-(?:i|m|t|msg|nomsg|\?|r)\b/)[0].trim();
|
|
688
695
|
if (keywordSection && !validateKeywordFormat(keywordSection)) {
|
|
689
|
-
error = '
|
|
696
|
+
error = '关键词应使用逗号分隔或引号框起(如: k1,k2,k3 或 "k1","k2" 或 "k1,k2",k3)';
|
|
690
697
|
}
|
|
691
698
|
|
|
692
699
|
return { keywords, flags, error };
|
|
@@ -781,7 +788,7 @@ export async function handleFailedVerification(
|
|
|
781
788
|
// 无法获取群名称时使用默认值
|
|
782
789
|
}
|
|
783
790
|
|
|
784
|
-
//
|
|
791
|
+
// 如果可用则提取 requestId(OneBot 事件会附带)
|
|
785
792
|
const requestId = ((session.event as any)?.requestId) || session.messageId || ''
|
|
786
793
|
|
|
787
794
|
// 删除同一用户在该群之前的所有待审核记录,保留最新一个
|
|
@@ -868,7 +875,7 @@ export async function processBlacklistCommand(ctx: Context, session: any, rawArg
|
|
|
868
875
|
if (!op || !['a','r','l','i'].includes(op)) {
|
|
869
876
|
// present a multiline Chinese usage guide without angle brackets
|
|
870
877
|
return [
|
|
871
|
-
'
|
|
878
|
+
'用法: ',
|
|
872
879
|
' gvb a id [reason] [group] 将用户加入黑名单',
|
|
873
880
|
' gvb r id [group] 将用户移出黑名单',
|
|
874
881
|
' gvb l [group] 查询群黑名单',
|
|
@@ -886,7 +893,7 @@ export async function processBlacklistCommand(ctx: Context, session: any, rawArg
|
|
|
886
893
|
targetUser = parts[1]
|
|
887
894
|
if (!targetUser) return '请提供用户ID'
|
|
888
895
|
// handle optional reason and group at end
|
|
889
|
-
//
|
|
896
|
+
// 规则: 如果只有一个附加参数,则作为 reason;两个及以上时最后一个为群号,其余拼成 reason
|
|
890
897
|
const rest = parts.slice(2)
|
|
891
898
|
if (rest.length === 1) {
|
|
892
899
|
reason = rest[0]
|
|
@@ -925,7 +932,7 @@ export async function processBlacklistCommand(ctx: Context, session: any, rawArg
|
|
|
925
932
|
const row = rows[0]
|
|
926
933
|
const entries = row.entries || {}
|
|
927
934
|
if (entries[targetUser] !== undefined) {
|
|
928
|
-
return `用户 ${targetUser} 已在群 ${group}
|
|
935
|
+
return `用户 ${targetUser} 已在群 ${group} 的黑名单中: ${entries[targetUser]}`
|
|
929
936
|
}
|
|
930
937
|
entries[targetUser] = storedReason
|
|
931
938
|
await ctx.database.set('group_verification_blacklist', { id: row.id }, { entries })
|
|
@@ -944,7 +951,7 @@ export async function processBlacklistCommand(ctx: Context, session: any, rawArg
|
|
|
944
951
|
}
|
|
945
952
|
}
|
|
946
953
|
const tmpl = (config && config.blacklistAddSuccess) || '已将用户 {user} 加入群 {group} 黑名单{reason}'
|
|
947
|
-
return tmpl.replace('{user}', targetUser).replace('{group}', group).replace('{reason}', reason ?
|
|
954
|
+
return tmpl.replace('{user}', targetUser).replace('{group}', group).replace('{reason}', reason ? `,原因: ${reason}` : '')
|
|
948
955
|
}
|
|
949
956
|
if (op === 'r') {
|
|
950
957
|
targetUser = parts[1]
|
|
@@ -1003,7 +1010,7 @@ export async function processBlacklistCommand(ctx: Context, session: any, rawArg
|
|
|
1003
1010
|
let prefix = group && group.toLowerCase() === 'all' ? '全局黑名单: \n' : `群 ${group} 黑名单: \n`
|
|
1004
1011
|
let msg = prefix
|
|
1005
1012
|
for (const uid in entries) {
|
|
1006
|
-
msg += `${uid}${entries[uid] ?
|
|
1013
|
+
msg += `${uid}${entries[uid] ? `: ${entries[uid]}` : ''}\n`
|
|
1007
1014
|
}
|
|
1008
1015
|
return msg
|
|
1009
1016
|
}
|
|
@@ -1014,7 +1021,7 @@ export async function processBlacklistCommand(ctx: Context, session: any, rawArg
|
|
|
1014
1021
|
const globalRows = await ctx.database.get('group_verification_blacklist', { groupId: 'all' })
|
|
1015
1022
|
const globalHit = globalRows.length > 0 && (globalRows[0].entries || {})[targetUser] !== undefined
|
|
1016
1023
|
|
|
1017
|
-
//
|
|
1024
|
+
// 使用模板格式化回复的辅助函数
|
|
1018
1025
|
const tmpl = (config && config.blacklistInfoTemplate) || '全局黑名单: {global}\n本群黑名单: {group}'
|
|
1019
1026
|
const formatReply = (localHit: boolean, groupsList?: string[]) => {
|
|
1020
1027
|
if (groupsList) {
|
|
@@ -1040,7 +1047,7 @@ export async function processBlacklistCommand(ctx: Context, session: any, rawArg
|
|
|
1040
1047
|
// groupArg provided
|
|
1041
1048
|
if (groupArg.toLowerCase() === 'all') {
|
|
1042
1049
|
const auth = session.author?.authority || session.user?.authority
|
|
1043
|
-
if (!(auth && auth >= 3)) return '
|
|
1050
|
+
if (!(auth && auth >= 3)) return '权限不足: 查看全局/所有群黑名单需要 koishi 3 级以上权限'
|
|
1044
1051
|
const rows = await ctx.database.get('group_verification_blacklist', {})
|
|
1045
1052
|
const globalReason = globalHit ? globalRows[0].entries[targetUser] : '无'
|
|
1046
1053
|
const lines: string[] = []
|
|
@@ -1102,7 +1109,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1102
1109
|
if (config.logLevel) logger.level = config.logLevel
|
|
1103
1110
|
|
|
1104
1111
|
// 设置日志级别
|
|
1105
|
-
//
|
|
1112
|
+
// 注意: Koishi logger的level设置可能需要不同的方式
|
|
1106
1113
|
|
|
1107
1114
|
// 记录插件启动信息
|
|
1108
1115
|
logger.info('群组验证插件已启动')
|
|
@@ -1132,7 +1139,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1132
1139
|
userId: 'string',
|
|
1133
1140
|
userName: 'string',
|
|
1134
1141
|
requestMessage: 'string',
|
|
1135
|
-
//
|
|
1142
|
+
// 保存 OneBot 事件提供的原始 requestId;用于同意/拒绝操作
|
|
1136
1143
|
requestId: 'string',
|
|
1137
1144
|
// record full timestamp as string to keep time component
|
|
1138
1145
|
applyTime: 'string'
|
|
@@ -1141,7 +1148,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1141
1148
|
autoInc: true
|
|
1142
1149
|
})
|
|
1143
1150
|
|
|
1144
|
-
//
|
|
1151
|
+
// 黑名单表: 每条记录对应一个群(或全局),entries 存储 userId->reason 键值对
|
|
1145
1152
|
ctx.model.extend('group_verification_blacklist', {
|
|
1146
1153
|
id: 'unsigned',
|
|
1147
1154
|
groupId: 'string',
|
|
@@ -1281,7 +1288,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1281
1288
|
|
|
1282
1289
|
logger.debug(`权限检查 - 权限不足`)
|
|
1283
1290
|
const debugInfo = `调试信息 - 用户ID:${session.userId}, 群号:${groupId}, 权限等级:${koishiAuthority || '未知'}`
|
|
1284
|
-
return [false, (config.permissionDeniedMessage || '
|
|
1291
|
+
return [false, (config.permissionDeniedMessage || '权限不足: 需要群主/管理员权限或koishi三级以上权限') + `\n${debugInfo}`]
|
|
1285
1292
|
}
|
|
1286
1293
|
|
|
1287
1294
|
// Create main command with aliases
|
|
@@ -1304,7 +1311,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1304
1311
|
.option('query', '-? 查询当前配置')
|
|
1305
1312
|
.option('remove', '-r 删除配置')
|
|
1306
1313
|
.action(async ({ session, options }: any, keywords: any) => {
|
|
1307
|
-
//
|
|
1314
|
+
// 详细调试: 记录所有输入信息
|
|
1308
1315
|
logger.info(`=== 命令解析调试 ===`)
|
|
1309
1316
|
logger.info(`session内容: guildId=${session.guildId}, userId=${session.userId}`)
|
|
1310
1317
|
|
|
@@ -1329,7 +1336,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1329
1336
|
method: flags.method || (options.method === '' ? undefined : options.method),
|
|
1330
1337
|
threshold: flags.threshold || options.threshold,
|
|
1331
1338
|
message: flags.message !== undefined ? flags.message : options.message,
|
|
1332
|
-
enableMessage: flags.enableMessage, //
|
|
1339
|
+
enableMessage: flags.enableMessage, // 新增: -msg 裸调用标记
|
|
1333
1340
|
disableMessage: flags.disableMessage || options.disableMessage,
|
|
1334
1341
|
query: flags.query || options.query,
|
|
1335
1342
|
remove: flags.remove || options.remove,
|
|
@@ -1340,7 +1347,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1340
1347
|
if ((cleanedOptions.query || cleanedOptions.remove) &&
|
|
1341
1348
|
(parsedKeywords.length > 0 || cleanedOptions.method !== undefined || cleanedOptions.threshold !== undefined ||
|
|
1342
1349
|
cleanedOptions.message !== undefined || cleanedOptions.enableMessage || cleanedOptions.disableMessage)) {
|
|
1343
|
-
return config.parameterConflictMessage || '
|
|
1350
|
+
return config.parameterConflictMessage || '参数冲突: -? 或 -r 不能与其他参数或关键词一起使用(仅可搭配 -i)'
|
|
1344
1351
|
}
|
|
1345
1352
|
|
|
1346
1353
|
// 检查消息参数冲突
|
|
@@ -1377,7 +1384,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1377
1384
|
|
|
1378
1385
|
// 参数冲突检查
|
|
1379
1386
|
if ((hasRealMessageParam || hasRealEnableMessageParam) && hasRealDisableMessageParam) {
|
|
1380
|
-
return '
|
|
1387
|
+
return '参数冲突: 不能同时使用 -msg 和 -nomsg'
|
|
1381
1388
|
}
|
|
1382
1389
|
|
|
1383
1390
|
// 重复参数检测
|
|
@@ -1450,7 +1457,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1450
1457
|
const updateTime = new Date(config.updatedAt).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
|
|
1451
1458
|
const reminderStatus = config.reminderEnabled ? '启用' : '禁用'
|
|
1452
1459
|
|
|
1453
|
-
return `群 ${targetGroupId}
|
|
1460
|
+
return `群 ${targetGroupId} 配置:
|
|
1454
1461
|
关键词: ${decodedKeywords.join(', ')}
|
|
1455
1462
|
审核方式: ${methodDesc}
|
|
1456
1463
|
阈值: ${thresholdInfo}
|
|
@@ -1490,7 +1497,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1490
1497
|
existingConfig = existingConfigs[0]
|
|
1491
1498
|
}
|
|
1492
1499
|
|
|
1493
|
-
//
|
|
1500
|
+
// 处理关键词: 如果没有关键词但有其他参数,从现有配置中获取
|
|
1494
1501
|
if (keywordList.length === 0 && !cleanedOptions.query && !cleanedOptions.remove) {
|
|
1495
1502
|
if (!existingConfig) {
|
|
1496
1503
|
return config.noKeywordsMessage || '请先提供关键词创建配置,或使用 -? 查询配置,-r 删除配置'
|
|
@@ -1511,13 +1518,13 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1511
1518
|
|
|
1512
1519
|
// 解析审核方式和阈值(基于新的数据库模型设计)
|
|
1513
1520
|
let reviewMethod: 0 | 1 | 2 | 3 = 0 // 默认值
|
|
1514
|
-
let reviewParameters: number = 0 //
|
|
1521
|
+
let reviewParameters: number = 0 // 直接存储数字: 0表示无阈值
|
|
1515
1522
|
|
|
1516
1523
|
if (existingConfig) {
|
|
1517
1524
|
// 默认使用现有配置的参数
|
|
1518
1525
|
reviewMethod = existingConfig.reviewMethod
|
|
1519
1526
|
|
|
1520
|
-
//
|
|
1527
|
+
// 老版本兼容性处理: 处理可能的NaN或无效值
|
|
1521
1528
|
if (existingConfig.reviewParameters === undefined ||
|
|
1522
1529
|
existingConfig.reviewParameters === null ||
|
|
1523
1530
|
isNaN(existingConfig.reviewParameters)) {
|
|
@@ -1532,7 +1539,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1532
1539
|
if (cleanedOptions.method !== undefined && cleanedOptions.method !== '') {
|
|
1533
1540
|
const methodNum = parseInt(cleanedOptions.method)
|
|
1534
1541
|
if (isNaN(methodNum) || methodNum < 0 || methodNum > 3) {
|
|
1535
|
-
return '
|
|
1542
|
+
return '审核方式参数错误: 0-全部同意, 1-按数量同意, 2-按比例同意, 3-全部拒绝'
|
|
1536
1543
|
}
|
|
1537
1544
|
const oldMethod = reviewMethod
|
|
1538
1545
|
reviewMethod = methodNum as 0 | 1 | 2 | 3
|
|
@@ -1872,16 +1879,16 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1872
1879
|
'gvs'
|
|
1873
1880
|
)
|
|
1874
1881
|
.action(async ({ session }: any, target: any) => {
|
|
1875
|
-
//
|
|
1882
|
+
// 参数验证: 只能是群号、all、total或空
|
|
1876
1883
|
const validTargets = ['all', 'total']
|
|
1877
1884
|
const isGroupId = target && /^\d+$/.test(target)
|
|
1878
1885
|
const isSpecialTarget = target && validTargets.includes(target.toLowerCase())
|
|
1879
1886
|
|
|
1880
1887
|
if (target && !isGroupId && !isSpecialTarget) {
|
|
1881
|
-
return '
|
|
1888
|
+
return '参数错误: 只能指定群号、all、total或留空'
|
|
1882
1889
|
}
|
|
1883
1890
|
|
|
1884
|
-
//
|
|
1891
|
+
// 权限检查: 总计统计需要3级以上权限
|
|
1885
1892
|
if (target?.toLowerCase() === 'total' || target?.toLowerCase() === 'all') {
|
|
1886
1893
|
// 检查是否为koishi 3级以上权限
|
|
1887
1894
|
const koishiAuthority = (session as any).author?.authority || (session as any).user?.authority
|
|
@@ -1894,7 +1901,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1894
1901
|
|
|
1895
1902
|
// 处理不同参数情况
|
|
1896
1903
|
if (!target) {
|
|
1897
|
-
//
|
|
1904
|
+
// 无参数: 显示当前群统计
|
|
1898
1905
|
if (!groupId) {
|
|
1899
1906
|
return '请在群聊中使用此命令或指定群号'
|
|
1900
1907
|
}
|
|
@@ -1935,11 +1942,11 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1935
1942
|
return '当前没有待审核的加群申请'
|
|
1936
1943
|
}
|
|
1937
1944
|
|
|
1938
|
-
let result = '
|
|
1945
|
+
let result = '待审核申请列表: \n'
|
|
1939
1946
|
pendingApplications.forEach((app, index) => {
|
|
1940
1947
|
result += `${index + 1}. ${app.userName}(${app.userId})
|
|
1941
|
-
|
|
1942
|
-
|
|
1948
|
+
申请时间: ${app.applyTime.toLocaleString()}
|
|
1949
|
+
申请理由: ${app.requestMessage}
|
|
1943
1950
|
|
|
1944
1951
|
`
|
|
1945
1952
|
})
|
|
@@ -1964,19 +1971,19 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1964
1971
|
.subcommand('.help', '显示帮助信息')
|
|
1965
1972
|
.alias('gv.帮助', 'gverify.帮助', 'group-verify.帮助', '帮助', 'hlp', '帮助信息')
|
|
1966
1973
|
.action(() => {
|
|
1967
|
-
return
|
|
1968
|
-
|
|
1974
|
+
return `群组验证命令帮助:
|
|
1975
|
+
主指令别名: gv, gverify
|
|
1969
1976
|
|
|
1970
1977
|
配置命令 (.config/.cfg):
|
|
1971
|
-
|
|
1972
|
-
1.
|
|
1973
|
-
2.
|
|
1974
|
-
3.
|
|
1975
|
-
4.
|
|
1976
|
-
5.
|
|
1977
|
-
6.
|
|
1978
|
+
用法:
|
|
1979
|
+
1. 创建新配置: gv.cfg 关键词1,关键词2 -m 1 -t 2
|
|
1980
|
+
2. 修改现有配置: gv.cfg -m 1 -t 2 (不提供关键词)
|
|
1981
|
+
3. 启用提醒消息: gv.cfg -msg "消息内容"
|
|
1982
|
+
4. 禁用提醒消息: gv.cfg -nomsg
|
|
1983
|
+
5. 查询配置: gv.cfg -?
|
|
1984
|
+
6. 删除配置: gv.cfg -r
|
|
1978
1985
|
|
|
1979
|
-
|
|
1986
|
+
参数说明:
|
|
1980
1987
|
-i <群号> 指定群号(私聊时必需)
|
|
1981
1988
|
-m <方式> 审核方式 (0=全部同意, 1=按数量, 2=按比例, 3=全部拒绝)
|
|
1982
1989
|
-t <阈值> 阈值参数(方式1:0-关键词数, 方式2:0-100)
|
|
@@ -1988,19 +1995,19 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1988
1995
|
-? 查询当前配置
|
|
1989
1996
|
-r 删除配置
|
|
1990
1997
|
|
|
1991
|
-
|
|
1992
|
-
•
|
|
1993
|
-
•
|
|
1994
|
-
•
|
|
1995
|
-
•
|
|
1998
|
+
引号使用规则:
|
|
1999
|
+
• 关键词包含空格: gv.cfg "关键词1,关键词 2,关键词3"
|
|
2000
|
+
• 提醒消息包含空格: gv.cfg -msg "这是包含空格的消息"
|
|
2001
|
+
• 内部引号转义: gv.cfg -msg "包含\\"引号\\"的内容"
|
|
2002
|
+
• 换行符: gv.cfg -msg "第一行\\n第二行"
|
|
1996
2003
|
|
|
1997
|
-
|
|
2004
|
+
特殊说明:
|
|
1998
2005
|
• 阈值可设为0表示全部同意
|
|
1999
2006
|
• 关键词数量变化时阈值会自动调整
|
|
2000
2007
|
• 重复参数会使用最后出现的值并提醒
|
|
2001
2008
|
• 群号检查可在插件配置中开关
|
|
2002
2009
|
|
|
2003
|
-
|
|
2010
|
+
提醒消息变量:
|
|
2004
2011
|
{user} - 用户名
|
|
2005
2012
|
{id} - 用户ID
|
|
2006
2013
|
{group} - 群号
|
|
@@ -2009,9 +2016,9 @@ export function apply(ctx: Context, config: Config) {
|
|
|
2009
2016
|
{answer} - 答对数量/比例
|
|
2010
2017
|
{threshold} - 阈值要求
|
|
2011
2018
|
|
|
2012
|
-
|
|
2019
|
+
使用示例:
|
|
2013
2020
|
gv.cfg "关键词1,关键词2" -m 1 -t 2
|
|
2014
|
-
gv.cfg -msg "用户 {user}\\n
|
|
2021
|
+
gv.cfg -msg "用户 {user}\\n申请理由: {question}"
|
|
2015
2022
|
gv.cfg -m 2 -t 80
|
|
2016
2023
|
gv.cfg -nomsg
|
|
2017
2024
|
|
|
@@ -2021,21 +2028,21 @@ export function apply(ctx: Context, config: Config) {
|
|
|
2021
2028
|
l [group] 查询群黑名单;传入 all 时查看全局黑名单
|
|
2022
2029
|
i id [group] 查询账号黑名单;不带参数时查询本群与全局,
|
|
2023
2030
|
指定群号查询该群与全局,传入 all 则列出所有群及全局(需Koishi 3级)
|
|
2024
|
-
|
|
2031
|
+
使用示例:
|
|
2025
2032
|
gvb a 12345 作弊记录
|
|
2026
2033
|
gvb r 12345 67890
|
|
2027
2034
|
gvb l
|
|
2028
2035
|
gvb l all
|
|
2029
2036
|
gvb i 12345
|
|
2030
2037
|
|
|
2031
|
-
|
|
2038
|
+
快捷命令:
|
|
2032
2039
|
gvc - 配置命令快捷方式
|
|
2033
2040
|
gva - 同意申请快捷命令
|
|
2034
2041
|
gvr - 拒绝申请快捷命令
|
|
2035
2042
|
gvp - 查看待审核列表快捷命令
|
|
2036
2043
|
gvs - 查看统计信息快捷命令
|
|
2037
2044
|
|
|
2038
|
-
|
|
2045
|
+
权限说明:
|
|
2039
2046
|
- 群主/管理员权限
|
|
2040
2047
|
- koishi三级以上权限
|
|
2041
2048
|
- 私聊时必须指定群号(-i参数)`
|
|
@@ -2043,7 +2050,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
2043
2050
|
|
|
2044
2051
|
// 插件初始化时确保总计统计行存在
|
|
2045
2052
|
ctx.on('ready', async () => {
|
|
2046
|
-
//
|
|
2053
|
+
// 迁移: 将旧版保留的 Date 对象字段转换为 ISO 字符串,以避免绑定错误
|
|
2047
2054
|
try {
|
|
2048
2055
|
const configs = await ctx.database.get('group_verification_config', {})
|
|
2049
2056
|
for (const cfg of configs) {
|
|
@@ -2100,7 +2107,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
2100
2107
|
await syncTotalStats(ctx)
|
|
2101
2108
|
})
|
|
2102
2109
|
|
|
2103
|
-
//
|
|
2110
|
+
// 辅助函数: 获取群组统计
|
|
2104
2111
|
async function getGroupStats(groupId: string): Promise<string> {
|
|
2105
2112
|
const stats = await ctx.database.get('group_verification_stats', { groupId })
|
|
2106
2113
|
|
|
@@ -2111,14 +2118,14 @@ export function apply(ctx: Context, config: Config) {
|
|
|
2111
2118
|
const stat = stats[0]
|
|
2112
2119
|
const lastUpdated = new Date(stat.lastUpdated).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
|
|
2113
2120
|
|
|
2114
|
-
return `群 ${groupId}
|
|
2121
|
+
return `群 ${groupId} 验证统计:
|
|
2115
2122
|
自动批准: ${stat.autoApproved}
|
|
2116
2123
|
手动批准: ${stat.manuallyApproved}
|
|
2117
2124
|
拒绝: ${stat.rejected}
|
|
2118
2125
|
最后更新: ${lastUpdated}`
|
|
2119
2126
|
}
|
|
2120
2127
|
|
|
2121
|
-
//
|
|
2128
|
+
// 辅助函数: 获取总计统计
|
|
2122
2129
|
async function getTotalStats(): Promise<string> {
|
|
2123
2130
|
const stats = await ctx.database.get('group_verification_stats', { groupId: 'TOTAL' })
|
|
2124
2131
|
|
|
@@ -2129,7 +2136,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
2129
2136
|
const stat = stats[0]
|
|
2130
2137
|
const lastUpdated = new Date(stat.lastUpdated).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
|
|
2131
2138
|
|
|
2132
|
-
return
|
|
2139
|
+
return `总计验证统计:
|
|
2133
2140
|
自动批准: ${stat.autoApproved}
|
|
2134
2141
|
手动批准: ${stat.manuallyApproved}
|
|
2135
2142
|
拒绝: ${stat.rejected}
|