@sideid/id-profanity-filter 1.11.7 → 1.11.9
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 +1166 -438
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1166 -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 +169 -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
package/src/utils/regexUtils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { escapeRegExp } from
|
|
2
|
-
import { RegexOptions } from
|
|
1
|
+
import { escapeRegExp } from './stringUtils';
|
|
2
|
+
import { RegexOptions } from '../types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Membuat pola regex untuk mencocokkan kata
|
|
@@ -8,10 +8,7 @@ import { RegexOptions } from "../types";
|
|
|
8
8
|
* @param options Opsi untuk pembuatan regex
|
|
9
9
|
* @returns Objek RegExp
|
|
10
10
|
*/
|
|
11
|
-
export function createWordRegex(
|
|
12
|
-
word: string,
|
|
13
|
-
options: RegexOptions = {},
|
|
14
|
-
): RegExp {
|
|
11
|
+
export function createWordRegex(word: string, options: RegexOptions = {}): RegExp {
|
|
15
12
|
const {
|
|
16
13
|
wholeWord = true,
|
|
17
14
|
caseSensitive = false,
|
|
@@ -44,7 +41,7 @@ export function createWordRegex(
|
|
|
44
41
|
}
|
|
45
42
|
|
|
46
43
|
// Buat regex dengan flag case-insensitive jika diminta
|
|
47
|
-
return new RegExp(pattern, caseSensitive ?
|
|
44
|
+
return new RegExp(pattern, caseSensitive ? 'g' : 'gi');
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
/**
|
|
@@ -59,32 +56,32 @@ export function createWordRegex(
|
|
|
59
56
|
*/
|
|
60
57
|
export function addLeetSpeakVariations(pattern: string): string {
|
|
61
58
|
const leetMap: Record<string, string[]> = {
|
|
62
|
-
a: [
|
|
63
|
-
b: [
|
|
64
|
-
c: [
|
|
65
|
-
e: [
|
|
66
|
-
g: [
|
|
67
|
-
i: [
|
|
68
|
-
l: [
|
|
69
|
-
o: [
|
|
70
|
-
s: [
|
|
71
|
-
t: [
|
|
72
|
-
z: [
|
|
59
|
+
a: ['a', '4', '@'],
|
|
60
|
+
b: ['b', '8', '6'],
|
|
61
|
+
c: ['c', '(', '{', '<'],
|
|
62
|
+
e: ['e', '3'],
|
|
63
|
+
g: ['g', '6', '9'],
|
|
64
|
+
i: ['i', '1', '!', '|'],
|
|
65
|
+
l: ['l', '1', '|'],
|
|
66
|
+
o: ['o', '0'],
|
|
67
|
+
s: ['s', '5', '$'],
|
|
68
|
+
t: ['t', '7', '+'],
|
|
69
|
+
z: ['z', '2'],
|
|
73
70
|
};
|
|
74
71
|
|
|
75
72
|
return pattern
|
|
76
|
-
.split(
|
|
73
|
+
.split('')
|
|
77
74
|
.map((char) => {
|
|
78
75
|
const lowerChar = char.toLowerCase();
|
|
79
76
|
const variations = leetMap[lowerChar];
|
|
80
77
|
|
|
81
78
|
if (variations && variations.length > 1) {
|
|
82
|
-
return `[${variations.join(
|
|
79
|
+
return `[${variations.join('')}]`;
|
|
83
80
|
}
|
|
84
81
|
|
|
85
82
|
return char;
|
|
86
83
|
})
|
|
87
|
-
.join(
|
|
84
|
+
.join('');
|
|
88
85
|
}
|
|
89
86
|
|
|
90
87
|
/**
|
|
@@ -95,7 +92,7 @@ export function addLeetSpeakVariations(pattern: string): string {
|
|
|
95
92
|
*/
|
|
96
93
|
export function addSplitVariations(pattern: string): string {
|
|
97
94
|
// Tambahkan kemungkinan spasi atau karakter penghubung di antara setiap huruf
|
|
98
|
-
return pattern.split(
|
|
95
|
+
return pattern.split('').join('[\\s\\-._*+]?');
|
|
99
96
|
}
|
|
100
97
|
|
|
101
98
|
/**
|
|
@@ -109,7 +106,7 @@ export function createEvasionRegex(word: string): RegExp {
|
|
|
109
106
|
// Tambahkan kemungkinan spasi atau karakter penghubung di antara setiap huruf
|
|
110
107
|
const pattern = addSplitVariations(escapeRegExp(word));
|
|
111
108
|
|
|
112
|
-
return new RegExp(pattern,
|
|
109
|
+
return new RegExp(pattern, 'gi');
|
|
113
110
|
}
|
|
114
111
|
|
|
115
112
|
/**
|
|
@@ -121,30 +118,30 @@ export function createEvasionRegex(word: string): RegExp {
|
|
|
121
118
|
export function addIndonesianVariations(pattern: string): string {
|
|
122
119
|
// Variasi ejaan dalam Bahasa Indonesia
|
|
123
120
|
const variationMap: Record<string, string[]> = {
|
|
124
|
-
c: [
|
|
125
|
-
k: [
|
|
126
|
-
j: [
|
|
127
|
-
y: [
|
|
128
|
-
u: [
|
|
129
|
-
f: [
|
|
130
|
-
z: [
|
|
131
|
-
x: [
|
|
121
|
+
c: ['c', 'k'], // contoh: becok/bekok
|
|
122
|
+
k: ['k', 'c', 'q'], // contoh: kacau/qacau
|
|
123
|
+
j: ['j', 'dj'], // contoh: jualan/djualan (ejaan lama)
|
|
124
|
+
y: ['y', 'j'], // contoh: ya/ja
|
|
125
|
+
u: ['u', 'oe'], // contoh: untuk/oentoek (ejaan lama)
|
|
126
|
+
f: ['f', 'p', 'v'], // contoh: kafir/kapir
|
|
127
|
+
z: ['z', 'j', 's'], // contoh: zaman/jaman
|
|
128
|
+
x: ['x', 'ks'], // contoh: taxi/taksi
|
|
132
129
|
};
|
|
133
130
|
|
|
134
131
|
// Ganti tiap karakter dengan variasinya
|
|
135
132
|
return pattern
|
|
136
|
-
.split(
|
|
133
|
+
.split('')
|
|
137
134
|
.map((char) => {
|
|
138
135
|
const lowerChar = char.toLowerCase();
|
|
139
136
|
const variations = variationMap[lowerChar];
|
|
140
137
|
|
|
141
138
|
if (variations && variations.length > 1) {
|
|
142
|
-
return `[${variations.join(
|
|
139
|
+
return `[${variations.join('')}]`;
|
|
143
140
|
}
|
|
144
141
|
|
|
145
142
|
return char;
|
|
146
143
|
})
|
|
147
|
-
.join(
|
|
144
|
+
.join('');
|
|
148
145
|
}
|
|
149
146
|
|
|
150
147
|
/**
|
|
@@ -155,7 +152,7 @@ export function addIndonesianVariations(pattern: string): string {
|
|
|
155
152
|
*/
|
|
156
153
|
export function createIndonesianVariationRegex(word: string): RegExp {
|
|
157
154
|
const pattern = addIndonesianVariations(escapeRegExp(word));
|
|
158
|
-
return new RegExp(`\\b${pattern}\\b`,
|
|
155
|
+
return new RegExp(`\\b${pattern}\\b`, 'gi');
|
|
159
156
|
}
|
|
160
157
|
|
|
161
158
|
/**
|
|
@@ -165,16 +162,13 @@ export function createIndonesianVariationRegex(word: string): RegExp {
|
|
|
165
162
|
* @param contextSize Jumlah kata konteks sebelum dan sesudah
|
|
166
163
|
* @returns Objek RegExp
|
|
167
164
|
*/
|
|
168
|
-
export function createContextRegex(
|
|
169
|
-
word: string,
|
|
170
|
-
contextSize: number = 3,
|
|
171
|
-
): RegExp {
|
|
165
|
+
export function createContextRegex(word: string, contextSize: number = 3): RegExp {
|
|
172
166
|
const wordPattern = escapeRegExp(word);
|
|
173
167
|
|
|
174
168
|
// Membuat pola yang menangkap beberapa kata sebelum dan setelah kata target
|
|
175
169
|
const pattern = `((?:\\S+\\s+){0,${contextSize}})(\\b${wordPattern}\\b)((?:\\s+\\S+){0,${contextSize}})`;
|
|
176
170
|
|
|
177
|
-
return new RegExp(pattern,
|
|
171
|
+
return new RegExp(pattern, 'gi');
|
|
178
172
|
}
|
|
179
173
|
|
|
180
174
|
/**
|
|
@@ -186,8 +180,8 @@ export function createContextRegex(
|
|
|
186
180
|
export function createWordFormRegex(word: string): RegExp {
|
|
187
181
|
// Implementasi sederhana untuk mencocokkan berbagai imbuhan
|
|
188
182
|
// Ini bisa dikembangkan lebih lanjut untuk mencocokkan bentukan kata yang lebih kompleks
|
|
189
|
-
const prefixes = [
|
|
190
|
-
const suffixes = [
|
|
183
|
+
const prefixes = ['', 'me', 'pe', 'ber', 'di', 'ter', 'se'];
|
|
184
|
+
const suffixes = ['', 'kan', 'an', 'i', 'nya'];
|
|
191
185
|
|
|
192
186
|
const patterns = [];
|
|
193
187
|
|
|
@@ -198,5 +192,5 @@ export function createWordFormRegex(word: string): RegExp {
|
|
|
198
192
|
}
|
|
199
193
|
}
|
|
200
194
|
|
|
201
|
-
return new RegExp(patterns.join(
|
|
195
|
+
return new RegExp(patterns.join('|'), 'gi');
|
|
202
196
|
}
|
|
@@ -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
|