@sideid/id-profanity-filter 1.11.7 → 1.11.8
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/dist/index.d.ts +2 -2
- package/dist/index.esm.js +1004 -438
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1004 -438
- package/dist/index.js.map +1 -1
- package/dist/types/config/options.d.ts +1 -1
- package/dist/types/constants/categories/blasphemy.d.ts +1 -1
- package/dist/types/constants/categories/disgusting.d.ts +1 -1
- package/dist/types/constants/categories/drugs.d.ts +1 -1
- package/dist/types/constants/categories/insult.d.ts +1 -1
- package/dist/types/constants/categories/profanity.d.ts +1 -1
- package/dist/types/constants/categories/sexual.d.ts +1 -1
- package/dist/types/constants/categories/slur.d.ts +1 -1
- package/dist/types/constants/regions/bali.d.ts +1 -1
- package/dist/types/constants/regions/batak.d.ts +1 -1
- package/dist/types/constants/regions/betawi.d.ts +1 -1
- package/dist/types/constants/regions/general.d.ts +1 -1
- package/dist/types/constants/regions/minang.d.ts +4 -0
- package/dist/types/constants/wordList.d.ts +10 -1
- package/dist/types/core/analyzer.d.ts +1 -1
- package/dist/types/core/filter.d.ts +1 -1
- package/dist/types/core/matcher.d.ts +1 -1
- package/dist/types/types/index.d.ts +2 -2
- package/dist/types/utils/regexUtils.d.ts +1 -1
- package/package.json +1 -1
- package/src/config/options.ts +25 -30
- package/src/constants/categories/blasphemy.ts +26 -15
- package/src/constants/categories/disgusting.ts +62 -54
- package/src/constants/categories/drugs.ts +56 -47
- package/src/constants/categories/insult.ts +80 -76
- package/src/constants/categories/profanity.ts +98 -92
- package/src/constants/categories/sexual.ts +74 -65
- package/src/constants/categories/slur.ts +73 -66
- package/src/constants/regions/aceh.ts +5 -9
- package/src/constants/regions/bali.ts +4 -7
- package/src/constants/regions/batak.ts +5 -9
- package/src/constants/regions/betawi.ts +19 -22
- package/src/constants/regions/general.ts +140 -144
- package/src/constants/regions/jawa.ts +11 -29
- package/src/constants/regions/madura.ts +2 -4
- package/src/constants/regions/minang.ts +53 -0
- package/src/constants/regions/sunda.ts +2 -4
- package/src/constants/wordList.ts +31 -34
- package/src/core/analyzer.ts +15 -26
- package/src/core/filter.ts +23 -39
- package/src/core/matcher.ts +25 -49
- package/src/index.ts +5 -16
- package/src/types/index.ts +26 -25
- package/src/utils/ahoCorasick.ts +1 -1
- package/src/utils/regexUtils.ts +37 -43
- package/src/utils/similarityUtils.ts +13 -28
- package/src/utils/stringUtils.ts +30 -38
- /package/{prettierrc → .prettierrc} +0 -0
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { ProfanityWord } from
|
|
2
|
-
import { sexualWords } from
|
|
3
|
-
import { insultWords } from
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { ProfanityWord } from '../types';
|
|
2
|
+
import { sexualWords } from './categories/sexual';
|
|
3
|
+
import { insultWords } from './categories/insult';
|
|
4
|
+
import { profanityWords } from './categories/profanity';
|
|
5
|
+
import { slurWords } from './categories/slur';
|
|
6
|
+
import { drugsWords } from './categories/drugs';
|
|
7
|
+
import { disgustingWords } from './categories/disgusting';
|
|
8
|
+
import { blasphemyWords } from './categories/blasphemy';
|
|
9
9
|
|
|
10
|
-
import { general, generalWords } from
|
|
11
|
-
import { jawa, jawaWords } from
|
|
12
|
-
import { sunda, sundaWords } from
|
|
13
|
-
import { betawi, betawiWords } from
|
|
14
|
-
import { batak, batakWords } from
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
import { general, generalWords } from './regions/general';
|
|
11
|
+
import { jawa, jawaWords } from './regions/jawa';
|
|
12
|
+
import { sunda, sundaWords } from './regions/sunda';
|
|
13
|
+
import { betawi, betawiWords } from './regions/betawi';
|
|
14
|
+
import { batak, batakWords } from './regions/batak';
|
|
15
|
+
import { minang, minangWords } from './regions/minang';
|
|
16
|
+
import { bali, baliWords } from './regions/bali';
|
|
17
|
+
import { madura, maduraWords } from './regions/madura';
|
|
18
18
|
// import { bugis, bugisWords } from './regions/bugis';
|
|
19
|
-
|
|
19
|
+
import { aceh, acehWords } from './regions/aceh';
|
|
20
20
|
// import { ambon, ambonWords } from './regions/ambon';
|
|
21
21
|
// import { papua, papuaWords } from './regions/papua';
|
|
22
22
|
// import { manado, manadoWords } from './regions/manado';
|
|
@@ -29,11 +29,11 @@ import { batak, batakWords } from "./regions/batak";
|
|
|
29
29
|
export const wordCategories = {
|
|
30
30
|
sexual: sexualWords,
|
|
31
31
|
insult: insultWords,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
profanity: profanityWords,
|
|
33
|
+
slur: slurWords,
|
|
34
|
+
drugs: drugsWords,
|
|
35
|
+
disgusting: disgustingWords,
|
|
36
|
+
blasphemy: blasphemyWords,
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
export const wordRegions = {
|
|
@@ -42,11 +42,11 @@ export const wordRegions = {
|
|
|
42
42
|
sunda: sundaWords,
|
|
43
43
|
betawi: betawiWords,
|
|
44
44
|
batak: batakWords,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
minang: minangWords,
|
|
46
|
+
bali: baliWords,
|
|
47
|
+
madura: maduraWords,
|
|
48
48
|
// bugis: bugisWords,
|
|
49
|
-
|
|
49
|
+
aceh: acehWords,
|
|
50
50
|
// ambon: ambonWords,
|
|
51
51
|
// papua: papuaWords,
|
|
52
52
|
// manado: manadoWords,
|
|
@@ -63,11 +63,11 @@ export const wordObjects: ProfanityWord[] = [
|
|
|
63
63
|
...sunda,
|
|
64
64
|
...betawi,
|
|
65
65
|
...batak,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
...minang,
|
|
67
|
+
...bali,
|
|
68
|
+
...madura,
|
|
69
69
|
// ...bugis,
|
|
70
|
-
|
|
70
|
+
...aceh,
|
|
71
71
|
// ...ambon,
|
|
72
72
|
// ...papua,
|
|
73
73
|
// ...manado,
|
|
@@ -122,10 +122,7 @@ export function getWordMetadata(word: string): ProfanityWord | undefined {
|
|
|
122
122
|
return wordObjects.find(
|
|
123
123
|
(item) =>
|
|
124
124
|
item.word.toLowerCase() === word.toLowerCase() ||
|
|
125
|
-
(item.aliases &&
|
|
126
|
-
item.aliases.some(
|
|
127
|
-
(alias) => alias.toLowerCase() === word.toLowerCase(),
|
|
128
|
-
)),
|
|
125
|
+
(item.aliases && item.aliases.some((alias) => alias.toLowerCase() === word.toLowerCase()))
|
|
129
126
|
);
|
|
130
127
|
}
|
|
131
128
|
|
package/src/core/analyzer.ts
CHANGED
|
@@ -1,23 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FilterOptions,
|
|
3
|
-
AnalysisResult,
|
|
4
|
-
ProfanityCategory,
|
|
5
|
-
Region,
|
|
6
|
-
} from "../types";
|
|
1
|
+
import { FilterOptions, AnalysisResult, ProfanityCategory, Region } from '../types';
|
|
7
2
|
import {
|
|
8
3
|
findProfanity,
|
|
9
4
|
findProfanityWithMetadata,
|
|
10
5
|
findCategories,
|
|
11
6
|
findRegions,
|
|
12
7
|
calculateSeverity,
|
|
13
|
-
} from
|
|
14
|
-
import { splitIntoSentences } from
|
|
8
|
+
} from './matcher';
|
|
9
|
+
import { splitIntoSentences } from '../utils/stringUtils';
|
|
15
10
|
import {
|
|
16
11
|
findPossibleProfanityBySimiliarity,
|
|
17
12
|
findProfanityByLevenshteinDistance,
|
|
18
|
-
} from
|
|
19
|
-
import { createContextRegex } from
|
|
20
|
-
import { DEFAULT_OPTIONS } from
|
|
13
|
+
} from '../utils/similarityUtils';
|
|
14
|
+
import { createContextRegex } from '../utils/regexUtils';
|
|
15
|
+
import { DEFAULT_OPTIONS } from '../config/options';
|
|
21
16
|
|
|
22
17
|
/**
|
|
23
18
|
* Menganalisis teks untuk kata kotor
|
|
@@ -26,10 +21,7 @@ import { DEFAULT_OPTIONS } from "../config/options";
|
|
|
26
21
|
* @param options Opsi untuk analisis
|
|
27
22
|
* @return AnalysisResult dengan hasil analisis
|
|
28
23
|
*/
|
|
29
|
-
export function analyze(
|
|
30
|
-
text: string,
|
|
31
|
-
options: FilterOptions = {},
|
|
32
|
-
): AnalysisResult {
|
|
24
|
+
export function analyze(text: string, options: FilterOptions = {}): AnalysisResult {
|
|
33
25
|
const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
34
26
|
|
|
35
27
|
const matches = findProfanity(text, mergedOptions);
|
|
@@ -65,7 +57,7 @@ export function analyze(
|
|
|
65
57
|
text,
|
|
66
58
|
wordList,
|
|
67
59
|
mergedOptions.similarityThreshold || 0.8,
|
|
68
|
-
mergedOptions.maxLevenshteinDistance || 2
|
|
60
|
+
mergedOptions.maxLevenshteinDistance || 2
|
|
69
61
|
);
|
|
70
62
|
|
|
71
63
|
similarWords = levenshteinResults.map((item) => ({
|
|
@@ -77,7 +69,7 @@ export function analyze(
|
|
|
77
69
|
similarWords = findPossibleProfanityBySimiliarity(
|
|
78
70
|
text,
|
|
79
71
|
wordList,
|
|
80
|
-
mergedOptions.similarityThreshold || 0.8
|
|
72
|
+
mergedOptions.similarityThreshold || 0.8
|
|
81
73
|
);
|
|
82
74
|
}
|
|
83
75
|
}
|
|
@@ -103,7 +95,7 @@ export function analyze(
|
|
|
103
95
|
*/
|
|
104
96
|
export function batchAnalyze(
|
|
105
97
|
texts: string[],
|
|
106
|
-
options: FilterOptions = {}
|
|
98
|
+
options: FilterOptions = {}
|
|
107
99
|
): {
|
|
108
100
|
totalTexts: number;
|
|
109
101
|
profaneTexts: number;
|
|
@@ -116,10 +108,7 @@ export function batchAnalyze(
|
|
|
116
108
|
const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
117
109
|
const results = texts.map((text) => analyze(text, mergedOptions));
|
|
118
110
|
const profaneTexts = results.filter((result) => result.hasProfanity).length;
|
|
119
|
-
const totalSeverity = results.reduce(
|
|
120
|
-
(sum, result) => sum + result.severityScore,
|
|
121
|
-
0,
|
|
122
|
-
);
|
|
111
|
+
const totalSeverity = results.reduce((sum, result) => sum + result.severityScore, 0);
|
|
123
112
|
const averageSeverity = profaneTexts > 0 ? totalSeverity / profaneTexts : 0;
|
|
124
113
|
const allCategories = results.flatMap((result) => result.categories);
|
|
125
114
|
const categoryCount: Record<string, number> = {};
|
|
@@ -175,7 +164,7 @@ export function batchAnalyze(
|
|
|
175
164
|
*/
|
|
176
165
|
export function analyzeBySentence(
|
|
177
166
|
text: string,
|
|
178
|
-
options: FilterOptions = {}
|
|
167
|
+
options: FilterOptions = {}
|
|
179
168
|
): Array<AnalysisResult & { sentence: string }> {
|
|
180
169
|
const sentences = splitIntoSentences(text);
|
|
181
170
|
const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
@@ -201,7 +190,7 @@ export function analyzeBySentence(
|
|
|
201
190
|
export function analyzeWithContext(
|
|
202
191
|
text: string,
|
|
203
192
|
contextWindowSize: number = 5,
|
|
204
|
-
options: FilterOptions = {}
|
|
193
|
+
options: FilterOptions = {}
|
|
205
194
|
): Array<{
|
|
206
195
|
word: string;
|
|
207
196
|
context: string;
|
|
@@ -221,9 +210,9 @@ export function analyzeWithContext(
|
|
|
221
210
|
|
|
222
211
|
let match;
|
|
223
212
|
while ((match = regex.exec(text)) !== null) {
|
|
224
|
-
const beforeContext = match[1] ||
|
|
213
|
+
const beforeContext = match[1] || '';
|
|
225
214
|
const wordMatch = match[2];
|
|
226
|
-
const afterContext = match[3] ||
|
|
215
|
+
const afterContext = match[3] || '';
|
|
227
216
|
|
|
228
217
|
result.push({
|
|
229
218
|
word: wordMatch,
|
package/src/core/filter.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { FilterOptions, FilterResult, ProfanityWord } from
|
|
2
|
-
import { findProfanity, findProfanityWithMetadata } from
|
|
3
|
-
import { censorWord, escapeRegExp } from
|
|
4
|
-
import { createWordRegex } from
|
|
5
|
-
import { DEFAULT_OPTIONS, makeRandomGrawlixString } from
|
|
1
|
+
import { FilterOptions, FilterResult, ProfanityWord } from '../types';
|
|
2
|
+
import { findProfanity, findProfanityWithMetadata } from './matcher';
|
|
3
|
+
import { censorWord, escapeRegExp } from '../utils/stringUtils';
|
|
4
|
+
import { createWordRegex } from '../utils/regexUtils';
|
|
5
|
+
import { DEFAULT_OPTIONS, makeRandomGrawlixString } from '../config/options';
|
|
6
6
|
|
|
7
7
|
interface FindProfanityFunction {
|
|
8
8
|
(text: string, options?: FilterOptions): string[];
|
|
@@ -16,12 +16,9 @@ interface FindProfanityFunction {
|
|
|
16
16
|
* @param options Opsi untuk filter
|
|
17
17
|
* @returns FilterResult dengan hasil filter
|
|
18
18
|
*/
|
|
19
|
-
export function filter(
|
|
20
|
-
text: string,
|
|
21
|
-
options: FilterOptions = {},
|
|
22
|
-
): FilterResult {
|
|
19
|
+
export function filter(text: string, options: FilterOptions = {}): FilterResult {
|
|
23
20
|
const {
|
|
24
|
-
replaceWith =
|
|
21
|
+
replaceWith = '*',
|
|
25
22
|
fullWordCensor = true,
|
|
26
23
|
detectLeetSpeak = true,
|
|
27
24
|
whitelist = [],
|
|
@@ -74,10 +71,7 @@ export function filter(
|
|
|
74
71
|
const metadata = matchDetails.find(
|
|
75
72
|
(m) =>
|
|
76
73
|
m.word.toLowerCase() === word.toLowerCase() ||
|
|
77
|
-
(m.aliases &&
|
|
78
|
-
m.aliases.some(
|
|
79
|
-
(alias) => alias.toLowerCase() === word.toLowerCase(),
|
|
80
|
-
)),
|
|
74
|
+
(m.aliases && m.aliases.some((alias) => alias.toLowerCase() === word.toLowerCase()))
|
|
81
75
|
);
|
|
82
76
|
|
|
83
77
|
const variants = actualMatches.get(word.toLowerCase()) || [];
|
|
@@ -86,7 +80,7 @@ export function filter(
|
|
|
86
80
|
const uniqueVariants = [...new Set(variants)];
|
|
87
81
|
|
|
88
82
|
uniqueVariants.forEach((variant) => {
|
|
89
|
-
const regex = new RegExp(`\\b${escapeRegExp(variant)}\\b`,
|
|
83
|
+
const regex = new RegExp(`\\b${escapeRegExp(variant)}\\b`, 'gi');
|
|
90
84
|
|
|
91
85
|
let match;
|
|
92
86
|
while ((match = regex.exec(filteredText)) !== null) {
|
|
@@ -98,11 +92,7 @@ export function filter(
|
|
|
98
92
|
if (useRandomGrawlix) {
|
|
99
93
|
censoredWord = makeRandomGrawlixString(originalWord.length);
|
|
100
94
|
} else {
|
|
101
|
-
censoredWord = censorWord(
|
|
102
|
-
originalWord,
|
|
103
|
-
replaceWith,
|
|
104
|
-
!fullWordCensor && keepFirstAndLast,
|
|
105
|
-
);
|
|
95
|
+
censoredWord = censorWord(originalWord, replaceWith, !fullWordCensor && keepFirstAndLast);
|
|
106
96
|
}
|
|
107
97
|
|
|
108
98
|
replacements.push({
|
|
@@ -112,8 +102,8 @@ export function filter(
|
|
|
112
102
|
});
|
|
113
103
|
|
|
114
104
|
filteredText = filteredText.replace(
|
|
115
|
-
new RegExp(`\\b${escapeRegExp(originalWord)}\\b`,
|
|
116
|
-
censoredWord
|
|
105
|
+
new RegExp(`\\b${escapeRegExp(originalWord)}\\b`, 'g'),
|
|
106
|
+
censoredWord
|
|
117
107
|
);
|
|
118
108
|
}
|
|
119
109
|
});
|
|
@@ -141,7 +131,7 @@ export function filter(
|
|
|
141
131
|
censoredWord = censorWord(
|
|
142
132
|
originalWord,
|
|
143
133
|
replaceWith,
|
|
144
|
-
!fullWordCensor && keepFirstAndLast
|
|
134
|
+
!fullWordCensor && keepFirstAndLast
|
|
145
135
|
);
|
|
146
136
|
}
|
|
147
137
|
|
|
@@ -152,8 +142,8 @@ export function filter(
|
|
|
152
142
|
});
|
|
153
143
|
|
|
154
144
|
filteredText = filteredText.replace(
|
|
155
|
-
new RegExp(escapeRegExp(originalWord),
|
|
156
|
-
censoredWord
|
|
145
|
+
new RegExp(escapeRegExp(originalWord), 'g'),
|
|
146
|
+
censoredWord
|
|
157
147
|
);
|
|
158
148
|
}
|
|
159
149
|
}
|
|
@@ -180,7 +170,7 @@ export function filter(
|
|
|
180
170
|
censoredWord = censorWord(
|
|
181
171
|
originalWord,
|
|
182
172
|
replaceWith,
|
|
183
|
-
!fullWordCensor && keepFirstAndLast
|
|
173
|
+
!fullWordCensor && keepFirstAndLast
|
|
184
174
|
);
|
|
185
175
|
}
|
|
186
176
|
|
|
@@ -191,8 +181,8 @@ export function filter(
|
|
|
191
181
|
});
|
|
192
182
|
|
|
193
183
|
filteredText = filteredText.replace(
|
|
194
|
-
new RegExp(escapeRegExp(originalWord),
|
|
195
|
-
censoredWord
|
|
184
|
+
new RegExp(escapeRegExp(originalWord), 'g'),
|
|
185
|
+
censoredWord
|
|
196
186
|
);
|
|
197
187
|
}
|
|
198
188
|
}
|
|
@@ -204,19 +194,13 @@ export function filter(
|
|
|
204
194
|
const metadata = matchDetails.find(
|
|
205
195
|
(m) =>
|
|
206
196
|
m.word.toLowerCase() === word.toLowerCase() ||
|
|
207
|
-
(m.aliases &&
|
|
208
|
-
m.aliases.some(
|
|
209
|
-
(alias) => alias.toLowerCase() === word.toLowerCase(),
|
|
210
|
-
)),
|
|
197
|
+
(m.aliases && m.aliases.some((alias) => alias.toLowerCase() === word.toLowerCase()))
|
|
211
198
|
);
|
|
212
199
|
|
|
213
200
|
const variants = actualMatches.get(word.toLowerCase()) || [];
|
|
214
201
|
|
|
215
202
|
variants.forEach((variant) => {
|
|
216
|
-
const exactVariantRegex = new RegExp(
|
|
217
|
-
`\\b${escapeRegExp(variant)}\\b`,
|
|
218
|
-
"gi",
|
|
219
|
-
);
|
|
203
|
+
const exactVariantRegex = new RegExp(`\\b${escapeRegExp(variant)}\\b`, 'gi');
|
|
220
204
|
|
|
221
205
|
let match;
|
|
222
206
|
while ((match = exactVariantRegex.exec(filteredText)) !== null) {
|
|
@@ -231,7 +215,7 @@ export function filter(
|
|
|
231
215
|
censoredWord = censorWord(
|
|
232
216
|
originalWord,
|
|
233
217
|
replaceWith,
|
|
234
|
-
!fullWordCensor && keepFirstAndLast
|
|
218
|
+
!fullWordCensor && keepFirstAndLast
|
|
235
219
|
);
|
|
236
220
|
}
|
|
237
221
|
|
|
@@ -242,8 +226,8 @@ export function filter(
|
|
|
242
226
|
});
|
|
243
227
|
|
|
244
228
|
filteredText = filteredText.replace(
|
|
245
|
-
new RegExp(`\\b${escapeRegExp(originalWord)}\\b`,
|
|
246
|
-
censoredWord
|
|
229
|
+
new RegExp(`\\b${escapeRegExp(originalWord)}\\b`, 'g'),
|
|
230
|
+
censoredWord
|
|
247
231
|
);
|
|
248
232
|
}
|
|
249
233
|
});
|
package/src/core/matcher.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "../types";
|
|
7
|
-
|
|
8
|
-
import { wordObjects } from "../constants/wordList";
|
|
9
|
-
import { normalizeText } from "../utils/stringUtils";
|
|
10
|
-
import { createWordRegex } from "../utils/regexUtils";
|
|
1
|
+
import { ProfanityWord, ProfanityCategory, Region, FilterOptions } from '../types';
|
|
2
|
+
|
|
3
|
+
import { wordObjects } from '../constants/wordList';
|
|
4
|
+
import { normalizeText } from '../utils/stringUtils';
|
|
5
|
+
import { createWordRegex } from '../utils/regexUtils';
|
|
11
6
|
import {
|
|
12
7
|
findPossibleProfanityBySimiliarity,
|
|
13
8
|
findProfanityByLevenshteinDistance,
|
|
14
|
-
} from
|
|
15
|
-
import { DEFAULT_OPTIONS } from
|
|
16
|
-
import { AhoCorasick } from
|
|
9
|
+
} from '../utils/similarityUtils';
|
|
10
|
+
import { DEFAULT_OPTIONS } from '../config/options';
|
|
11
|
+
import { AhoCorasick } from '../utils/ahoCorasick';
|
|
17
12
|
|
|
18
13
|
const globalAhoCorasick = new AhoCorasick();
|
|
19
14
|
let ahoCorasickInitialized = false;
|
|
@@ -34,10 +29,7 @@ interface FindProfanityFunction {
|
|
|
34
29
|
lastActualMatches?: Map<string, string[]>;
|
|
35
30
|
}
|
|
36
31
|
|
|
37
|
-
export function findProfanity(
|
|
38
|
-
text: string,
|
|
39
|
-
options: FilterOptions = {},
|
|
40
|
-
): string[] {
|
|
32
|
+
export function findProfanity(text: string, options: FilterOptions = {}): string[] {
|
|
41
33
|
const {
|
|
42
34
|
wordList = [],
|
|
43
35
|
detectLeetSpeak = true,
|
|
@@ -60,9 +52,7 @@ export function findProfanity(
|
|
|
60
52
|
|
|
61
53
|
if (baseWordsToCheck.length === 0) {
|
|
62
54
|
const filteredWords = wordObjects.filter((word) => {
|
|
63
|
-
const matchCategory = categories
|
|
64
|
-
? categories.includes(word.category)
|
|
65
|
-
: true;
|
|
55
|
+
const matchCategory = categories ? categories.includes(word.category) : true;
|
|
66
56
|
const matchRegion = regions ? regions.includes(word.region) : true;
|
|
67
57
|
const matchSeverity = word.severity >= severityThreshold;
|
|
68
58
|
return matchCategory && matchRegion && matchSeverity;
|
|
@@ -74,9 +64,7 @@ export function findProfanity(
|
|
|
74
64
|
const aliasMap = new Map<string, string>();
|
|
75
65
|
wordObjects.forEach((wordObj) => {
|
|
76
66
|
if (wordObj.aliases && wordObj.aliases.length > 0) {
|
|
77
|
-
const matchCategory = categories
|
|
78
|
-
? categories.includes(wordObj.category)
|
|
79
|
-
: true;
|
|
67
|
+
const matchCategory = categories ? categories.includes(wordObj.category) : true;
|
|
80
68
|
const matchRegion = regions ? regions.includes(wordObj.region) : true;
|
|
81
69
|
const matchSeverity = wordObj.severity >= severityThreshold;
|
|
82
70
|
|
|
@@ -88,10 +76,9 @@ export function findProfanity(
|
|
|
88
76
|
}
|
|
89
77
|
});
|
|
90
78
|
|
|
91
|
-
const wordsToCheck = [
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
].filter((word) => !whitelist.includes(word.toLowerCase()));
|
|
79
|
+
const wordsToCheck = [...baseWordsToCheck, ...Array.from(aliasMap.keys())].filter(
|
|
80
|
+
(word) => !whitelist.includes(word.toLowerCase())
|
|
81
|
+
);
|
|
95
82
|
|
|
96
83
|
if (wordsToCheck.length === 0) {
|
|
97
84
|
return [];
|
|
@@ -104,8 +91,7 @@ export function findProfanity(
|
|
|
104
91
|
|
|
105
92
|
const basicMatches = globalAhoCorasick.searchUnique(normalizedText);
|
|
106
93
|
for (const match of basicMatches) {
|
|
107
|
-
const originalWord =
|
|
108
|
-
aliasMap.get(match.toLowerCase()) || match.toLowerCase();
|
|
94
|
+
const originalWord = aliasMap.get(match.toLowerCase()) || match.toLowerCase();
|
|
109
95
|
matches.add(originalWord);
|
|
110
96
|
|
|
111
97
|
if (!actualMatches.has(originalWord)) {
|
|
@@ -126,8 +112,7 @@ export function findProfanity(
|
|
|
126
112
|
|
|
127
113
|
let match;
|
|
128
114
|
while ((match = leetRegex.exec(text)) !== null) {
|
|
129
|
-
const originalWord =
|
|
130
|
-
aliasMap.get(word.toLowerCase()) || word.toLowerCase();
|
|
115
|
+
const originalWord = aliasMap.get(word.toLowerCase()) || word.toLowerCase();
|
|
131
116
|
matches.add(originalWord);
|
|
132
117
|
|
|
133
118
|
if (!actualMatches.has(originalWord)) {
|
|
@@ -150,8 +135,7 @@ export function findProfanity(
|
|
|
150
135
|
|
|
151
136
|
let match;
|
|
152
137
|
while ((match = variantRegex.exec(text)) !== null) {
|
|
153
|
-
const originalWord =
|
|
154
|
-
aliasMap.get(word.toLowerCase()) || word.toLowerCase();
|
|
138
|
+
const originalWord = aliasMap.get(word.toLowerCase()) || word.toLowerCase();
|
|
155
139
|
matches.add(originalWord);
|
|
156
140
|
|
|
157
141
|
if (!actualMatches.has(originalWord)) {
|
|
@@ -174,8 +158,7 @@ export function findProfanity(
|
|
|
174
158
|
|
|
175
159
|
let match;
|
|
176
160
|
while ((match = splitRegex.exec(text)) !== null) {
|
|
177
|
-
const originalWord =
|
|
178
|
-
aliasMap.get(word.toLowerCase()) || word.toLowerCase();
|
|
161
|
+
const originalWord = aliasMap.get(word.toLowerCase()) || word.toLowerCase();
|
|
179
162
|
matches.add(originalWord);
|
|
180
163
|
|
|
181
164
|
if (!actualMatches.has(originalWord)) {
|
|
@@ -192,13 +175,12 @@ export function findProfanity(
|
|
|
192
175
|
text,
|
|
193
176
|
wordsToCheck,
|
|
194
177
|
similarityThreshold,
|
|
195
|
-
maxLevenshteinDistance
|
|
178
|
+
maxLevenshteinDistance
|
|
196
179
|
);
|
|
197
180
|
|
|
198
181
|
possibleProfanity.forEach((item) => {
|
|
199
182
|
const originalWord =
|
|
200
|
-
aliasMap.get(item.original.toLowerCase()) ||
|
|
201
|
-
item.original.toLowerCase();
|
|
183
|
+
aliasMap.get(item.original.toLowerCase()) || item.original.toLowerCase();
|
|
202
184
|
matches.add(originalWord);
|
|
203
185
|
|
|
204
186
|
if (!actualMatches.has(originalWord)) {
|
|
@@ -210,15 +192,14 @@ export function findProfanity(
|
|
|
210
192
|
const possibleProfanity = findPossibleProfanityBySimiliarity(
|
|
211
193
|
text,
|
|
212
194
|
wordsToCheck,
|
|
213
|
-
similarityThreshold
|
|
195
|
+
similarityThreshold
|
|
214
196
|
);
|
|
215
197
|
|
|
216
198
|
possibleProfanity.forEach((item) => {
|
|
217
199
|
matches.add(item.original.toLowerCase());
|
|
218
200
|
|
|
219
201
|
const originalWord =
|
|
220
|
-
aliasMap.get(item.original.toLowerCase()) ||
|
|
221
|
-
item.original.toLowerCase();
|
|
202
|
+
aliasMap.get(item.original.toLowerCase()) || item.original.toLowerCase();
|
|
222
203
|
if (!actualMatches.has(originalWord)) {
|
|
223
204
|
actualMatches.set(originalWord, []);
|
|
224
205
|
}
|
|
@@ -241,7 +222,7 @@ export function findProfanity(
|
|
|
241
222
|
*/
|
|
242
223
|
export function findProfanityWithMetadata(
|
|
243
224
|
text: string,
|
|
244
|
-
options: FilterOptions = {}
|
|
225
|
+
options: FilterOptions = {}
|
|
245
226
|
): ProfanityWord[] {
|
|
246
227
|
const matches = findProfanity(text, options);
|
|
247
228
|
if (matches.length === 0) {
|
|
@@ -253,10 +234,7 @@ export function findProfanityWithMetadata(
|
|
|
253
234
|
const wordObject = wordObjects.find(
|
|
254
235
|
(obj) =>
|
|
255
236
|
obj.word.toLowerCase() === word.toLowerCase() ||
|
|
256
|
-
(obj.aliases &&
|
|
257
|
-
obj.aliases.some(
|
|
258
|
-
(alias) => alias.toLowerCase() === word.toLowerCase(),
|
|
259
|
-
)),
|
|
237
|
+
(obj.aliases && obj.aliases.some((alias) => alias.toLowerCase() === word.toLowerCase()))
|
|
260
238
|
);
|
|
261
239
|
|
|
262
240
|
return wordObject;
|
|
@@ -270,9 +248,7 @@ export function findProfanityWithMetadata(
|
|
|
270
248
|
* @param matchDetails Hasil pencarian dari fingProfanityWithMetadata()
|
|
271
249
|
* @return Array kategori unik
|
|
272
250
|
*/
|
|
273
|
-
export function findCategories(
|
|
274
|
-
matchDetails: ProfanityWord[],
|
|
275
|
-
): ProfanityCategory[] {
|
|
251
|
+
export function findCategories(matchDetails: ProfanityWord[]): ProfanityCategory[] {
|
|
276
252
|
const categories = new Set<ProfanityCategory>();
|
|
277
253
|
|
|
278
254
|
matchDetails.forEach((word) => {
|
package/src/index.ts
CHANGED
|
@@ -8,12 +8,7 @@ export * from './utils/similarityUtils';
|
|
|
8
8
|
export * from './config/options';
|
|
9
9
|
|
|
10
10
|
import { filter, isProfane } from './core/filter';
|
|
11
|
-
import {
|
|
12
|
-
analyze,
|
|
13
|
-
batchAnalyze,
|
|
14
|
-
analyzeBySentence,
|
|
15
|
-
analyzeWithContext,
|
|
16
|
-
} from './core/analyzer';
|
|
11
|
+
import { analyze, batchAnalyze, analyzeBySentence, analyzeWithContext } from './core/analyzer';
|
|
17
12
|
import { FilterOptions, FilterResult, AnalysisResult } from './types';
|
|
18
13
|
import {
|
|
19
14
|
DEFAULT_OPTIONS,
|
|
@@ -105,10 +100,7 @@ export class IDProfanityFilter {
|
|
|
105
100
|
* @param presetName Nama preset yang akan digunakan
|
|
106
101
|
* @param additionalOptions Opsi tambahan untuk override
|
|
107
102
|
*/
|
|
108
|
-
usePreset(
|
|
109
|
-
presetName: string,
|
|
110
|
-
additionalOptions: Partial<FilterOptions> = {},
|
|
111
|
-
) {
|
|
103
|
+
usePreset(presetName: string, additionalOptions: Partial<FilterOptions> = {}) {
|
|
112
104
|
this.options = getPresetOptions(presetName, additionalOptions);
|
|
113
105
|
}
|
|
114
106
|
|
|
@@ -125,10 +117,7 @@ export class IDProfanityFilter {
|
|
|
125
117
|
* @param word Kata yang akan diabaikan
|
|
126
118
|
*/
|
|
127
119
|
addToWhitelist(word: string) {
|
|
128
|
-
this.options.whitelist = [
|
|
129
|
-
...(this.options.whitelist || []),
|
|
130
|
-
word.toLowerCase(),
|
|
131
|
-
];
|
|
120
|
+
this.options.whitelist = [...(this.options.whitelist || []), word.toLowerCase()];
|
|
132
121
|
}
|
|
133
122
|
|
|
134
123
|
/**
|
|
@@ -139,7 +128,7 @@ export class IDProfanityFilter {
|
|
|
139
128
|
if (!this.options.whitelist) return;
|
|
140
129
|
|
|
141
130
|
this.options.whitelist = this.options.whitelist.filter(
|
|
142
|
-
(w) => w.toLowerCase() !== word.toLowerCase()
|
|
131
|
+
(w) => w.toLowerCase() !== word.toLowerCase()
|
|
143
132
|
);
|
|
144
133
|
}
|
|
145
134
|
|
|
@@ -166,7 +155,7 @@ export class IDProfanityFilter {
|
|
|
166
155
|
enableSimilarityDetection(
|
|
167
156
|
threshold: number = 0.8,
|
|
168
157
|
useLevenshtein: boolean = false,
|
|
169
|
-
maxLevenshteinDistance: number = 2
|
|
158
|
+
maxLevenshteinDistance: number = 2
|
|
170
159
|
) {
|
|
171
160
|
this.options.detectSimilarity = true;
|
|
172
161
|
this.options.similarityThreshold = threshold;
|
package/src/types/index.ts
CHANGED
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
export type ProfanityCategory =
|
|
2
|
-
|
|
|
3
|
-
|
|
|
4
|
-
|
|
|
5
|
-
|
|
|
6
|
-
|
|
|
7
|
-
|
|
|
8
|
-
|
|
|
2
|
+
| 'sexual'
|
|
3
|
+
| 'insult'
|
|
4
|
+
| 'profanity'
|
|
5
|
+
| 'slur'
|
|
6
|
+
| 'drugs'
|
|
7
|
+
| 'disgusting'
|
|
8
|
+
| 'blasphemy';
|
|
9
9
|
|
|
10
10
|
export type Region =
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
11
|
+
| 'general'
|
|
12
|
+
| 'jawa'
|
|
13
|
+
| 'sunda'
|
|
14
|
+
| 'betawi'
|
|
15
|
+
| 'batak'
|
|
16
|
+
| 'minang'
|
|
17
|
+
| 'bali'
|
|
18
|
+
| 'madura'
|
|
19
|
+
| 'bugis'
|
|
20
|
+
| 'aceh'
|
|
21
|
+
| 'ambon'
|
|
22
|
+
| 'papua'
|
|
23
|
+
| 'manado'
|
|
24
|
+
| 'banjar'
|
|
25
|
+
| 'palembang'
|
|
26
|
+
| 'lampung'
|
|
27
|
+
| 'ntt'
|
|
28
|
+
| 'mandailing'
|
|
29
|
+
| 'ntb';
|
|
29
30
|
|
|
30
31
|
export interface ProfanityWord {
|
|
31
32
|
word: string;
|
package/src/utils/ahoCorasick.ts
CHANGED
|
@@ -30,7 +30,7 @@ export class AhoCorasick {
|
|
|
30
30
|
*/
|
|
31
31
|
addPattern(pattern: string): void {
|
|
32
32
|
if (this.built) {
|
|
33
|
-
throw new Error(
|
|
33
|
+
throw new Error('Cannot add patterns after the automaton is built');
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
let node = this.root;
|