@sideid/id-profanity-filter 1.0.0 → 1.9.4

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.
@@ -1,204 +1,225 @@
1
- import {
2
- FilterOptions,
3
- AnalysisResult,
4
- ProfanityWord,
5
- ProfanityCategory,
6
- Region,
7
- } from '../types';
8
- import {
9
- findProfanity,
10
- findProfanityWithMetadata,
11
- findCategories,
12
- findRegions,
13
- calculateSeverity,
14
- } from './matcher';
15
-
16
- /**
17
- * Menganalisis teks untuk kata kotor
18
- *
19
- * @param text Teks yang akan dianalisis
20
- * @param options Opsi untuk analisis
21
- * @return AnalysisResult dengan hasil analisis
22
- */
23
- export function analyze(
24
- text: string,
25
- options: FilterOptions = {},
26
- ): AnalysisResult {
27
- const matches = findProfanity(text, options);
28
-
29
- if (matches.length === 0) {
30
- return {
31
- hasProfanity: false,
32
- matches: [],
33
- matchDetails: [],
34
- categories: [],
35
- regions: [],
36
- severityScore: 0,
37
- };
38
- }
39
-
40
- const matchDetails = findProfanityWithMetadata(text, options);
41
- const categories = findCategories(matchDetails);
42
- const regions = findRegions(matchDetails);
43
- const severityScore = calculateSeverity(matchDetails);
44
-
45
- return {
46
- hasProfanity: true,
47
- matches,
48
- matchDetails,
49
- categories,
50
- regions,
51
- severityScore,
52
- };
53
- }
54
-
55
- /**
56
- * menganalisis daftar teks dan ringkasan
57
- *
58
- * @param texts Daftar teks yagn dianalisis
59
- * @param options Opsi untuk analisis
60
- * @return Objek dengan ringkasan analisis
61
- */
62
- export function batchAnalyze(
63
- texts: string[],
64
- options: FilterOptions = {},
65
- ): {
66
- totalTexts: number;
67
- profaneTexts: number;
68
- cleanTexts: number;
69
- averageSeverity: number;
70
- topCategories: ProfanityCategory[];
71
- topRegions: Region[];
72
- mostFrequentWords: Array<{ word: string; count: number }>;
73
- } {
74
- const results = texts.map((text) => analyze(text, options));
75
- const profaneTexts = results.filter((result) => result.hasProfanity).length;
76
- const totalSeverity = results.reduce(
77
- (sum, result) => sum + result.severityScore,
78
- 0,
79
- );
80
- const averageSeverity = profaneTexts > 0 ? totalSeverity / profaneTexts : 0;
81
- const allCategories = results.flatMap((result) => result.categories);
82
- const categoryCount: Record<string, number> = {};
83
-
84
- allCategories.forEach((category) => {
85
- categoryCount[category] = (categoryCount[category] || 0) + 1;
86
- });
87
-
88
- const topCategories = Object.entries(categoryCount)
89
- .sort((a, b) => b[1] - a[1])
90
- .map(([category]) => category as ProfanityCategory);
91
-
92
- const allRegions = results.flatMap((result) => result.regions);
93
- const regionCount: Record<string, number> = {};
94
-
95
- allRegions.forEach((region) => {
96
- regionCount[region] = (regionCount[region] || 0) + 1;
97
- });
98
-
99
- const topRegions = Object.entries(regionCount)
100
- .sort((a, b) => b[1] - a[1])
101
- .map(([region]) => region as Region);
102
-
103
- const allWords = results.flatMap((result) => result.matches);
104
- const wordCount: Record<string, number> = {};
105
-
106
- allWords.forEach((word) => {
107
- wordCount[word] = (wordCount[word] || 0) + 1;
108
- });
109
-
110
- const mostFrequentWords = Object.entries(wordCount)
111
- .sort((a, b) => b[1] - a[1])
112
- .slice(0, 10)
113
- .map(([word, count]) => ({ word, count }));
114
-
115
- return {
116
- totalTexts: texts.length,
117
- profaneTexts,
118
- cleanTexts: texts.length - profaneTexts,
119
- averageSeverity,
120
- topCategories,
121
- topRegions,
122
- mostFrequentWords,
123
- };
124
- }
125
-
126
- /**
127
- * Menganalisis per-kalimat untuk melokalisasi kata kotor
128
- *
129
- * @param text Teks yang akan dianalisis per-kalimat
130
- * @param options Opsi untuk analisis
131
- * @returns Array hasil analisis per-kalimat
132
- */
133
- export function analyzeBySentence(
134
- text: string,
135
- options: FilterOptions = {},
136
- ): Array<AnalysisResult & { sentence: string }> {
137
- const sentences = text
138
- .split(/(?<=[.!?])\s+/)
139
- .filter((sentence) => sentence.trim().length > 0);
140
-
141
- return sentences.map((sentence) => {
142
- const result = analyze(sentence, options);
143
-
144
- return {
145
- ...result,
146
- sentence,
147
- };
148
- });
149
- }
150
-
151
- /**
152
- * Menganalisis teks untuk menemukan kata kotor pada konteks tertentu
153
- *
154
- * @param text Teks yang akan dianalisis
155
- * @param context Kata konteks untuk dicari di dekat kata kotor
156
- * @param options Opsi untuk analisis
157
- * @returns Konteks di dekat kata kotor
158
- */
159
- export function analyzeWithContext(
160
- text: string,
161
- contextWindowSize: number = 5,
162
- options: FilterOptions = {},
163
- ): Array<{
164
- word: string;
165
- context: string;
166
- position: { start: number; end: number };
167
- }> {
168
- const matches = findProfanity(text, options);
169
-
170
- if (matches.length === 0) {
171
- return [];
172
- }
173
-
174
- const result = [];
175
-
176
- for (const word of matches) {
177
- const regex = new RegExp(
178
- `((?:\\S+\\s+){0,${contextWindowSize}})(\\b${escapeRegExp(word)}\\b)((?:\\s+\\S+){0,${contextWindowSize}})`,
179
- 'gi',
180
- );
181
-
182
- let match;
183
- while ((match = regex.exec(text)) !== null) {
184
- const beforeContext = match[1] || '';
185
- const wordMatch = match[2];
186
- const afterContext = match[3] || '';
187
-
188
- result.push({
189
- word: wordMatch,
190
- context: beforeContext + wordMatch + afterContext,
191
- position: {
192
- start: match.index,
193
- end: match.index + match[0].length,
194
- },
195
- });
196
- }
197
- }
198
-
199
- return result;
200
- }
201
-
202
- function escapeRegExp(string: string): string {
203
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
204
- }
1
+ import {
2
+ FilterOptions,
3
+ AnalysisResult,
4
+ ProfanityWord,
5
+ ProfanityCategory,
6
+ Region,
7
+ } from "../types";
8
+ import {
9
+ findProfanity,
10
+ findProfanityWithMetadata,
11
+ findCategories,
12
+ findRegions,
13
+ calculateSeverity,
14
+ } from "./matcher";
15
+ import {
16
+ normalizeText,
17
+ escapeRegExp,
18
+ splitIntoSentences,
19
+ getContextAroundIndex,
20
+ } from "../utils/stringUtils";
21
+ import { findPossibleProfanityBySimiliarity } from "../utils/similarityUtils";
22
+ import { createContextRegex } from "../utils/regexUtils";
23
+ import { DEFAULT_OPTIONS } from "../config/options";
24
+
25
+ /**
26
+ * Menganalisis teks untuk kata kotor
27
+ *
28
+ * @param text Teks yang akan dianalisis
29
+ * @param options Opsi untuk analisis
30
+ * @return AnalysisResult dengan hasil analisis
31
+ */
32
+ export function analyze(
33
+ text: string,
34
+ options: FilterOptions = {},
35
+ ): AnalysisResult {
36
+ const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
37
+
38
+ const matches = findProfanity(text, mergedOptions);
39
+
40
+ if (matches.length === 0) {
41
+ return {
42
+ hasProfanity: false,
43
+ matches: [],
44
+ matchDetails: [],
45
+ categories: [],
46
+ regions: [],
47
+ severityScore: 0,
48
+ };
49
+ }
50
+
51
+ const matchDetails = findProfanityWithMetadata(text, mergedOptions);
52
+ const categories = findCategories(matchDetails);
53
+ const regions = findRegions(matchDetails);
54
+ const severityScore = calculateSeverity(matchDetails);
55
+
56
+ let similarWords: Array<{
57
+ word: string;
58
+ original: string;
59
+ similarity: number;
60
+ }> = [];
61
+
62
+ if (mergedOptions.detectSimilarity) {
63
+ const wordList = matchDetails.map((word) => word.word);
64
+ similarWords = findPossibleProfanityBySimiliarity(
65
+ text,
66
+ wordList,
67
+ mergedOptions.similarityThreshold || 0.8,
68
+ );
69
+ }
70
+
71
+ return {
72
+ hasProfanity: true,
73
+ matches,
74
+ matchDetails,
75
+ categories,
76
+ regions,
77
+ severityScore,
78
+ similarWords: mergedOptions.detectSimilarity ? similarWords : undefined,
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Menganalisis daftar teks dan ringkasan
84
+ *
85
+ * @param texts Daftar teks yang dianalisis
86
+ * @param options Opsi untuk analisis
87
+ * @return Objek dengan ringkasan analisis
88
+ */
89
+ export function batchAnalyze(
90
+ texts: string[],
91
+ options: FilterOptions = {},
92
+ ): {
93
+ totalTexts: number;
94
+ profaneTexts: number;
95
+ cleanTexts: number;
96
+ averageSeverity: number;
97
+ topCategories: ProfanityCategory[];
98
+ topRegions: Region[];
99
+ mostFrequentWords: Array<{ word: string; count: number }>;
100
+ } {
101
+ const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
102
+ const results = texts.map((text) => analyze(text, mergedOptions));
103
+ const profaneTexts = results.filter((result) => result.hasProfanity).length;
104
+ const totalSeverity = results.reduce(
105
+ (sum, result) => sum + result.severityScore,
106
+ 0,
107
+ );
108
+ const averageSeverity = profaneTexts > 0 ? totalSeverity / profaneTexts : 0;
109
+ const allCategories = results.flatMap((result) => result.categories);
110
+ const categoryCount: Record<string, number> = {};
111
+
112
+ allCategories.forEach((category) => {
113
+ categoryCount[category] = (categoryCount[category] || 0) + 1;
114
+ });
115
+
116
+ const topCategories = Object.entries(categoryCount)
117
+ .sort((a, b) => b[1] - a[1])
118
+ .map(([category]) => category as ProfanityCategory);
119
+
120
+ const allRegions = results.flatMap((result) => result.regions);
121
+ const regionCount: Record<string, number> = {};
122
+
123
+ allRegions.forEach((region) => {
124
+ regionCount[region] = (regionCount[region] || 0) + 1;
125
+ });
126
+
127
+ const topRegions = Object.entries(regionCount)
128
+ .sort((a, b) => b[1] - a[1])
129
+ .map(([region]) => region as Region);
130
+
131
+ const allWords = results.flatMap((result) => result.matches);
132
+ const wordCount: Record<string, number> = {};
133
+
134
+ allWords.forEach((word) => {
135
+ wordCount[word] = (wordCount[word] || 0) + 1;
136
+ });
137
+
138
+ const mostFrequentWords = Object.entries(wordCount)
139
+ .sort((a, b) => b[1] - a[1])
140
+ .slice(0, 10)
141
+ .map(([word, count]) => ({ word, count }));
142
+
143
+ return {
144
+ totalTexts: texts.length,
145
+ profaneTexts,
146
+ cleanTexts: texts.length - profaneTexts,
147
+ averageSeverity,
148
+ topCategories,
149
+ topRegions,
150
+ mostFrequentWords,
151
+ };
152
+ }
153
+
154
+ /**
155
+ * Menganalisis per-kalimat untuk melokalisasi kata kotor
156
+ *
157
+ * @param text Teks yang akan dianalisis per-kalimat
158
+ * @param options Opsi untuk analisis
159
+ * @returns Array hasil analisis per-kalimat
160
+ */
161
+ export function analyzeBySentence(
162
+ text: string,
163
+ options: FilterOptions = {},
164
+ ): Array<AnalysisResult & { sentence: string }> {
165
+ const sentences = splitIntoSentences(text);
166
+ const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
167
+
168
+ return sentences.map((sentence) => {
169
+ const result = analyze(sentence, mergedOptions);
170
+
171
+ return {
172
+ ...result,
173
+ sentence,
174
+ };
175
+ });
176
+ }
177
+
178
+ /**
179
+ * Menganalisis teks untuk menemukan kata kotor pada konteks tertentu
180
+ *
181
+ * @param text Teks yang akan dianalisis
182
+ * @param contextWindowSize Ukuran konteks (jumlah kata) di sekitar kata kotor
183
+ * @param options Opsi untuk analisis
184
+ * @returns Konteks di dekat kata kotor
185
+ */
186
+ export function analyzeWithContext(
187
+ text: string,
188
+ contextWindowSize: number = 5,
189
+ options: FilterOptions = {},
190
+ ): Array<{
191
+ word: string;
192
+ context: string;
193
+ position: { start: number; end: number };
194
+ }> {
195
+ const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
196
+ const matches = findProfanity(text, mergedOptions);
197
+
198
+ if (matches.length === 0) {
199
+ return [];
200
+ }
201
+
202
+ const result = [];
203
+
204
+ for (const word of matches) {
205
+ const regex = createContextRegex(word, contextWindowSize);
206
+
207
+ let match;
208
+ while ((match = regex.exec(text)) !== null) {
209
+ const beforeContext = match[1] || "";
210
+ const wordMatch = match[2];
211
+ const afterContext = match[3] || "";
212
+
213
+ result.push({
214
+ word: wordMatch,
215
+ context: beforeContext + wordMatch + afterContext,
216
+ position: {
217
+ start: match.index,
218
+ end: match.index + match[0].length,
219
+ },
220
+ });
221
+ }
222
+ }
223
+
224
+ return result;
225
+ }