@zhin.js/plugin-content-moderation 1.0.0
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 +173 -0
- package/lib/bypass.d.ts +10 -0
- package/lib/bypass.d.ts.map +1 -0
- package/lib/bypass.js +27 -0
- package/lib/bypass.js.map +1 -0
- package/lib/config.d.ts +5 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +184 -0
- package/lib/config.js.map +1 -0
- package/lib/engine.d.ts +40 -0
- package/lib/engine.d.ts.map +1 -0
- package/lib/engine.js +170 -0
- package/lib/engine.js.map +1 -0
- package/lib/extract.d.ts +18 -0
- package/lib/extract.d.ts.map +1 -0
- package/lib/extract.js +121 -0
- package/lib/extract.js.map +1 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +11 -0
- package/lib/index.js.map +1 -0
- package/lib/providers/builtin-lexicon.d.ts +15 -0
- package/lib/providers/builtin-lexicon.d.ts.map +1 -0
- package/lib/providers/builtin-lexicon.js +133 -0
- package/lib/providers/builtin-lexicon.js.map +1 -0
- package/lib/providers/http.d.ts +14 -0
- package/lib/providers/http.d.ts.map +1 -0
- package/lib/providers/http.js +191 -0
- package/lib/providers/http.js.map +1 -0
- package/lib/providers/local-lexicon.d.ts +28 -0
- package/lib/providers/local-lexicon.d.ts.map +1 -0
- package/lib/providers/local-lexicon.js +129 -0
- package/lib/providers/local-lexicon.js.map +1 -0
- package/lib/providers/registry.d.ts +8 -0
- package/lib/providers/registry.d.ts.map +1 -0
- package/lib/providers/registry.js +24 -0
- package/lib/providers/registry.js.map +1 -0
- package/lib/providers/types.d.ts +6 -0
- package/lib/providers/types.d.ts.map +1 -0
- package/lib/providers/types.js +2 -0
- package/lib/providers/types.js.map +1 -0
- package/lib/redact.d.ts +18 -0
- package/lib/redact.d.ts.map +1 -0
- package/lib/redact.js +102 -0
- package/lib/redact.js.map +1 -0
- package/lib/types.d.ts +105 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +26 -0
- package/lib/types.js.map +1 -0
- package/middlewares/inbound.js +97 -0
- package/middlewares/inbound.ts +112 -0
- package/middlewares/outbound.js +53 -0
- package/middlewares/outbound.ts +66 -0
- package/package.json +81 -0
- package/plugin.js +8 -0
- package/schema.json +240 -0
- package/src/bypass.ts +38 -0
- package/src/config.ts +203 -0
- package/src/engine.ts +220 -0
- package/src/extract.ts +155 -0
- package/src/index.ts +49 -0
- package/src/providers/builtin-lexicon.ts +149 -0
- package/src/providers/http.ts +225 -0
- package/src/providers/local-lexicon.ts +148 -0
- package/src/providers/registry.ts +37 -0
- package/src/providers/types.ts +6 -0
- package/src/redact.ts +141 -0
- package/src/types.ts +143 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { maxSeverity } from '../types.js';
|
|
4
|
+
import { BUILTIN_LEXICON, mergeLexiconEntries } from './builtin-lexicon.js';
|
|
5
|
+
export class LocalLexiconProvider {
|
|
6
|
+
id;
|
|
7
|
+
#entries;
|
|
8
|
+
#onError;
|
|
9
|
+
constructor(config, cwd = process.cwd()) {
|
|
10
|
+
this.id = config.id;
|
|
11
|
+
this.#onError = config.onError;
|
|
12
|
+
this.#entries = Object.freeze(loadLexiconEntries(config, cwd));
|
|
13
|
+
}
|
|
14
|
+
async scan(input) {
|
|
15
|
+
try {
|
|
16
|
+
const matches = findGradedMatches(input.text, this.#entries);
|
|
17
|
+
if (matches.length === 0) {
|
|
18
|
+
return Object.freeze({
|
|
19
|
+
sourceId: this.id,
|
|
20
|
+
severity: 'pass',
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
let severity = matches[0].severity;
|
|
24
|
+
for (const match of matches) {
|
|
25
|
+
severity = maxSeverity(severity, match.severity);
|
|
26
|
+
}
|
|
27
|
+
return Object.freeze({
|
|
28
|
+
sourceId: this.id,
|
|
29
|
+
severity,
|
|
30
|
+
matches: Object.freeze(matches.map(({ start, end }) => ({ start, end }))),
|
|
31
|
+
reason: `lexicon hit (${matches.length}, max=${severity})`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
return Object.freeze({
|
|
36
|
+
sourceId: this.id,
|
|
37
|
+
severity: this.#onError === 'closed' ? 'critical' : 'pass',
|
|
38
|
+
error: true,
|
|
39
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function loadLexiconEntries(config, cwd) {
|
|
45
|
+
const custom = [];
|
|
46
|
+
for (const item of config.words) {
|
|
47
|
+
custom.push({ word: item.word, severity: item.severity });
|
|
48
|
+
}
|
|
49
|
+
for (const file of config.wordFiles) {
|
|
50
|
+
custom.push(...parseWordFile(readFileSync(resolve(cwd, file), 'utf8'), config.defaultSeverity));
|
|
51
|
+
}
|
|
52
|
+
const builtin = config.includeBuiltin ? BUILTIN_LEXICON : undefined;
|
|
53
|
+
return mergeLexiconEntries(builtin, custom);
|
|
54
|
+
}
|
|
55
|
+
/** @deprecated Use loadLexiconEntries; kept for tests that only need bare words. */
|
|
56
|
+
export function loadWords(config, cwd) {
|
|
57
|
+
return loadLexiconEntries(config, cwd).map((e) => e.word);
|
|
58
|
+
}
|
|
59
|
+
export function findMatches(text, words) {
|
|
60
|
+
const entries = words.map((word) => ({ word, severity: 'high' }));
|
|
61
|
+
return findGradedMatches(text, entries).map(({ start, end }) => ({ start, end }));
|
|
62
|
+
}
|
|
63
|
+
export function findGradedMatches(text, entries) {
|
|
64
|
+
if (!text || entries.length === 0)
|
|
65
|
+
return Object.freeze([]);
|
|
66
|
+
const matches = [];
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
if (!entry.word)
|
|
69
|
+
continue;
|
|
70
|
+
let from = 0;
|
|
71
|
+
while (from < text.length) {
|
|
72
|
+
const idx = text.indexOf(entry.word, from);
|
|
73
|
+
if (idx < 0)
|
|
74
|
+
break;
|
|
75
|
+
matches.push({
|
|
76
|
+
start: idx,
|
|
77
|
+
end: idx + entry.word.length,
|
|
78
|
+
word: entry.word,
|
|
79
|
+
severity: entry.severity,
|
|
80
|
+
});
|
|
81
|
+
from = idx + entry.word.length;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
matches.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
85
|
+
return Object.freeze(matches);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Word-file lines:
|
|
89
|
+
* - `# comment`
|
|
90
|
+
* - `word` → defaultSeverity
|
|
91
|
+
* - `severity:word` → graded(low|medium|high|critical)
|
|
92
|
+
* - `word|severity` → graded
|
|
93
|
+
*/
|
|
94
|
+
export function parseWordFile(content, defaultSeverity) {
|
|
95
|
+
const out = [];
|
|
96
|
+
for (const line of content.split(/\r?\n/u)) {
|
|
97
|
+
const trimmed = line.trim();
|
|
98
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
99
|
+
continue;
|
|
100
|
+
const parsed = parseWordLine(trimmed, defaultSeverity);
|
|
101
|
+
if (parsed)
|
|
102
|
+
out.push(parsed);
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
export function parseWordLine(line, defaultSeverity) {
|
|
107
|
+
const pipe = line.indexOf('|');
|
|
108
|
+
if (pipe > 0) {
|
|
109
|
+
const word = line.slice(0, pipe).trim();
|
|
110
|
+
const severity = parseSeverityToken(line.slice(pipe + 1).trim()) ?? defaultSeverity;
|
|
111
|
+
return word ? { word, severity } : null;
|
|
112
|
+
}
|
|
113
|
+
const colon = line.indexOf(':');
|
|
114
|
+
if (colon > 0) {
|
|
115
|
+
const maybeSeverity = parseSeverityToken(line.slice(0, colon).trim());
|
|
116
|
+
if (maybeSeverity) {
|
|
117
|
+
const word = line.slice(colon + 1).trim();
|
|
118
|
+
return word ? { word, severity: maybeSeverity } : null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { word: line, severity: defaultSeverity };
|
|
122
|
+
}
|
|
123
|
+
function parseSeverityToken(value) {
|
|
124
|
+
if (value === 'low' || value === 'medium' || value === 'high' || value === 'critical') {
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=local-lexicon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-lexicon.js","sourceRoot":"","sources":["../../src/providers/local-lexicon.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ModerationConfig } from '../types.js';
|
|
2
|
+
import type { ModerationProvider } from './types.js';
|
|
3
|
+
export interface CreateProvidersOptions {
|
|
4
|
+
readonly cwd?: string;
|
|
5
|
+
readonly fetch?: typeof fetch;
|
|
6
|
+
}
|
|
7
|
+
export declare function createProviders(config: ModerationConfig, options?: CreateProvidersOptions): readonly ModerationProvider[];
|
|
8
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAgB,MAAM,aAAa,CAAC;AAGlE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CAC/B;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE,sBAA2B,GACnC,SAAS,kBAAkB,EAAE,CAS/B"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { HttpModerationProvider } from './http.js';
|
|
2
|
+
import { LocalLexiconProvider } from './local-lexicon.js';
|
|
3
|
+
export function createProviders(config, options = {}) {
|
|
4
|
+
const cwd = options.cwd ?? process.cwd();
|
|
5
|
+
const providers = [];
|
|
6
|
+
for (const source of config.sources) {
|
|
7
|
+
if (!source.enabled)
|
|
8
|
+
continue;
|
|
9
|
+
const provider = createProvider(source, cwd, options.fetch);
|
|
10
|
+
if (provider)
|
|
11
|
+
providers.push(provider);
|
|
12
|
+
}
|
|
13
|
+
return Object.freeze(providers);
|
|
14
|
+
}
|
|
15
|
+
function createProvider(source, cwd, fetchImpl) {
|
|
16
|
+
if (source.type === 'local') {
|
|
17
|
+
return new LocalLexiconProvider(source, cwd);
|
|
18
|
+
}
|
|
19
|
+
if (source.type === 'http') {
|
|
20
|
+
return new HttpModerationProvider(source, { fetch: fetchImpl });
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAQ1D,MAAM,UAAU,eAAe,CAC7B,MAAwB,EACxB,UAAkC,EAAE;IAEpC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,SAAS,GAAyB,EAAE,CAAC;IAC3C,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,SAAS;QAC9B,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QAC5D,IAAI,QAAQ;YAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,cAAc,CACrB,MAAoB,EACpB,GAAW,EACX,SAAwB;IAExB,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC5B,OAAO,IAAI,oBAAoB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC3B,OAAO,IAAI,sBAAsB,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7D,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACjD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":""}
|
package/lib/redact.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ExtractedContent } from './extract.js';
|
|
2
|
+
import type { ExtractedImage, TextMatch } from './types.js';
|
|
3
|
+
export interface RedactOptions {
|
|
4
|
+
readonly maskChar: string;
|
|
5
|
+
readonly matches: readonly TextMatch[];
|
|
6
|
+
readonly flaggedImageIndexes: readonly number[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Mask matched spans in text. When there are no spans but redact is requested
|
|
10
|
+
* for non-empty text, replace the whole string with mask chars.
|
|
11
|
+
*/
|
|
12
|
+
export declare function redactText(text: string, matches: readonly TextMatch[], maskChar: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Apply text masking + remove flagged image segments from outbound payload.
|
|
15
|
+
*/
|
|
16
|
+
export declare function redactOutboundPayload(payload: unknown, extracted: ExtractedContent, options: RedactOptions): unknown;
|
|
17
|
+
export declare function flaggedImagesFromIndexes(images: readonly ExtractedImage[], indexes: readonly number[]): readonly ExtractedImage[];
|
|
18
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;IACvC,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;CACjD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,SAAS,EAAE,EAC7B,QAAQ,EAAE,MAAM,GACf,MAAM,CAgBR;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,aAAa,GACrB,OAAO,CAyBT;AA6DD,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,SAAS,cAAc,EAAE,EACjC,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB,SAAS,cAAc,EAAE,CAG3B"}
|
package/lib/redact.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mask matched spans in text. When there are no spans but redact is requested
|
|
3
|
+
* for non-empty text, replace the whole string with mask chars.
|
|
4
|
+
*/
|
|
5
|
+
export function redactText(text, matches, maskChar) {
|
|
6
|
+
const ch = maskChar.slice(0, 1) || '*';
|
|
7
|
+
if (!text)
|
|
8
|
+
return text;
|
|
9
|
+
if (!matches.length) {
|
|
10
|
+
const len = Math.max(3, Math.min(text.length, 16));
|
|
11
|
+
return ch.repeat(len);
|
|
12
|
+
}
|
|
13
|
+
const chars = [...text];
|
|
14
|
+
const sorted = [...matches].sort((a, b) => a.start - b.start);
|
|
15
|
+
for (const match of sorted) {
|
|
16
|
+
const start = Math.max(0, Math.min(chars.length, match.start));
|
|
17
|
+
const end = Math.max(start, Math.min(chars.length, match.end));
|
|
18
|
+
for (let i = start; i < end; i++)
|
|
19
|
+
chars[i] = ch;
|
|
20
|
+
}
|
|
21
|
+
return chars.join('');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Apply text masking + remove flagged image segments from outbound payload.
|
|
25
|
+
*/
|
|
26
|
+
export function redactOutboundPayload(payload, extracted, options) {
|
|
27
|
+
const flagged = new Set(options.flaggedImageIndexes);
|
|
28
|
+
const flaggedSegmentIndexes = new Set(extracted.images
|
|
29
|
+
.filter((img) => flagged.has(img.index) && img.segmentIndex != null)
|
|
30
|
+
.map((img) => img.segmentIndex));
|
|
31
|
+
if (typeof payload === 'string') {
|
|
32
|
+
return redactText(payload, options.matches, options.maskChar);
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(payload)) {
|
|
35
|
+
return redactSegmentList(payload, options, flaggedSegmentIndexes);
|
|
36
|
+
}
|
|
37
|
+
if (payload && typeof payload === 'object' && 'type' in payload) {
|
|
38
|
+
const list = redactSegmentList([payload], options, flaggedSegmentIndexes);
|
|
39
|
+
return list.length === 1 ? list[0] : list;
|
|
40
|
+
}
|
|
41
|
+
if (extracted.text) {
|
|
42
|
+
return redactText(extracted.text, options.matches, options.maskChar);
|
|
43
|
+
}
|
|
44
|
+
return payload;
|
|
45
|
+
}
|
|
46
|
+
function redactSegmentList(segments, options, flaggedSegmentIndexes) {
|
|
47
|
+
const out = [];
|
|
48
|
+
let textCursor = 0;
|
|
49
|
+
for (let i = 0; i < segments.length; i++) {
|
|
50
|
+
const seg = segments[i];
|
|
51
|
+
if (!seg || typeof seg !== 'object') {
|
|
52
|
+
out.push(seg);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (flaggedSegmentIndexes.has(i))
|
|
56
|
+
continue;
|
|
57
|
+
const record = seg;
|
|
58
|
+
const type = String(record.type ?? '');
|
|
59
|
+
if (type === 'text') {
|
|
60
|
+
const data = asRecord(record.data);
|
|
61
|
+
const text = typeof data.text === 'string' ? data.text : '';
|
|
62
|
+
const localMatches = shiftMatches(options.matches, textCursor, text.length);
|
|
63
|
+
// No spans overall → whole-segment mask; otherwise only mask local hits.
|
|
64
|
+
const nextText = options.matches.length === 0
|
|
65
|
+
? redactText(text, [], options.maskChar)
|
|
66
|
+
: localMatches.length > 0
|
|
67
|
+
? redactText(text, localMatches, options.maskChar)
|
|
68
|
+
: text;
|
|
69
|
+
out.push({
|
|
70
|
+
...record,
|
|
71
|
+
data: { ...data, text: nextText },
|
|
72
|
+
});
|
|
73
|
+
textCursor += text.length;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
out.push(seg);
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
function shiftMatches(matches, offset, length) {
|
|
81
|
+
const end = offset + length;
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const m of matches) {
|
|
84
|
+
if (m.end <= offset || m.start >= end)
|
|
85
|
+
continue;
|
|
86
|
+
out.push({
|
|
87
|
+
start: Math.max(0, m.start - offset),
|
|
88
|
+
end: Math.min(length, m.end - offset),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
export function flaggedImagesFromIndexes(images, indexes) {
|
|
94
|
+
const set = new Set(indexes);
|
|
95
|
+
return Object.freeze(images.filter((img) => set.has(img.index)));
|
|
96
|
+
}
|
|
97
|
+
function asRecord(value) {
|
|
98
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
99
|
+
? value
|
|
100
|
+
: {};
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"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"}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export type Severity = 'pass' | 'low' | 'medium' | 'high' | 'critical';
|
|
2
|
+
export type Action = 'allow' | 'log' | 'reply' | 'redact' | 'drop' | 'recall';
|
|
3
|
+
export type OnErrorPolicy = 'open' | 'closed';
|
|
4
|
+
export type Direction = 'inbound' | 'outbound';
|
|
5
|
+
export interface TextMatch {
|
|
6
|
+
readonly start: number;
|
|
7
|
+
readonly end: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ExtractedImage {
|
|
10
|
+
/** Index among extracted images (for HTTP / flaggedImageIndexes). */
|
|
11
|
+
readonly index: number;
|
|
12
|
+
/** Index in the original segment list when applicable. */
|
|
13
|
+
readonly segmentIndex?: number;
|
|
14
|
+
readonly url?: string;
|
|
15
|
+
readonly base64?: string;
|
|
16
|
+
readonly mime?: string;
|
|
17
|
+
readonly path?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ScanContext {
|
|
20
|
+
readonly adapter: string;
|
|
21
|
+
readonly endpoint: string;
|
|
22
|
+
readonly conversationKind: string;
|
|
23
|
+
readonly conversationId: string;
|
|
24
|
+
readonly sender?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ScanInput {
|
|
27
|
+
readonly text: string;
|
|
28
|
+
readonly images: readonly ExtractedImage[];
|
|
29
|
+
readonly direction: Direction;
|
|
30
|
+
readonly context: ScanContext;
|
|
31
|
+
}
|
|
32
|
+
export interface ProviderResult {
|
|
33
|
+
readonly sourceId: string;
|
|
34
|
+
readonly severity: Severity;
|
|
35
|
+
readonly matches?: readonly TextMatch[];
|
|
36
|
+
readonly flaggedImageIndexes?: readonly number[];
|
|
37
|
+
readonly reason?: string;
|
|
38
|
+
readonly error?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface MergedResult {
|
|
41
|
+
readonly severity: Severity;
|
|
42
|
+
readonly matches: readonly TextMatch[];
|
|
43
|
+
readonly flaggedImageIndexes: readonly number[];
|
|
44
|
+
readonly sources: readonly ProviderResult[];
|
|
45
|
+
readonly reason?: string;
|
|
46
|
+
}
|
|
47
|
+
/** Graded lexicon item (config `words` entry). */
|
|
48
|
+
export interface LexiconWordConfig {
|
|
49
|
+
readonly word: string;
|
|
50
|
+
/** Per-word severity; falls back to source `defaultSeverity`. */
|
|
51
|
+
readonly severity: Exclude<Severity, 'pass'>;
|
|
52
|
+
}
|
|
53
|
+
export interface LocalSourceConfig {
|
|
54
|
+
readonly id: string;
|
|
55
|
+
readonly type: 'local';
|
|
56
|
+
readonly enabled: boolean;
|
|
57
|
+
readonly onError: OnErrorPolicy;
|
|
58
|
+
/** Custom words with per-entry severity. */
|
|
59
|
+
readonly words: readonly LexiconWordConfig[];
|
|
60
|
+
readonly wordFiles: readonly string[];
|
|
61
|
+
/** Merge built-in graded lexicon (default true). */
|
|
62
|
+
readonly includeBuiltin: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Default severity for plain string words / ungraded file lines.
|
|
65
|
+
* (Config key may still be written as `severity` for compatibility.)
|
|
66
|
+
*/
|
|
67
|
+
readonly defaultSeverity: Exclude<Severity, 'pass'>;
|
|
68
|
+
}
|
|
69
|
+
export interface HttpSourceConfig {
|
|
70
|
+
readonly id: string;
|
|
71
|
+
readonly type: 'http';
|
|
72
|
+
readonly enabled: boolean;
|
|
73
|
+
readonly onError: OnErrorPolicy;
|
|
74
|
+
readonly url: string;
|
|
75
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
76
|
+
readonly timeoutMs: number;
|
|
77
|
+
readonly forceUpload: boolean;
|
|
78
|
+
}
|
|
79
|
+
export type SourceConfig = LocalSourceConfig | HttpSourceConfig;
|
|
80
|
+
export interface ModerationConfig {
|
|
81
|
+
readonly enabled: boolean;
|
|
82
|
+
readonly onError: OnErrorPolicy;
|
|
83
|
+
readonly maskChar: string;
|
|
84
|
+
readonly replyTemplate: string;
|
|
85
|
+
readonly masters: readonly string[];
|
|
86
|
+
readonly inbound: {
|
|
87
|
+
readonly enabled: boolean;
|
|
88
|
+
readonly bypassMasters: boolean;
|
|
89
|
+
readonly whitelist: {
|
|
90
|
+
readonly userIds: readonly string[];
|
|
91
|
+
readonly conversationIds: readonly string[];
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
readonly outbound: {
|
|
95
|
+
readonly enabled: boolean;
|
|
96
|
+
readonly bypass: boolean;
|
|
97
|
+
};
|
|
98
|
+
readonly actions: Readonly<Record<Severity, readonly Action[]>>;
|
|
99
|
+
readonly sources: readonly SourceConfig[];
|
|
100
|
+
}
|
|
101
|
+
export declare const SEVERITY_RANK: Readonly<Record<Severity, number>>;
|
|
102
|
+
export declare function maxSeverity(a: Severity, b: Severity): Severity;
|
|
103
|
+
export declare function isSeverity(value: unknown): value is Severity;
|
|
104
|
+
export declare function isAction(value: unknown): value is Action;
|
|
105
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/lib/types.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const SEVERITY_RANK = Object.freeze({
|
|
2
|
+
pass: 0,
|
|
3
|
+
low: 1,
|
|
4
|
+
medium: 2,
|
|
5
|
+
high: 3,
|
|
6
|
+
critical: 4,
|
|
7
|
+
});
|
|
8
|
+
export function maxSeverity(a, b) {
|
|
9
|
+
return SEVERITY_RANK[a] >= SEVERITY_RANK[b] ? a : b;
|
|
10
|
+
}
|
|
11
|
+
export function isSeverity(value) {
|
|
12
|
+
return value === 'pass'
|
|
13
|
+
|| value === 'low'
|
|
14
|
+
|| value === 'medium'
|
|
15
|
+
|| value === 'high'
|
|
16
|
+
|| value === 'critical';
|
|
17
|
+
}
|
|
18
|
+
export function isAction(value) {
|
|
19
|
+
return value === 'allow'
|
|
20
|
+
|| value === 'log'
|
|
21
|
+
|| value === 'reply'
|
|
22
|
+
|| value === 'redact'
|
|
23
|
+
|| value === 'drop'
|
|
24
|
+
|| value === 'recall';
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=types.js.map
|
package/lib/types.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAmHA,MAAM,CAAC,MAAM,aAAa,GAAuC,MAAM,CAAC,MAAM,CAAC;IAC7E,IAAI,EAAE,CAAC;IACP,GAAG,EAAE,CAAC;IACN,MAAM,EAAE,CAAC;IACT,IAAI,EAAE,CAAC;IACP,QAAQ,EAAE,CAAC;CACZ,CAAC,CAAC;AAEH,MAAM,UAAU,WAAW,CAAC,CAAW,EAAE,CAAW;IAClD,OAAO,aAAa,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,KAAK,KAAK,MAAM;WAClB,KAAK,KAAK,KAAK;WACf,KAAK,KAAK,QAAQ;WAClB,KAAK,KAAK,MAAM;WAChB,KAAK,KAAK,UAAU,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,KAAK,KAAK,OAAO;WACnB,KAAK,KAAK,KAAK;WACf,KAAK,KAAK,OAAO;WACjB,KAAK,KAAK,QAAQ;WAClB,KAAK,KAAK,MAAM;WAChB,KAAK,KAAK,QAAQ,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineMiddleware } from '@zhin.js/middleware';
|
|
3
|
+
import { outboundHostToken } from '@zhin.js/plugin-runtime';
|
|
4
|
+
import { buildScanContext, extractFromTextAndSegments, getModerationEngine, resolveModerationConfig, shouldBypassInbound, } from "../lib/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* Inbound content moderation.
|
|
7
|
+
* - order -100: run early before commands / other middleware
|
|
8
|
+
* - inbound redact:无法改写只读 Message,仍 next() 放行
|
|
9
|
+
*/
|
|
10
|
+
export default defineMiddleware({
|
|
11
|
+
target: 'inbound',
|
|
12
|
+
phase: 'before-dispatch',
|
|
13
|
+
order: -100,
|
|
14
|
+
async handle(context, next) {
|
|
15
|
+
const config = resolveModerationConfig(context.config);
|
|
16
|
+
if (!config.enabled || !config.inbound.enabled) {
|
|
17
|
+
await next();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const message = context.input;
|
|
21
|
+
const sender = message.sender;
|
|
22
|
+
const conversationId = message.conversation.id;
|
|
23
|
+
const endpointMasters = resolveEndpointMasters(message, config);
|
|
24
|
+
if (shouldBypassInbound(config, {
|
|
25
|
+
sender,
|
|
26
|
+
conversationId,
|
|
27
|
+
endpointMasters,
|
|
28
|
+
})) {
|
|
29
|
+
await next();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const engine = getModerationEngine();
|
|
33
|
+
engine.configure(config);
|
|
34
|
+
const extracted = extractFromTextAndSegments(message.content, message.segments);
|
|
35
|
+
if (!extracted.text && extracted.images.length === 0) {
|
|
36
|
+
await next();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const scanInput = {
|
|
40
|
+
text: extracted.text,
|
|
41
|
+
images: extracted.images,
|
|
42
|
+
direction: 'inbound',
|
|
43
|
+
context: buildScanContext({
|
|
44
|
+
adapter: message.conversation.endpoint.adapter,
|
|
45
|
+
endpoint: message.conversation.endpoint.id,
|
|
46
|
+
conversationKind: message.conversation.kind,
|
|
47
|
+
conversationId,
|
|
48
|
+
sender,
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
const result = await engine.apply({
|
|
52
|
+
direction: 'inbound',
|
|
53
|
+
extracted,
|
|
54
|
+
scanInput,
|
|
55
|
+
hooks: {
|
|
56
|
+
reply: async (content) => {
|
|
57
|
+
await message.$reply(content);
|
|
58
|
+
},
|
|
59
|
+
recall: async () => {
|
|
60
|
+
const messageId = message.id;
|
|
61
|
+
if (!messageId)
|
|
62
|
+
return false;
|
|
63
|
+
try {
|
|
64
|
+
const host = context.use(outboundHostToken);
|
|
65
|
+
if (!host.recall)
|
|
66
|
+
return false;
|
|
67
|
+
await host.recall({
|
|
68
|
+
adapter: message.conversation.endpoint.adapter,
|
|
69
|
+
endpointId: message.conversation.endpoint.id,
|
|
70
|
+
messageId,
|
|
71
|
+
});
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
if (result.continue) {
|
|
81
|
+
await next();
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
/**
|
|
86
|
+
* Masters from plugin config + optional metadata.endpointMaster / metadata.master.
|
|
87
|
+
*/
|
|
88
|
+
function resolveEndpointMasters(message, config) {
|
|
89
|
+
const out = [...config.masters];
|
|
90
|
+
const meta = message.metadata ?? {};
|
|
91
|
+
for (const key of ['master', 'endpointMaster', 'owner']) {
|
|
92
|
+
const value = meta[key];
|
|
93
|
+
if (typeof value === 'string' && value.trim())
|
|
94
|
+
out.push(value.trim());
|
|
95
|
+
}
|
|
96
|
+
return Object.freeze(out);
|
|
97
|
+
}
|