@sideid/id-profanity-filter 1.11.6 → 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 +1345 -455
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1345 -455
- 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/madura.d.ts +4 -0
- package/dist/types/constants/regions/minang.d.ts +4 -0
- package/dist/types/constants/regions/sunda.d.ts +1 -1
- 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 -174
- package/src/constants/regions/bali.ts +4 -137
- 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 +57 -0
- package/src/constants/regions/minang.ts +53 -0
- package/src/constants/regions/sunda.ts +343 -19
- 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
|
@@ -36,7 +36,7 @@ export function levenshteinDistance(str1: string, str2: string): number {
|
|
|
36
36
|
matrix[i][j] = Math.min(
|
|
37
37
|
matrix[i - 1][j] + 1, // deletion
|
|
38
38
|
matrix[i][j - 1] + 1, // insertion
|
|
39
|
-
matrix[i - 1][j - 1] + cost
|
|
39
|
+
matrix[i - 1][j - 1] + cost // substitution
|
|
40
40
|
);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -72,7 +72,7 @@ export function stringSimilarity(str1: string, str2: string): number {
|
|
|
72
72
|
export function findMostSimilar(
|
|
73
73
|
target: string,
|
|
74
74
|
candidates: string[],
|
|
75
|
-
threshold: number = 0.7
|
|
75
|
+
threshold: number = 0.7
|
|
76
76
|
): string | null {
|
|
77
77
|
if (!candidates.length) return null;
|
|
78
78
|
|
|
@@ -104,7 +104,7 @@ export function findMostSimilarWithLevenshtein(
|
|
|
104
104
|
target: string,
|
|
105
105
|
candidates: string[],
|
|
106
106
|
threshold: number = 0.7,
|
|
107
|
-
maxDistance: number = 3
|
|
107
|
+
maxDistance: number = 3
|
|
108
108
|
): string | null {
|
|
109
109
|
if (!candidates.length) return null;
|
|
110
110
|
|
|
@@ -145,7 +145,7 @@ export function findMostSimilarWithLevenshtein(
|
|
|
145
145
|
export function isPossibleProfanityVariation(
|
|
146
146
|
input: string,
|
|
147
147
|
profanityWords: string[],
|
|
148
|
-
threshold: number = 0.75
|
|
148
|
+
threshold: number = 0.75
|
|
149
149
|
): [boolean, string | null] {
|
|
150
150
|
if (!input || !profanityWords.length) return [false, null];
|
|
151
151
|
|
|
@@ -167,10 +167,7 @@ export function isPossibleProfanityVariation(
|
|
|
167
167
|
* @param threshold Batas minimum kesamaan (default: 0.8)
|
|
168
168
|
* @returns Array kluster kata yang mirip
|
|
169
169
|
*/
|
|
170
|
-
export function clusterSimilarWords(
|
|
171
|
-
words: string[],
|
|
172
|
-
threshold: number = 0.8,
|
|
173
|
-
): string[][] {
|
|
170
|
+
export function clusterSimilarWords(words: string[], threshold: number = 0.8): string[][] {
|
|
174
171
|
const clusters: string[][] = [];
|
|
175
172
|
const processed: Set<string> = new Set();
|
|
176
173
|
|
|
@@ -217,10 +214,9 @@ export function clusterSimilarWords(
|
|
|
217
214
|
export function findPossibleProfanityBySimiliarity(
|
|
218
215
|
text: string,
|
|
219
216
|
profanityWords: string[],
|
|
220
|
-
threshold: number = 0.8
|
|
217
|
+
threshold: number = 0.8
|
|
221
218
|
): Array<{ word: string; original: string; similarity: number }> {
|
|
222
|
-
const result: Array<{ word: string; original: string; similarity: number }> =
|
|
223
|
-
[];
|
|
219
|
+
const result: Array<{ word: string; original: string; similarity: number }> = [];
|
|
224
220
|
|
|
225
221
|
// Optimasi dengan bikin map kata-kata kotor berdasarkan huruf pertama
|
|
226
222
|
const profanityMap = new Map<string, string[]>();
|
|
@@ -262,7 +258,7 @@ export function findPossibleProfanityBySimiliarity(
|
|
|
262
258
|
|
|
263
259
|
// Filter kandidat berdasarkan perbedaan panjang sebelum ngitung kemiripannya
|
|
264
260
|
const lengthFilteredCandidates = allCandidates.filter(
|
|
265
|
-
(candidate) => Math.abs(candidate.length - word.length) <= 2
|
|
261
|
+
(candidate) => Math.abs(candidate.length - word.length) <= 2
|
|
266
262
|
);
|
|
267
263
|
|
|
268
264
|
// Cari yang paling cocok
|
|
@@ -275,10 +271,7 @@ export function findPossibleProfanityBySimiliarity(
|
|
|
275
271
|
for (const profanity of lengthFilteredCandidates) {
|
|
276
272
|
const similarity = stringSimilarity(word, profanity);
|
|
277
273
|
|
|
278
|
-
if (
|
|
279
|
-
similarity >= threshold &&
|
|
280
|
-
(!bestMatch || similarity > bestMatch.similarity)
|
|
281
|
-
) {
|
|
274
|
+
if (similarity >= threshold && (!bestMatch || similarity > bestMatch.similarity)) {
|
|
282
275
|
bestMatch = {
|
|
283
276
|
word,
|
|
284
277
|
original: profanity,
|
|
@@ -308,7 +301,7 @@ export function findProfanityByLevenshteinDistance(
|
|
|
308
301
|
text: string,
|
|
309
302
|
profanityWords: string[],
|
|
310
303
|
threshold: number = 0.8,
|
|
311
|
-
maxDistance: number = 2
|
|
304
|
+
maxDistance: number = 2
|
|
312
305
|
): Array<{
|
|
313
306
|
word: string;
|
|
314
307
|
original: string;
|
|
@@ -361,13 +354,9 @@ export function findProfanityByLevenshteinDistance(
|
|
|
361
354
|
const distance = levenshteinDistance(word, profanity);
|
|
362
355
|
|
|
363
356
|
if (distance <= maxDistance) {
|
|
364
|
-
const similarity =
|
|
365
|
-
1 - distance / Math.max(word.length, profanity.length);
|
|
357
|
+
const similarity = 1 - distance / Math.max(word.length, profanity.length);
|
|
366
358
|
|
|
367
|
-
if (
|
|
368
|
-
similarity >= threshold &&
|
|
369
|
-
(!bestMatch || similarity > bestMatch.similarity)
|
|
370
|
-
) {
|
|
359
|
+
if (similarity >= threshold && (!bestMatch || similarity > bestMatch.similarity)) {
|
|
371
360
|
bestMatch = {
|
|
372
361
|
word,
|
|
373
362
|
original: profanity,
|
|
@@ -395,11 +384,7 @@ export function findProfanityByLevenshteinDistance(
|
|
|
395
384
|
* Helper function to efficiently check if character counts between two strings
|
|
396
385
|
* are similar enough to warrant a full Levenshtein calculation
|
|
397
386
|
*/
|
|
398
|
-
function isCharacterCountSimilar(
|
|
399
|
-
str1: string,
|
|
400
|
-
str2: string,
|
|
401
|
-
maxDifference: number,
|
|
402
|
-
): boolean {
|
|
387
|
+
function isCharacterCountSimilar(str1: string, str2: string, maxDifference: number): boolean {
|
|
403
388
|
const charCount1: Record<string, number> = {};
|
|
404
389
|
const charCount2: Record<string, number> = {};
|
|
405
390
|
|
package/src/utils/stringUtils.ts
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export function censorWord(
|
|
10
10
|
word: string,
|
|
11
|
-
replaceChar: string =
|
|
12
|
-
keepFirstAndLast: boolean = false
|
|
11
|
+
replaceChar: string = '*',
|
|
12
|
+
keepFirstAndLast: boolean = false
|
|
13
13
|
): string {
|
|
14
14
|
if (word.length <= 2) {
|
|
15
15
|
return replaceChar.repeat(word.length);
|
|
@@ -31,9 +31,9 @@ export function censorWord(
|
|
|
31
31
|
export function normalizeText(text: string): string {
|
|
32
32
|
return text
|
|
33
33
|
.toLowerCase()
|
|
34
|
-
.normalize(
|
|
35
|
-
.replace(/[\u0300-\u036f]/g,
|
|
36
|
-
.replace(/[^\w\s]/g,
|
|
34
|
+
.normalize('NFD') // Normalisasi Unicode
|
|
35
|
+
.replace(/[\u0300-\u036f]/g, '') // Hapus diacritic marks
|
|
36
|
+
.replace(/[^\w\s]/g, '') // Hapus karakter non-alphanumeric
|
|
37
37
|
.trim(); // Hapus whitespace di awal dan akhir
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -48,7 +48,7 @@ export function normalizeText(text: string): string {
|
|
|
48
48
|
export function containsAnyWord(
|
|
49
49
|
text: string,
|
|
50
50
|
wordList: string[],
|
|
51
|
-
checkSubstring: boolean = false
|
|
51
|
+
checkSubstring: boolean = false
|
|
52
52
|
): boolean {
|
|
53
53
|
const normalizedText = normalizeText(text);
|
|
54
54
|
|
|
@@ -56,9 +56,7 @@ export function containsAnyWord(
|
|
|
56
56
|
const normalizedWord = normalizeText(word);
|
|
57
57
|
return checkSubstring
|
|
58
58
|
? normalizedText.includes(normalizedWord)
|
|
59
|
-
: new RegExp(`\\b${escapeRegExp(normalizedWord)}\\b`,
|
|
60
|
-
normalizedText,
|
|
61
|
-
);
|
|
59
|
+
: new RegExp(`\\b${escapeRegExp(normalizedWord)}\\b`, 'i').test(normalizedText);
|
|
62
60
|
});
|
|
63
61
|
}
|
|
64
62
|
|
|
@@ -78,7 +76,7 @@ export function containsEuphemism(text: string, wordList: string[]): boolean {
|
|
|
78
76
|
const lastChar = word[word.length - 1];
|
|
79
77
|
const pattern = new RegExp(
|
|
80
78
|
`\\b${escapeRegExp(firstChar)}[*@#\\-_.!?\\s]{${word.length - 2}}${escapeRegExp(lastChar)}\\b`,
|
|
81
|
-
|
|
79
|
+
'i'
|
|
82
80
|
);
|
|
83
81
|
|
|
84
82
|
return pattern.test(text);
|
|
@@ -94,13 +92,13 @@ export function containsEuphemism(text: string, wordList: string[]): boolean {
|
|
|
94
92
|
* @returns Boolean apakah teks mengandung upaya menghindari filter
|
|
95
93
|
*/
|
|
96
94
|
export function detectSplitWords(text: string, wordList: string[]): boolean {
|
|
97
|
-
const compressedText = text.replace(/[\s\-_.!?*]/g,
|
|
95
|
+
const compressedText = text.replace(/[\s\-_.!?*]/g, '').toLowerCase();
|
|
98
96
|
|
|
99
97
|
return wordList.some((word) => compressedText.includes(normalizeText(word)));
|
|
100
98
|
}
|
|
101
99
|
|
|
102
100
|
export function escapeRegExp(string: string): string {
|
|
103
|
-
return string.replace(/[.*+?^${}()|[\]\\]/g,
|
|
101
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
104
102
|
}
|
|
105
103
|
|
|
106
104
|
/**
|
|
@@ -117,9 +115,9 @@ export function maskText(
|
|
|
117
115
|
text: string,
|
|
118
116
|
visibleStart: number = 1,
|
|
119
117
|
visibleEnd: number = 1,
|
|
120
|
-
maskChar: string =
|
|
118
|
+
maskChar: string = '*'
|
|
121
119
|
): string {
|
|
122
|
-
if (!text) return
|
|
120
|
+
if (!text) return '';
|
|
123
121
|
if (text.length <= visibleStart + visibleEnd) return text;
|
|
124
122
|
|
|
125
123
|
const start = text.substring(0, visibleStart);
|
|
@@ -138,26 +136,26 @@ export function maskText(
|
|
|
138
136
|
*/
|
|
139
137
|
export function toLeetSpeak(text: string): string {
|
|
140
138
|
const leetMap: Record<string, string[]> = {
|
|
141
|
-
a: [
|
|
142
|
-
b: [
|
|
143
|
-
c: [
|
|
144
|
-
e: [
|
|
145
|
-
g: [
|
|
146
|
-
i: [
|
|
147
|
-
l: [
|
|
148
|
-
o: [
|
|
149
|
-
s: [
|
|
150
|
-
t: [
|
|
151
|
-
z: [
|
|
139
|
+
a: ['4', '@'],
|
|
140
|
+
b: ['8', '6'],
|
|
141
|
+
c: ['<', '(', '{'],
|
|
142
|
+
e: ['3'],
|
|
143
|
+
g: ['9'],
|
|
144
|
+
i: ['1', '!'],
|
|
145
|
+
l: ['1', '|'],
|
|
146
|
+
o: ['0'],
|
|
147
|
+
s: ['5', '$'],
|
|
148
|
+
t: ['7', '+'],
|
|
149
|
+
z: ['2'],
|
|
152
150
|
};
|
|
153
151
|
|
|
154
152
|
return text
|
|
155
|
-
.split(
|
|
153
|
+
.split('')
|
|
156
154
|
.map((char) => {
|
|
157
155
|
const lowerChar = char.toLowerCase();
|
|
158
156
|
return leetMap[lowerChar] || char;
|
|
159
157
|
})
|
|
160
|
-
.join(
|
|
158
|
+
.join('');
|
|
161
159
|
}
|
|
162
160
|
|
|
163
161
|
/**
|
|
@@ -168,9 +166,7 @@ export function toLeetSpeak(text: string): string {
|
|
|
168
166
|
*/
|
|
169
167
|
export function splitIntoSentences(text: string): string[] {
|
|
170
168
|
// split berdasarkan titik, seru, taya yagn diikuti spasi atau akhir string
|
|
171
|
-
return text
|
|
172
|
-
.split(/(?<=[.!?])\s+|(?<=[.!?])$/)
|
|
173
|
-
.filter((sentence) => sentence.trim().length > 0);
|
|
169
|
+
return text.split(/(?<=[.!?])\s+|(?<=[.!?])$/).filter((sentence) => sentence.trim().length > 0);
|
|
174
170
|
}
|
|
175
171
|
|
|
176
172
|
/**
|
|
@@ -181,12 +177,8 @@ export function splitIntoSentences(text: string): string[] {
|
|
|
181
177
|
* @param windowSize jumlah kata di sekitar indeks
|
|
182
178
|
* @return Kata-kata di sekitar indeks
|
|
183
179
|
*/
|
|
184
|
-
export function getContextAroundIndex(
|
|
185
|
-
text
|
|
186
|
-
index: number,
|
|
187
|
-
windowSize: number = 5,
|
|
188
|
-
): string {
|
|
189
|
-
if (!text || index < 0 || index >= text.length) return "";
|
|
180
|
+
export function getContextAroundIndex(text: string, index: number, windowSize: number = 5): string {
|
|
181
|
+
if (!text || index < 0 || index >= text.length) return '';
|
|
190
182
|
|
|
191
183
|
const words = text.split(/\s+/);
|
|
192
184
|
|
|
@@ -203,11 +195,11 @@ export function getContextAroundIndex(
|
|
|
203
195
|
currentPosition += wordLength + 1;
|
|
204
196
|
}
|
|
205
197
|
|
|
206
|
-
if (targetWordIndex === -1) return
|
|
198
|
+
if (targetWordIndex === -1) return '';
|
|
207
199
|
|
|
208
200
|
// Ambil kata-kata di sekitar
|
|
209
201
|
const startIndex = Math.max(0, targetWordIndex - windowSize);
|
|
210
202
|
const endIndex = Math.min(words.length, targetWordIndex + windowSize + 1);
|
|
211
203
|
|
|
212
|
-
return words.slice(startIndex, endIndex).join(
|
|
204
|
+
return words.slice(startIndex, endIndex).join(' ');
|
|
213
205
|
}
|
|
File without changes
|