koishi-plugin-group-verification 1.0.31 → 1.0.33
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 +13 -3
- package/package.json +50 -51
- package/readme.md +16 -0
- package/src/index.ts +28 -14
package/lib/index.js
CHANGED
|
@@ -348,7 +348,7 @@ async function checkPermission(session, targetGroupId) {
|
|
|
348
348
|
});
|
|
349
349
|
}
|
|
350
350
|
if (koishiAuthority && koishiAuthority >= 3) {
|
|
351
|
-
logger.
|
|
351
|
+
logger.debug(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`);
|
|
352
352
|
return [true];
|
|
353
353
|
}
|
|
354
354
|
try {
|
|
@@ -661,7 +661,9 @@ async function processBlacklistCommand(ctx, session, rawArgs, config) {
|
|
|
661
661
|
targetUser = parts[1];
|
|
662
662
|
if (!targetUser) return "请提供用户ID";
|
|
663
663
|
const rest = parts.slice(2);
|
|
664
|
-
if (rest.length
|
|
664
|
+
if (rest.length === 1) {
|
|
665
|
+
reason = rest[0];
|
|
666
|
+
} else if (rest.length > 1) {
|
|
665
667
|
const last = rest[rest.length - 1];
|
|
666
668
|
if (/^\d+$/.test(last) || last.toLowerCase() === "all") {
|
|
667
669
|
group = last;
|
|
@@ -700,6 +702,14 @@ async function processBlacklistCommand(ctx, session, rawArgs, config) {
|
|
|
700
702
|
entries[targetUser] = storedReason;
|
|
701
703
|
await ctx.database.create("group_verification_blacklist", { groupId: group, entries });
|
|
702
704
|
}
|
|
705
|
+
if (session.bot && typeof session.bot.kickGuildMember === "function") {
|
|
706
|
+
try {
|
|
707
|
+
await session.bot.kickGuildMember(group, targetUser);
|
|
708
|
+
logger.info(`已将黑名单用户 ${targetUser} 从群 ${group} 踢出`);
|
|
709
|
+
} catch (e) {
|
|
710
|
+
logger.warn(`踢出用户 ${targetUser} 失败`, e);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
703
713
|
const tmpl = config && config.blacklistAddSuccess || "已将用户 {user} 加入群 {group} 黑名单{reason}";
|
|
704
714
|
return tmpl.replace("{user}", targetUser).replace("{group}", group).replace("{reason}", reason ? `,原因:${reason}` : "");
|
|
705
715
|
}
|
|
@@ -872,7 +882,7 @@ function apply(ctx, config) {
|
|
|
872
882
|
userId: "string",
|
|
873
883
|
userName: "string",
|
|
874
884
|
requestMessage: "string",
|
|
875
|
-
//
|
|
885
|
+
// 保存 OneBot 事件提供的原始 requestId;用于同意/拒绝操作
|
|
876
886
|
requestId: "string",
|
|
877
887
|
// record full timestamp as string to keep time component
|
|
878
888
|
applyTime: "string"
|
package/package.json
CHANGED
|
@@ -1,51 +1,50 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "koishi-plugin-group-verification",
|
|
3
|
-
"description": "Koishi 群组加群验证插件,支持多关键词匹配审核、多种审核方式和详细统计功能",
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "koishi-plugin-group-verification",
|
|
3
|
+
"description": "Koishi 群组加群验证插件,支持多关键词匹配审核、多种审核方式和详细统计功能",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/LHDyx/koishi-plugin-group-verification.git"
|
|
7
|
+
},
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/LHDyx/koishi-plugin-group-verification/issues"
|
|
10
|
+
},
|
|
11
|
+
"version": "1.0.33",
|
|
12
|
+
"main": "lib/index.js",
|
|
13
|
+
"typings": "lib/index.d.ts",
|
|
14
|
+
"files": [
|
|
15
|
+
"lib",
|
|
16
|
+
"dist",
|
|
17
|
+
"src",
|
|
18
|
+
"README.md",
|
|
19
|
+
"USAGE_EXAMPLE.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"dev": "tsc -w",
|
|
24
|
+
"test": "ts-node test/basic-test.ts"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"chatbot",
|
|
29
|
+
"koishi",
|
|
30
|
+
"plugin",
|
|
31
|
+
"group-verification",
|
|
32
|
+
"guild-management",
|
|
33
|
+
"join-request",
|
|
34
|
+
"moderation"
|
|
35
|
+
],
|
|
36
|
+
"koishi": {
|
|
37
|
+
"service": {
|
|
38
|
+
"required": [
|
|
39
|
+
"database"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"koishi": "^4.15.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"typescript": "^4.9.0",
|
|
48
|
+
"@types/node": "^16.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
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
|
@@ -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
|
}
|
|
@@ -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++;
|
|
@@ -226,7 +226,7 @@ export interface ParsedArgs {
|
|
|
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;
|
|
@@ -403,7 +403,8 @@ export async function incrementTotal(ctx: Context, groupId: string) {
|
|
|
403
403
|
await syncTotalStats(ctx)
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
-
//
|
|
406
|
+
// 根据已有配置、关键词列表及用户输入确定阈值参数的辅助函数,
|
|
407
|
+
// 并处理审核方式更改所致的自动调整
|
|
407
408
|
// and whether the audit method has been changed by the command.
|
|
408
409
|
export interface ThresholdResult {
|
|
409
410
|
reviewParameters: number
|
|
@@ -498,7 +499,7 @@ export async function checkPermission(session: any, targetGroupId?: string): Pro
|
|
|
498
499
|
}
|
|
499
500
|
|
|
500
501
|
if (koishiAuthority && koishiAuthority >= 3) {
|
|
501
|
-
logger.
|
|
502
|
+
logger.debug(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`)
|
|
502
503
|
return [true]
|
|
503
504
|
}
|
|
504
505
|
|
|
@@ -603,7 +604,7 @@ export function parseConfigArgs(raw: string): ParsedArgs {
|
|
|
603
604
|
|
|
604
605
|
const isFlag = (tok: string) => /^-(?:i|m|t|msg|nomsg|\?|r)$/.test(tok);
|
|
605
606
|
|
|
606
|
-
//
|
|
607
|
+
// 在还原哨兵字符之前处理未转义的杂散引号
|
|
607
608
|
for (const tok of tokens) {
|
|
608
609
|
if (tok.includes('"')) {
|
|
609
610
|
error = '存在未转义的引号';
|
|
@@ -611,7 +612,7 @@ export function parseConfigArgs(raw: string): ParsedArgs {
|
|
|
611
612
|
}
|
|
612
613
|
}
|
|
613
614
|
|
|
614
|
-
//
|
|
615
|
+
// 将哨兵占位符还原为真实字符
|
|
615
616
|
tokens = tokens.map(t =>
|
|
616
617
|
t.replace(new RegExp(ESC_QUOTE, 'g'), '"').replace(new RegExp(ESC_BACKSLASH, 'g'), '\\')
|
|
617
618
|
);
|
|
@@ -781,7 +782,7 @@ export async function handleFailedVerification(
|
|
|
781
782
|
// 无法获取群名称时使用默认值
|
|
782
783
|
}
|
|
783
784
|
|
|
784
|
-
//
|
|
785
|
+
// 如果可用则提取 requestId(OneBot 事件会附带)
|
|
785
786
|
const requestId = ((session.event as any)?.requestId) || session.messageId || ''
|
|
786
787
|
|
|
787
788
|
// 删除同一用户在该群之前的所有待审核记录,保留最新一个
|
|
@@ -886,13 +887,17 @@ export async function processBlacklistCommand(ctx: Context, session: any, rawArg
|
|
|
886
887
|
targetUser = parts[1]
|
|
887
888
|
if (!targetUser) return '请提供用户ID'
|
|
888
889
|
// handle optional reason and group at end
|
|
890
|
+
// 规则:如果只有一个附加参数,则作为 reason;两个及以上时最后一个为群号,其余拼成 reason
|
|
889
891
|
const rest = parts.slice(2)
|
|
890
|
-
if (rest.length
|
|
892
|
+
if (rest.length === 1) {
|
|
893
|
+
reason = rest[0]
|
|
894
|
+
} else if (rest.length > 1) {
|
|
891
895
|
const last = rest[rest.length - 1]
|
|
892
896
|
if (/^\d+$/.test(last) || last.toLowerCase() === 'all') {
|
|
893
897
|
group = last
|
|
894
898
|
reason = rest.slice(0, -1).join(' ')
|
|
895
899
|
} else {
|
|
900
|
+
// 虽然数量>=2,但最后一个不是数字,全部作为reason
|
|
896
901
|
reason = rest.join(' ')
|
|
897
902
|
}
|
|
898
903
|
}
|
|
@@ -930,6 +935,15 @@ export async function processBlacklistCommand(ctx: Context, session: any, rawArg
|
|
|
930
935
|
entries[targetUser] = storedReason
|
|
931
936
|
await ctx.database.create('group_verification_blacklist', { groupId: group, entries })
|
|
932
937
|
}
|
|
938
|
+
// 添加成功后尝试踢人
|
|
939
|
+
if (session.bot && typeof session.bot.kickGuildMember === 'function') {
|
|
940
|
+
try {
|
|
941
|
+
await session.bot.kickGuildMember(group, targetUser)
|
|
942
|
+
logger.info(`已将黑名单用户 ${targetUser} 从群 ${group} 踢出`)
|
|
943
|
+
} catch (e) {
|
|
944
|
+
logger.warn(`踢出用户 ${targetUser} 失败`, e)
|
|
945
|
+
}
|
|
946
|
+
}
|
|
933
947
|
const tmpl = (config && config.blacklistAddSuccess) || '已将用户 {user} 加入群 {group} 黑名单{reason}'
|
|
934
948
|
return tmpl.replace('{user}', targetUser).replace('{group}', group).replace('{reason}', reason ? `,原因:${reason}` : '')
|
|
935
949
|
}
|
|
@@ -1001,7 +1015,7 @@ export async function processBlacklistCommand(ctx: Context, session: any, rawArg
|
|
|
1001
1015
|
const globalRows = await ctx.database.get('group_verification_blacklist', { groupId: 'all' })
|
|
1002
1016
|
const globalHit = globalRows.length > 0 && (globalRows[0].entries || {})[targetUser] !== undefined
|
|
1003
1017
|
|
|
1004
|
-
//
|
|
1018
|
+
// 使用模板格式化回复的辅助函数
|
|
1005
1019
|
const tmpl = (config && config.blacklistInfoTemplate) || '全局黑名单: {global}\n本群黑名单: {group}'
|
|
1006
1020
|
const formatReply = (localHit: boolean, groupsList?: string[]) => {
|
|
1007
1021
|
if (groupsList) {
|
|
@@ -1119,7 +1133,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1119
1133
|
userId: 'string',
|
|
1120
1134
|
userName: 'string',
|
|
1121
1135
|
requestMessage: 'string',
|
|
1122
|
-
//
|
|
1136
|
+
// 保存 OneBot 事件提供的原始 requestId;用于同意/拒绝操作
|
|
1123
1137
|
requestId: 'string',
|
|
1124
1138
|
// record full timestamp as string to keep time component
|
|
1125
1139
|
applyTime: 'string'
|