@zhin.js/plugin-content-moderation 1.1.14 → 1.1.16
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/README.md +37 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +10 -3
- package/lib/config.js.map +1 -1
- package/lib/engine.d.ts +10 -0
- package/lib/engine.d.ts.map +1 -1
- package/lib/engine.js +12 -2
- package/lib/engine.js.map +1 -1
- package/lib/group-moderation.d.ts +55 -0
- package/lib/group-moderation.d.ts.map +1 -0
- package/lib/group-moderation.js +223 -0
- package/lib/group-moderation.js.map +1 -0
- package/lib/group-review-batch.d.ts +15 -0
- package/lib/group-review-batch.d.ts.map +1 -0
- package/lib/group-review-batch.js +156 -0
- package/lib/group-review-batch.js.map +1 -0
- package/lib/group-review-queue.d.ts +14 -0
- package/lib/group-review-queue.d.ts.map +1 -0
- package/lib/group-review-queue.js +148 -0
- package/lib/group-review-queue.js.map +1 -0
- package/lib/messages.d.ts +2 -0
- package/lib/messages.d.ts.map +1 -0
- package/lib/messages.js +12 -0
- package/lib/messages.js.map +1 -0
- package/lib/types.d.ts +10 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js.map +1 -1
- package/middlewares/inbound/index.js +98 -3
- package/middlewares/inbound/index.ts +94 -4
- package/package.json +5 -5
- package/plugin.js +1 -0
- package/schema.json +289 -44
- package/src/config.ts +12 -3
- package/src/engine.ts +16 -2
- package/src/group-moderation.ts +253 -0
- package/src/group-review-batch.ts +135 -0
- package/src/group-review-queue.ts +135 -0
- package/src/messages.ts +12 -0
- package/src/types.ts +11 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { defineMiddleware } from 'zhin.js/middleware';
|
|
1
|
+
import { defineMiddleware, type MiddlewareContext } from 'zhin.js/middleware';
|
|
2
2
|
import type { Message } from '@zhin.js/core/runtime';
|
|
3
|
-
import {
|
|
3
|
+
import { aiCompletionToken, createDeliveryTarget, databaseHostToken, getLogger, hasResource,
|
|
4
|
+
outboundHostToken, usageSubjectFromMessage } from 'zhin.js';
|
|
5
|
+
import { adapterFeatureId, type AdapterIndex } from 'zhin.js/adapter';
|
|
6
|
+
import { generationAdmissionSource } from 'zhin.js/plugin-runtime';
|
|
7
|
+
import { resolveLocale } from 'zhin.js/i18n';
|
|
4
8
|
import {
|
|
5
9
|
buildScanContext,
|
|
6
10
|
extractFromTextAndSegments,
|
|
@@ -27,15 +31,18 @@ export default defineMiddleware<Message, ModerationConfig>({
|
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
const message = context.input;
|
|
34
|
+
const locale = resolveLocale(message.locale);
|
|
30
35
|
const sender = message.sender?.id;
|
|
31
36
|
const conversationId = message.conversation.id;
|
|
32
37
|
const endpointMasters = resolveEndpointMasters(message, config);
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
const bypass = shouldBypassInbound(config, {
|
|
35
40
|
sender,
|
|
36
41
|
conversationId,
|
|
37
42
|
endpointMasters,
|
|
38
|
-
})
|
|
43
|
+
});
|
|
44
|
+
const review = await enqueueGroupModeration(context, config, bypass);
|
|
45
|
+
if (bypass) {
|
|
39
46
|
await next();
|
|
40
47
|
return;
|
|
41
48
|
}
|
|
@@ -65,6 +72,8 @@ export default defineMiddleware<Message, ModerationConfig>({
|
|
|
65
72
|
|
|
66
73
|
const result = await engine.apply({
|
|
67
74
|
direction: 'inbound',
|
|
75
|
+
locale,
|
|
76
|
+
recallManaged: review?.done,
|
|
68
77
|
extracted,
|
|
69
78
|
scanInput,
|
|
70
79
|
hooks: {
|
|
@@ -104,6 +113,87 @@ export default defineMiddleware<Message, ModerationConfig>({
|
|
|
104
113
|
},
|
|
105
114
|
});
|
|
106
115
|
|
|
116
|
+
async function enqueueGroupModeration(
|
|
117
|
+
context: MiddlewareContext<Message, ModerationConfig, string | undefined>,
|
|
118
|
+
config: ModerationConfig, bypass: boolean,
|
|
119
|
+
): Promise<{ done: Promise<boolean> } | undefined> {
|
|
120
|
+
const message = context.input;
|
|
121
|
+
const locale = resolveLocale(message.locale);
|
|
122
|
+
const conversation = structuredClone(message.conversation);
|
|
123
|
+
const target = config.groupModeration.find((item) => conversation.kind === 'group'
|
|
124
|
+
&& item.adapter === conversation.endpoint.adapter
|
|
125
|
+
&& item.endpoint === message.endpointId && item.group === conversation.id);
|
|
126
|
+
if (!target) return;
|
|
127
|
+
const logger = getLogger('content-moderation');
|
|
128
|
+
try {
|
|
129
|
+
const adapters = context.project<AdapterIndex>(adapterFeatureId);
|
|
130
|
+
const endpoint = adapters.connection(conversation.endpoint.adapter, conversation.endpoint.id);
|
|
131
|
+
// Reject alias fallback and account switches, even when a local instance name is reused.
|
|
132
|
+
if (!endpoint || endpoint.identity.id !== conversation.endpoint.id
|
|
133
|
+
|| endpoint.identity.adapter !== target.adapter || endpoint.selfUserId !== target.account) return;
|
|
134
|
+
const engine = context.use(moderationEngineToken);
|
|
135
|
+
const { text } = extractFromTextAndSegments(message.content, message.segments);
|
|
136
|
+
const destination = createDeliveryTarget({ conversation });
|
|
137
|
+
const scope = JSON.stringify(destination);
|
|
138
|
+
const remembered = engine.groupModerator.remember(scope, message.sender?.id ?? '', text);
|
|
139
|
+
if (!bypass && message.sender?.id && message.origin !== 'self'
|
|
140
|
+
&& hasResource(context, aiCompletionToken) && hasResource(context, databaseHostToken)
|
|
141
|
+
&& hasResource(context, outboundHostToken)) {
|
|
142
|
+
const host = context.use(outboundHostToken);
|
|
143
|
+
if (!host.sendTarget) return;
|
|
144
|
+
const lifetime = new AbortController();
|
|
145
|
+
const gates = adapters[generationAdmissionSource] ?? [];
|
|
146
|
+
const detach = gates.map(gate => gate.onDeactivate(() => lifetime.abort(new Error('Moderation adapter generation retired'))));
|
|
147
|
+
const signal = AbortSignal.any([context.signal, lifetime.signal]);
|
|
148
|
+
const messageId = message.id ?? '';
|
|
149
|
+
const assertTarget = () => {
|
|
150
|
+
signal.throwIfAborted();
|
|
151
|
+
if (endpoint.identity.id !== conversation.endpoint.id || endpoint.selfUserId !== target.account
|
|
152
|
+
|| endpoint.identity.adapter !== target.adapter || gates.some(gate => !gate.active)) {
|
|
153
|
+
throw new Error('Moderation target account is no longer active');
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
try {
|
|
157
|
+
const queued = await engine.groupReviewQueue.enqueue({
|
|
158
|
+
scope, group: conversation.id, sender: message.sender.id, locale,
|
|
159
|
+
messageId, sentAt: Number(message.metadata?.platformSentAt), text, ...remembered,
|
|
160
|
+
signal,
|
|
161
|
+
state: context.use(databaseHostToken).state.forConversation(destination),
|
|
162
|
+
completion: context.use(aiCompletionToken), management: {
|
|
163
|
+
inspectGroupMember: endpoint.management?.inspectGroupMember ? async (group, member, operationSignal) => {
|
|
164
|
+
assertTarget(); return endpoint.management!.inspectGroupMember!(group, member, operationSignal);
|
|
165
|
+
} : undefined,
|
|
166
|
+
moderateGroupMember: endpoint.management?.moderateGroupMember ? async (group, member, action, operationSignal) => {
|
|
167
|
+
assertTarget(); return endpoint.management!.moderateGroupMember!(group, member, action, operationSignal);
|
|
168
|
+
} : undefined,
|
|
169
|
+
},
|
|
170
|
+
onBehalfOf: usageSubjectFromMessage(message),
|
|
171
|
+
reply: async (content, mention) => {
|
|
172
|
+
assertTarget();
|
|
173
|
+
const receipt = await host.sendTarget!(destination, mention ? [
|
|
174
|
+
{ type: 'mention', data: { target: mention } },
|
|
175
|
+
{ type: 'text', data: { text: ` ${content}` } },
|
|
176
|
+
] : content, { locale });
|
|
177
|
+
if (!receipt) throw new Error('Moderation notice was not confirmed');
|
|
178
|
+
},
|
|
179
|
+
recall: async () => {
|
|
180
|
+
assertTarget();
|
|
181
|
+
if (!host.recall || !messageId) throw new Error('Original message cannot be recalled');
|
|
182
|
+
await host.recall({ adapter: target.adapter, endpointKey: conversation.endpoint.id,
|
|
183
|
+
message: { conversation, id: messageId } });
|
|
184
|
+
},
|
|
185
|
+
audit: (event) => logger.info({ ...event, endpoint: conversation.endpoint.id, group: conversation.id }),
|
|
186
|
+
});
|
|
187
|
+
return { done: queued.done.finally(() => detach.forEach(remove => remove())) };
|
|
188
|
+
} catch (error) {
|
|
189
|
+
detach.forEach(remove => remove()); throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
logger.warn('Automatic group moderation unavailable', error instanceof Error ? error.message : String(error));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
107
197
|
/**
|
|
108
198
|
* Masters from plugin config + optional metadata.endpointMaster / metadata.master.
|
|
109
199
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-content-moderation",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.16",
|
|
4
4
|
"description": "Content moderation plugin for Zhin.js — inbound/outbound multi-source scanning (Plugin Runtime)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -37,13 +37,13 @@
|
|
|
37
37
|
},
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@zhin.js/core": "1.1.
|
|
40
|
+
"@zhin.js/core": "1.1.51",
|
|
41
41
|
"@zhin.js/logger": "1.1.2"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"typescript": "^6.0.3",
|
|
45
45
|
"vitest": "^4.1.10",
|
|
46
|
-
"zhin.js": "1.1.
|
|
46
|
+
"zhin.js": "1.1.16"
|
|
47
47
|
},
|
|
48
48
|
"repository": {
|
|
49
49
|
"type": "git",
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"plugins": []
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
|
-
"@zhin.js/middleware": "^1.1.
|
|
76
|
-
"zhin.js": "^1.1.
|
|
75
|
+
"@zhin.js/middleware": "^1.1.10",
|
|
76
|
+
"zhin.js": "^1.1.16"
|
|
77
77
|
},
|
|
78
78
|
"peerDependenciesMeta": {
|
|
79
79
|
"@zhin.js/middleware": {
|
package/plugin.js
CHANGED
package/schema.json
CHANGED
|
@@ -3,44 +3,119 @@
|
|
|
3
3
|
"type": "object",
|
|
4
4
|
"additionalProperties": false,
|
|
5
5
|
"properties": {
|
|
6
|
+
"groupModeration": {
|
|
7
|
+
"type": "array",
|
|
8
|
+
"default": [],
|
|
9
|
+
"description": "自动广告/辱骂审核的精确范围;未配置时关闭。需要 AI、原子持久状态和适配器群管支持。",
|
|
10
|
+
"items": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"additionalProperties": false,
|
|
13
|
+
"required": [
|
|
14
|
+
"adapter",
|
|
15
|
+
"endpoint",
|
|
16
|
+
"account",
|
|
17
|
+
"group"
|
|
18
|
+
],
|
|
19
|
+
"properties": {
|
|
20
|
+
"adapter": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"minLength": 1
|
|
23
|
+
},
|
|
24
|
+
"endpoint": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"minLength": 1
|
|
27
|
+
},
|
|
28
|
+
"account": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"minLength": 1
|
|
31
|
+
},
|
|
32
|
+
"group": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"minLength": 1
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"x-i18n": {
|
|
39
|
+
"en": {
|
|
40
|
+
"description": "Exact scope for automatic advertising/abuse moderation. Disabled when unconfigured. Requires AI, atomic persistent state and adapter group-management support."
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
6
44
|
"enabled": {
|
|
7
45
|
"type": "boolean",
|
|
8
46
|
"default": true,
|
|
9
|
-
"description": "总开关"
|
|
47
|
+
"description": "总开关",
|
|
48
|
+
"x-i18n": {
|
|
49
|
+
"en": {
|
|
50
|
+
"description": "Enable moderation."
|
|
51
|
+
}
|
|
52
|
+
}
|
|
10
53
|
},
|
|
11
54
|
"onError": {
|
|
12
55
|
"type": "string",
|
|
13
|
-
"enum": [
|
|
56
|
+
"enum": [
|
|
57
|
+
"open",
|
|
58
|
+
"closed"
|
|
59
|
+
],
|
|
14
60
|
"default": "open",
|
|
15
|
-
"description": "审查源失败时的全局默认策略:open=视为通过,closed=视为 critical"
|
|
61
|
+
"description": "审查源失败时的全局默认策略:open=视为通过,closed=视为 critical",
|
|
62
|
+
"x-i18n": {
|
|
63
|
+
"en": {
|
|
64
|
+
"description": "Default policy when a review source fails: open treats it as passed; closed treats it as critical."
|
|
65
|
+
}
|
|
66
|
+
}
|
|
16
67
|
},
|
|
17
68
|
"maskChar": {
|
|
18
69
|
"type": "string",
|
|
19
70
|
"default": "*",
|
|
20
71
|
"minLength": 1,
|
|
21
72
|
"maxLength": 4,
|
|
22
|
-
"description": "文本打码字符"
|
|
73
|
+
"description": "文本打码字符",
|
|
74
|
+
"x-i18n": {
|
|
75
|
+
"en": {
|
|
76
|
+
"description": "Text masking character."
|
|
77
|
+
}
|
|
78
|
+
}
|
|
23
79
|
},
|
|
24
80
|
"replyTemplate": {
|
|
25
81
|
"type": "string",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
82
|
+
"description": "reply 动作时的提示文案;省略时按本次消息语言选择默认文案,显式文本保持原样",
|
|
83
|
+
"x-i18n": {
|
|
84
|
+
"en": {
|
|
85
|
+
"description": "Reply text for the reply action. When omitted, use the message language; explicit text is preserved."
|
|
86
|
+
}
|
|
87
|
+
}
|
|
28
88
|
},
|
|
29
89
|
"masters": {
|
|
30
90
|
"type": "array",
|
|
31
|
-
"items": {
|
|
91
|
+
"items": {
|
|
92
|
+
"type": "string"
|
|
93
|
+
},
|
|
32
94
|
"default": [],
|
|
33
|
-
"description": "额外视为 master 的 userId(兜底,与 endpoint master 合并)"
|
|
95
|
+
"description": "额外视为 master 的 userId(兜底,与 endpoint master 合并)",
|
|
96
|
+
"x-i18n": {
|
|
97
|
+
"en": {
|
|
98
|
+
"description": "Additional user IDs treated as masters, merged with endpoint masters as a fallback."
|
|
99
|
+
}
|
|
100
|
+
}
|
|
34
101
|
},
|
|
35
102
|
"inbound": {
|
|
36
103
|
"type": "object",
|
|
37
104
|
"additionalProperties": false,
|
|
38
105
|
"properties": {
|
|
39
|
-
"enabled": {
|
|
106
|
+
"enabled": {
|
|
107
|
+
"type": "boolean",
|
|
108
|
+
"default": true
|
|
109
|
+
},
|
|
40
110
|
"bypassMasters": {
|
|
41
111
|
"type": "boolean",
|
|
42
112
|
"default": true,
|
|
43
|
-
"description": "master 跳过入站审查"
|
|
113
|
+
"description": "master 跳过入站审查",
|
|
114
|
+
"x-i18n": {
|
|
115
|
+
"en": {
|
|
116
|
+
"description": "Skip inbound review for masters."
|
|
117
|
+
}
|
|
118
|
+
}
|
|
44
119
|
},
|
|
45
120
|
"whitelist": {
|
|
46
121
|
"type": "object",
|
|
@@ -48,12 +123,16 @@
|
|
|
48
123
|
"properties": {
|
|
49
124
|
"userIds": {
|
|
50
125
|
"type": "array",
|
|
51
|
-
"items": {
|
|
126
|
+
"items": {
|
|
127
|
+
"type": "string"
|
|
128
|
+
},
|
|
52
129
|
"default": []
|
|
53
130
|
},
|
|
54
131
|
"conversationIds": {
|
|
55
132
|
"type": "array",
|
|
56
|
-
"items": {
|
|
133
|
+
"items": {
|
|
134
|
+
"type": "string"
|
|
135
|
+
},
|
|
57
136
|
"default": []
|
|
58
137
|
}
|
|
59
138
|
}
|
|
@@ -64,11 +143,19 @@
|
|
|
64
143
|
"type": "object",
|
|
65
144
|
"additionalProperties": false,
|
|
66
145
|
"properties": {
|
|
67
|
-
"enabled": {
|
|
146
|
+
"enabled": {
|
|
147
|
+
"type": "boolean",
|
|
148
|
+
"default": true
|
|
149
|
+
},
|
|
68
150
|
"bypass": {
|
|
69
151
|
"type": "boolean",
|
|
70
152
|
"default": false,
|
|
71
|
-
"description": "为 true 时跳过出站审查"
|
|
153
|
+
"description": "为 true 时跳过出站审查",
|
|
154
|
+
"x-i18n": {
|
|
155
|
+
"en": {
|
|
156
|
+
"description": "Skip outbound review when true."
|
|
157
|
+
}
|
|
158
|
+
}
|
|
72
159
|
}
|
|
73
160
|
}
|
|
74
161
|
},
|
|
@@ -81,14 +168,28 @@
|
|
|
81
168
|
"anyOf": [
|
|
82
169
|
{
|
|
83
170
|
"type": "string",
|
|
84
|
-
"enum": [
|
|
171
|
+
"enum": [
|
|
172
|
+
"allow",
|
|
173
|
+
"log",
|
|
174
|
+
"reply",
|
|
175
|
+
"redact",
|
|
176
|
+
"drop",
|
|
177
|
+
"recall"
|
|
178
|
+
]
|
|
85
179
|
},
|
|
86
180
|
{
|
|
87
181
|
"type": "array",
|
|
88
182
|
"minItems": 1,
|
|
89
183
|
"items": {
|
|
90
184
|
"type": "string",
|
|
91
|
-
"enum": [
|
|
185
|
+
"enum": [
|
|
186
|
+
"allow",
|
|
187
|
+
"log",
|
|
188
|
+
"reply",
|
|
189
|
+
"redact",
|
|
190
|
+
"drop",
|
|
191
|
+
"recall"
|
|
192
|
+
]
|
|
92
193
|
}
|
|
93
194
|
}
|
|
94
195
|
]
|
|
@@ -97,14 +198,28 @@
|
|
|
97
198
|
"anyOf": [
|
|
98
199
|
{
|
|
99
200
|
"type": "string",
|
|
100
|
-
"enum": [
|
|
201
|
+
"enum": [
|
|
202
|
+
"allow",
|
|
203
|
+
"log",
|
|
204
|
+
"reply",
|
|
205
|
+
"redact",
|
|
206
|
+
"drop",
|
|
207
|
+
"recall"
|
|
208
|
+
]
|
|
101
209
|
},
|
|
102
210
|
{
|
|
103
211
|
"type": "array",
|
|
104
212
|
"minItems": 1,
|
|
105
213
|
"items": {
|
|
106
214
|
"type": "string",
|
|
107
|
-
"enum": [
|
|
215
|
+
"enum": [
|
|
216
|
+
"allow",
|
|
217
|
+
"log",
|
|
218
|
+
"reply",
|
|
219
|
+
"redact",
|
|
220
|
+
"drop",
|
|
221
|
+
"recall"
|
|
222
|
+
]
|
|
108
223
|
}
|
|
109
224
|
}
|
|
110
225
|
]
|
|
@@ -113,14 +228,28 @@
|
|
|
113
228
|
"anyOf": [
|
|
114
229
|
{
|
|
115
230
|
"type": "string",
|
|
116
|
-
"enum": [
|
|
231
|
+
"enum": [
|
|
232
|
+
"allow",
|
|
233
|
+
"log",
|
|
234
|
+
"reply",
|
|
235
|
+
"redact",
|
|
236
|
+
"drop",
|
|
237
|
+
"recall"
|
|
238
|
+
]
|
|
117
239
|
},
|
|
118
240
|
{
|
|
119
241
|
"type": "array",
|
|
120
242
|
"minItems": 1,
|
|
121
243
|
"items": {
|
|
122
244
|
"type": "string",
|
|
123
|
-
"enum": [
|
|
245
|
+
"enum": [
|
|
246
|
+
"allow",
|
|
247
|
+
"log",
|
|
248
|
+
"reply",
|
|
249
|
+
"redact",
|
|
250
|
+
"drop",
|
|
251
|
+
"recall"
|
|
252
|
+
]
|
|
124
253
|
}
|
|
125
254
|
}
|
|
126
255
|
]
|
|
@@ -129,14 +258,28 @@
|
|
|
129
258
|
"anyOf": [
|
|
130
259
|
{
|
|
131
260
|
"type": "string",
|
|
132
|
-
"enum": [
|
|
261
|
+
"enum": [
|
|
262
|
+
"allow",
|
|
263
|
+
"log",
|
|
264
|
+
"reply",
|
|
265
|
+
"redact",
|
|
266
|
+
"drop",
|
|
267
|
+
"recall"
|
|
268
|
+
]
|
|
133
269
|
},
|
|
134
270
|
{
|
|
135
271
|
"type": "array",
|
|
136
272
|
"minItems": 1,
|
|
137
273
|
"items": {
|
|
138
274
|
"type": "string",
|
|
139
|
-
"enum": [
|
|
275
|
+
"enum": [
|
|
276
|
+
"allow",
|
|
277
|
+
"log",
|
|
278
|
+
"reply",
|
|
279
|
+
"redact",
|
|
280
|
+
"drop",
|
|
281
|
+
"recall"
|
|
282
|
+
]
|
|
140
283
|
}
|
|
141
284
|
}
|
|
142
285
|
]
|
|
@@ -145,18 +288,37 @@
|
|
|
145
288
|
"anyOf": [
|
|
146
289
|
{
|
|
147
290
|
"type": "string",
|
|
148
|
-
"enum": [
|
|
291
|
+
"enum": [
|
|
292
|
+
"allow",
|
|
293
|
+
"log",
|
|
294
|
+
"reply",
|
|
295
|
+
"redact",
|
|
296
|
+
"drop",
|
|
297
|
+
"recall"
|
|
298
|
+
]
|
|
149
299
|
},
|
|
150
300
|
{
|
|
151
301
|
"type": "array",
|
|
152
302
|
"minItems": 1,
|
|
153
303
|
"items": {
|
|
154
304
|
"type": "string",
|
|
155
|
-
"enum": [
|
|
305
|
+
"enum": [
|
|
306
|
+
"allow",
|
|
307
|
+
"log",
|
|
308
|
+
"reply",
|
|
309
|
+
"redact",
|
|
310
|
+
"drop",
|
|
311
|
+
"recall"
|
|
312
|
+
]
|
|
156
313
|
}
|
|
157
314
|
}
|
|
158
315
|
]
|
|
159
316
|
}
|
|
317
|
+
},
|
|
318
|
+
"x-i18n": {
|
|
319
|
+
"en": {
|
|
320
|
+
"description": "Severity-to-action mapping (string or array); uses the stricter default mapping when omitted."
|
|
321
|
+
}
|
|
160
322
|
}
|
|
161
323
|
},
|
|
162
324
|
"sources": {
|
|
@@ -164,17 +326,43 @@
|
|
|
164
326
|
"default": [],
|
|
165
327
|
"items": {
|
|
166
328
|
"type": "object",
|
|
167
|
-
"required": [
|
|
329
|
+
"required": [
|
|
330
|
+
"id",
|
|
331
|
+
"type"
|
|
332
|
+
],
|
|
168
333
|
"additionalProperties": false,
|
|
169
334
|
"properties": {
|
|
170
|
-
"id": {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
335
|
+
"id": {
|
|
336
|
+
"type": "string",
|
|
337
|
+
"minLength": 1
|
|
338
|
+
},
|
|
339
|
+
"type": {
|
|
340
|
+
"type": "string",
|
|
341
|
+
"enum": [
|
|
342
|
+
"local",
|
|
343
|
+
"http"
|
|
344
|
+
]
|
|
345
|
+
},
|
|
346
|
+
"enabled": {
|
|
347
|
+
"type": "boolean",
|
|
348
|
+
"default": true
|
|
349
|
+
},
|
|
350
|
+
"onError": {
|
|
351
|
+
"type": "string",
|
|
352
|
+
"enum": [
|
|
353
|
+
"open",
|
|
354
|
+
"closed"
|
|
355
|
+
]
|
|
356
|
+
},
|
|
174
357
|
"includeBuiltin": {
|
|
175
358
|
"type": "boolean",
|
|
176
359
|
"default": true,
|
|
177
|
-
"description": "是否合并内置分级违禁词词库"
|
|
360
|
+
"description": "是否合并内置分级违禁词词库",
|
|
361
|
+
"x-i18n": {
|
|
362
|
+
"en": {
|
|
363
|
+
"description": "Include the built-in graded blocked-word dictionary."
|
|
364
|
+
}
|
|
365
|
+
}
|
|
178
366
|
},
|
|
179
367
|
"words": {
|
|
180
368
|
"type": "array",
|
|
@@ -182,45 +370,97 @@
|
|
|
182
370
|
"description": "自定义词:字符串(用 defaultSeverity)或 { word, severity }",
|
|
183
371
|
"items": {
|
|
184
372
|
"anyOf": [
|
|
185
|
-
{
|
|
373
|
+
{
|
|
374
|
+
"type": "string",
|
|
375
|
+
"minLength": 1
|
|
376
|
+
},
|
|
186
377
|
{
|
|
187
378
|
"type": "object",
|
|
188
379
|
"additionalProperties": false,
|
|
189
|
-
"required": [
|
|
380
|
+
"required": [
|
|
381
|
+
"word"
|
|
382
|
+
],
|
|
190
383
|
"properties": {
|
|
191
|
-
"word": {
|
|
192
|
-
|
|
384
|
+
"word": {
|
|
385
|
+
"type": "string",
|
|
386
|
+
"minLength": 1
|
|
387
|
+
},
|
|
388
|
+
"text": {
|
|
389
|
+
"type": "string",
|
|
390
|
+
"minLength": 1
|
|
391
|
+
},
|
|
193
392
|
"severity": {
|
|
194
393
|
"type": "string",
|
|
195
|
-
"enum": [
|
|
394
|
+
"enum": [
|
|
395
|
+
"low",
|
|
396
|
+
"medium",
|
|
397
|
+
"high",
|
|
398
|
+
"critical"
|
|
399
|
+
]
|
|
196
400
|
}
|
|
197
401
|
}
|
|
198
402
|
}
|
|
199
403
|
]
|
|
404
|
+
},
|
|
405
|
+
"x-i18n": {
|
|
406
|
+
"en": {
|
|
407
|
+
"description": "Custom words: strings using defaultSeverity, or { word, severity } objects."
|
|
408
|
+
}
|
|
200
409
|
}
|
|
201
410
|
},
|
|
202
411
|
"wordFiles": {
|
|
203
412
|
"type": "array",
|
|
204
|
-
"items": {
|
|
413
|
+
"items": {
|
|
414
|
+
"type": "string"
|
|
415
|
+
},
|
|
205
416
|
"default": [],
|
|
206
|
-
"description": "词库文件;行格式 word / severity:word / word|severity"
|
|
417
|
+
"description": "词库文件;行格式 word / severity:word / word|severity",
|
|
418
|
+
"x-i18n": {
|
|
419
|
+
"en": {
|
|
420
|
+
"description": "Dictionary file; line formats: word, severity:word, or word|severity."
|
|
421
|
+
}
|
|
422
|
+
}
|
|
207
423
|
},
|
|
208
424
|
"defaultSeverity": {
|
|
209
425
|
"type": "string",
|
|
210
|
-
"enum": [
|
|
426
|
+
"enum": [
|
|
427
|
+
"low",
|
|
428
|
+
"medium",
|
|
429
|
+
"high",
|
|
430
|
+
"critical"
|
|
431
|
+
],
|
|
211
432
|
"default": "high",
|
|
212
|
-
"description": "未标注分级的自定义词默认 severity"
|
|
433
|
+
"description": "未标注分级的自定义词默认 severity",
|
|
434
|
+
"x-i18n": {
|
|
435
|
+
"en": {
|
|
436
|
+
"description": "Default severity for custom words without an explicit severity."
|
|
437
|
+
}
|
|
438
|
+
}
|
|
213
439
|
},
|
|
214
440
|
"severity": {
|
|
215
441
|
"type": "string",
|
|
216
|
-
"enum": [
|
|
442
|
+
"enum": [
|
|
443
|
+
"low",
|
|
444
|
+
"medium",
|
|
445
|
+
"high",
|
|
446
|
+
"critical"
|
|
447
|
+
],
|
|
217
448
|
"default": "high",
|
|
218
|
-
"description": "兼容旧字段,等同 defaultSeverity"
|
|
449
|
+
"description": "兼容旧字段,等同 defaultSeverity",
|
|
450
|
+
"x-i18n": {
|
|
451
|
+
"en": {
|
|
452
|
+
"description": "Legacy alias for defaultSeverity."
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
"url": {
|
|
457
|
+
"type": "string"
|
|
219
458
|
},
|
|
220
|
-
"url": { "type": "string" },
|
|
221
459
|
"headers": {
|
|
222
460
|
"type": "object",
|
|
223
|
-
"additionalProperties": {
|
|
461
|
+
"additionalProperties": {
|
|
462
|
+
"type": "string"
|
|
463
|
+
}
|
|
224
464
|
},
|
|
225
465
|
"timeoutMs": {
|
|
226
466
|
"type": "number",
|
|
@@ -231,7 +471,12 @@
|
|
|
231
471
|
"forceUpload": {
|
|
232
472
|
"type": "boolean",
|
|
233
473
|
"default": false,
|
|
234
|
-
"description": "强制下载图片后上传,不传 URL"
|
|
474
|
+
"description": "强制下载图片后上传,不传 URL",
|
|
475
|
+
"x-i18n": {
|
|
476
|
+
"en": {
|
|
477
|
+
"description": "Download images before uploading them instead of passing the URL."
|
|
478
|
+
}
|
|
479
|
+
}
|
|
235
480
|
}
|
|
236
481
|
}
|
|
237
482
|
}
|