@useverse/profanity-guard 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/LICENSE +23 -0
- package/README.md +1302 -0
- package/dist/index.d.mts +1548 -0
- package/dist/index.d.ts +1548 -0
- package/dist/index.js +12111 -0
- package/dist/index.mjs +12075 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,1302 @@
|
|
|
1
|
+
# Profanity Guard 🚫
|
|
2
|
+
|
|
3
|
+
A flexible, production-ready TypeScript library for content moderation and profanity filtering with React hooks support.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@useverse/profanity-guard)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://reactjs.org/)
|
|
9
|
+
|
|
10
|
+
## ✨ Features
|
|
11
|
+
|
|
12
|
+
- 🎯 **Multiple Severity Levels** - MILD, MODERATE, SEVERE, and WTF classifications
|
|
13
|
+
- 🔧 **Flexible Moderation Modes** - OFF, RELAXED, MODERATE, and STRICT
|
|
14
|
+
- 🕵️ **Obfuscation Detection** - Catches common character substitutions (e.g., `@` for `a`, `3` for `e`)
|
|
15
|
+
- 📚 **Expandable Library** - Easily add, remove, or import custom word lists
|
|
16
|
+
- 🔄 **Smart Alternatives** - Replace profanity with appropriate alternatives
|
|
17
|
+
- 📊 **Detailed Results** - Get comprehensive information about detected words
|
|
18
|
+
- 💪 **TypeScript First** - Full type safety and IntelliSense support
|
|
19
|
+
- ⚡ **Performance Optimized** - Efficient pattern matching and caching
|
|
20
|
+
|
|
21
|
+
## 📦 Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @useverse/profanity-guard
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
yarn add @useverse/profanity-guard
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pnpm add @useverse/profanity-guard
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 🚀 Quick Start
|
|
36
|
+
|
|
37
|
+
### Basic Usage
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { ProfanityGuard, ModerationLevel } from '@useverse/profanity-guard';
|
|
41
|
+
|
|
42
|
+
// Create a moderator instance
|
|
43
|
+
const moderator = new ProfanityGuard(ModerationLevel.MODERATE);
|
|
44
|
+
|
|
45
|
+
// Check if content is clean
|
|
46
|
+
const isClean = moderator.isClean("Hello world!"); // true
|
|
47
|
+
|
|
48
|
+
// Get sanitized content
|
|
49
|
+
const sanitized = moderator.sanitize("This is some shit text");
|
|
50
|
+
// Output: "This is some **** text"
|
|
51
|
+
|
|
52
|
+
// Get detailed moderation results
|
|
53
|
+
const result = moderator.moderate("This shit text has ass in it");
|
|
54
|
+
console.log(result);
|
|
55
|
+
// {
|
|
56
|
+
// isClean: false,
|
|
57
|
+
// foundWords: ['shit', 'ass'],
|
|
58
|
+
// severity: 'moderate',
|
|
59
|
+
// isWTF: false,
|
|
60
|
+
// sanitized: 'This **** text has *** in it',
|
|
61
|
+
// matches: [
|
|
62
|
+
// { word: 'shit', severity: 'moderate', position: 5 },
|
|
63
|
+
// { word: 'ass', severity: 'moderate', position: 19 }
|
|
64
|
+
// ]
|
|
65
|
+
// }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### React Hooks (Quick Start)
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { useProfanityGuard, ModerationLevel } from '@useverse/profanity-guard';
|
|
72
|
+
|
|
73
|
+
function CommentForm() {
|
|
74
|
+
const [comment, setComment] = useState('');
|
|
75
|
+
const { moderate, sanitize } = useProfanityGuard({
|
|
76
|
+
level: ModerationLevel.MODERATE
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const result = moderate(comment);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div>
|
|
83
|
+
<textarea
|
|
84
|
+
value={comment}
|
|
85
|
+
onChange={e => setComment(e.target.value)}
|
|
86
|
+
placeholder="Enter your comment..."
|
|
87
|
+
/>
|
|
88
|
+
{!result.isClean && (
|
|
89
|
+
<p className="error">
|
|
90
|
+
⚠️ Contains inappropriate content: {result.foundWords.join(', ')}
|
|
91
|
+
</p>
|
|
92
|
+
)}
|
|
93
|
+
<p>Preview: {sanitize(comment)}</p>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 📚 Table of Contents
|
|
100
|
+
|
|
101
|
+
- [Installation](#-installation)
|
|
102
|
+
- [Quick Start](#-quick-start)
|
|
103
|
+
- [Core Concepts](#-core-concepts)
|
|
104
|
+
- [Usage Guide](#-usage-guide)
|
|
105
|
+
- [Creating a Moderator](#creating-a-moderator)
|
|
106
|
+
- [Moderation Levels](#moderation-levels)
|
|
107
|
+
- [Basic Operations](#basic-operations)
|
|
108
|
+
- [Custom Word Library](#custom-word-library)
|
|
109
|
+
- [React Hooks](#-react-hooks)
|
|
110
|
+
- [useProfanityGuard](#useprofanityguard)
|
|
111
|
+
- [useModeratedInput](#usemoderatedinput)
|
|
112
|
+
- [useBatchModeration](#usebatchmoderation)
|
|
113
|
+
- [useProfanityValidation](#useprofanityvalidation)
|
|
114
|
+
- [useLiveSanitizer](#uselivesanitizer)
|
|
115
|
+
- [useProfanityStats](#useprofanitystats)
|
|
116
|
+
- [useContentReplacement](#usecontentreplacement)
|
|
117
|
+
- [Advanced Features](#-advanced-features)
|
|
118
|
+
- [Real-World Use Cases](#-real-world-use-cases)
|
|
119
|
+
- [API Reference](#-api-reference)
|
|
120
|
+
- [Performance](#-performance)
|
|
121
|
+
- [Contributing](#-contributing)
|
|
122
|
+
|
|
123
|
+
## 🎓 Core Concepts
|
|
124
|
+
|
|
125
|
+
### Word Severity Levels
|
|
126
|
+
|
|
127
|
+
Profanity Guard classifies words into four severity levels:
|
|
128
|
+
|
|
129
|
+
| Severity | Description | Examples | Use Case |
|
|
130
|
+
|----------|-------------|----------|----------|
|
|
131
|
+
| **MILD** | Minor profanity, generally acceptable in casual contexts | damn, hell, crap | Family-friendly apps with some tolerance |
|
|
132
|
+
| **MODERATE** | Common profanity, inappropriate in most public contexts | shit, ass, bitch | Standard content moderation |
|
|
133
|
+
| **SEVERE** | Strong profanity and slurs, offensive in virtually all contexts | fuck, and various slurs | Strict professional environments |
|
|
134
|
+
| **WTF** | Extreme profanity, hate speech, always blocked | Extreme content | Zero-tolerance policies |
|
|
135
|
+
|
|
136
|
+
### Moderation Modes
|
|
137
|
+
|
|
138
|
+
| Mode | Blocks | Best For |
|
|
139
|
+
|------|--------|----------|
|
|
140
|
+
| **OFF** | Nothing | Testing, admin panels |
|
|
141
|
+
| **RELAXED** | SEVERE + WTF | Adult-oriented platforms |
|
|
142
|
+
| **MODERATE** | MODERATE + SEVERE + WTF | General social media, forums |
|
|
143
|
+
| **STRICT** | ALL levels | Children's apps, professional platforms |
|
|
144
|
+
|
|
145
|
+
## 📖 Usage Guide
|
|
146
|
+
|
|
147
|
+
### Creating a Moderator
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { ProfanityGuard, ModerationLevel } from '@useverse/profanity-guard';
|
|
151
|
+
|
|
152
|
+
// Default settings (MODERATE level, '*' censor)
|
|
153
|
+
const moderator = new ProfanityGuard();
|
|
154
|
+
|
|
155
|
+
// Custom moderation level
|
|
156
|
+
const strictModerator = new ProfanityGuard(ModerationLevel.STRICT);
|
|
157
|
+
|
|
158
|
+
// Custom censor character
|
|
159
|
+
const hashModerator = new ProfanityGuard(ModerationLevel.MODERATE, '#');
|
|
160
|
+
|
|
161
|
+
// Relaxed for adult content
|
|
162
|
+
const relaxedModerator = new ProfanityGuard(ModerationLevel.RELAXED);
|
|
163
|
+
|
|
164
|
+
// Turn off moderation (for admin/testing)
|
|
165
|
+
const offModerator = new ProfanityGuard(ModerationLevel.OFF);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Moderation Levels
|
|
169
|
+
|
|
170
|
+
| Level | Description | Blocks |
|
|
171
|
+
|-------|-------------|--------|
|
|
172
|
+
| `OFF` | No moderation | Nothing |
|
|
173
|
+
| `RELAXED` | Minimal filtering | SEVERE, WTF |
|
|
174
|
+
| `MODERATE` | Standard filtering (default) | MODERATE, SEVERE, WTF |
|
|
175
|
+
| `STRICT` | Maximum filtering | MILD, MODERATE, SEVERE, WTF |
|
|
176
|
+
|
|
177
|
+
### Basic Operations
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// Quick clean check
|
|
181
|
+
if (moderator.isClean(userInput)) {
|
|
182
|
+
console.log("Content is safe!");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Sanitize content
|
|
186
|
+
const clean = moderator.sanitize("This damn thing is shit");
|
|
187
|
+
// Output: "This **** thing is ****"
|
|
188
|
+
|
|
189
|
+
// Get detailed results
|
|
190
|
+
const result = moderator.moderate(userInput);
|
|
191
|
+
if (!result.isClean) {
|
|
192
|
+
console.log(`Found ${result.foundWords.length} profane words`);
|
|
193
|
+
console.log(`Severity: ${result.severity}`);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Using Alternatives
|
|
198
|
+
|
|
199
|
+
Replace profanity with appropriate alternatives instead of censoring:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
const result = moderator.moderateSentence(
|
|
203
|
+
"This damn thing is awesome",
|
|
204
|
+
true // preserveStructure
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
console.log(result.sanitized);
|
|
208
|
+
// Output: "This darn thing is awesome"
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Custom Word Library
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { WordSeverity } from '@useverse/profanity-guard';
|
|
215
|
+
|
|
216
|
+
// Add individual words
|
|
217
|
+
moderator.addWord('badword', WordSeverity.MODERATE, ['goodword']);
|
|
218
|
+
|
|
219
|
+
// Add multiple words
|
|
220
|
+
moderator.addWords([
|
|
221
|
+
{ word: 'word1', severity: WordSeverity.MILD, alternatives: ['alt1'] },
|
|
222
|
+
{ word: 'word2', severity: WordSeverity.SEVERE }
|
|
223
|
+
]);
|
|
224
|
+
|
|
225
|
+
// Remove a word
|
|
226
|
+
moderator.removeWord('damn');
|
|
227
|
+
|
|
228
|
+
// Import from JSON
|
|
229
|
+
import customWords from './my-words.json';
|
|
230
|
+
moderator.importLibrary(customWords);
|
|
231
|
+
|
|
232
|
+
// Export current library
|
|
233
|
+
const myLibrary = moderator.exportLibrary();
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Using Pre-filtered Libraries
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import {
|
|
240
|
+
all_bad_words,
|
|
241
|
+
mild_bad_words,
|
|
242
|
+
moderate_bad_words,
|
|
243
|
+
severe_bad_words
|
|
244
|
+
} from 'nobadword/core/library';
|
|
245
|
+
|
|
246
|
+
// Import only severe words
|
|
247
|
+
const moderator = new NoBadWord();
|
|
248
|
+
moderator.clearLibrary(); // Remove defaults
|
|
249
|
+
moderator.importLibrary(severe_bad_words);
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Library Statistics
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
const stats = moderator.getStats();
|
|
256
|
+
console.log(`Total: ${stats.total}`);
|
|
257
|
+
console.log(`Mild: ${stats.mild}`);
|
|
258
|
+
console.log(`Moderate: ${stats.moderate}`);
|
|
259
|
+
console.log(`Severe: ${stats.severe}`);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Quick Moderate (One-off Use)
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { quickModerate, ModerationLevel } from 'nobadword';
|
|
266
|
+
|
|
267
|
+
const result = quickModerate("Some text", ModerationLevel.STRICT);
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## 🎯 Advanced Features
|
|
271
|
+
|
|
272
|
+
### Obfuscation Detection
|
|
273
|
+
|
|
274
|
+
NoBadWord automatically detects common character substitutions:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
moderator.isClean("sh!t"); // false - detects ! as i
|
|
278
|
+
moderator.isClean("d@mn"); // false - detects @ as a
|
|
279
|
+
moderator.isClean("f**k"); // false - detects asterisks
|
|
280
|
+
moderator.isClean("b-a-d"); // false - detects spacing
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Supported substitutions:
|
|
284
|
+
- `a` → `@`, `4`
|
|
285
|
+
- `e` → `3`
|
|
286
|
+
- `i` → `1`, `!`, `|`
|
|
287
|
+
- `o` → `0`
|
|
288
|
+
- `s` → `$`, `5`
|
|
289
|
+
- And more...
|
|
290
|
+
|
|
291
|
+
### Content Analysis Utilities
|
|
292
|
+
|
|
293
|
+
#### Profanity Scoring
|
|
294
|
+
```typescript
|
|
295
|
+
const score = moderator.getProfanityScore("This damn text");
|
|
296
|
+
// Returns 0-100 based on severity and frequency
|
|
297
|
+
|
|
298
|
+
const isAcceptable = moderator.isWithinThreshold(content, 20);
|
|
299
|
+
// Check if score is within acceptable range
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
#### Detailed Analysis
|
|
303
|
+
```typescript
|
|
304
|
+
const report = moderator.getDetailedReport("This damn shit is bad");
|
|
305
|
+
console.log(report);
|
|
306
|
+
// {
|
|
307
|
+
// isClean: false,
|
|
308
|
+
// totalWords: 5,
|
|
309
|
+
// profaneWords: 2,
|
|
310
|
+
// profanityPercentage: 40,
|
|
311
|
+
// score: 35,
|
|
312
|
+
// highestSeverity: 'moderate',
|
|
313
|
+
// severityCounts: { mild: 1, moderate: 1, severe: 0, wtf: 0 },
|
|
314
|
+
// flaggedWords: ['damn', 'shit'],
|
|
315
|
+
// details: [...]
|
|
316
|
+
// }
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
#### Content Validation
|
|
320
|
+
```typescript
|
|
321
|
+
const validation = moderator.validate("Content to check", {
|
|
322
|
+
maxProfanityScore: 20,
|
|
323
|
+
maxSeverity: WordSeverity.MILD,
|
|
324
|
+
maxProfaneWords: 1
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
if (!validation.isValid) {
|
|
328
|
+
console.log(validation.reasons); // Why validation failed
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Content Manipulation Utilities
|
|
333
|
+
|
|
334
|
+
#### Highlighting
|
|
335
|
+
```typescript
|
|
336
|
+
const highlighted = moderator.highlight("This damn text is shit");
|
|
337
|
+
// Output: "This <mark>damn</mark> text is <mark>shit</mark>"
|
|
338
|
+
|
|
339
|
+
// Custom tags
|
|
340
|
+
const custom = moderator.highlight(
|
|
341
|
+
content,
|
|
342
|
+
'<span class="flagged">',
|
|
343
|
+
'</span>'
|
|
344
|
+
);
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
#### Alternative Replacements
|
|
348
|
+
```typescript
|
|
349
|
+
const replaced = moderator.replaceWithAlternatives("This damn thing");
|
|
350
|
+
// Output: "This darn thing" or "This dang thing" (random from alternatives)
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### Extract Clean Content
|
|
354
|
+
```typescript
|
|
355
|
+
const text = "Hello world. This is damn bad. Nice day.";
|
|
356
|
+
const clean = moderator.getCleanSentences(text);
|
|
357
|
+
// Output: ["Hello world", "Nice day"]
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Batch Processing
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
// Process multiple items
|
|
364
|
+
const results = moderator.moderateBatch([
|
|
365
|
+
"Hello world",
|
|
366
|
+
"This is damn bad",
|
|
367
|
+
"Nice day"
|
|
368
|
+
]);
|
|
369
|
+
|
|
370
|
+
// Filter arrays
|
|
371
|
+
const cleanOnly = moderator.filterClean(allComments);
|
|
372
|
+
const profaneOnly = moderator.filterProfane(allComments);
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Library Inspection
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
// Check if word exists
|
|
379
|
+
if (moderator.hasWord('damn')) {
|
|
380
|
+
const info = moderator.getWordInfo('damn');
|
|
381
|
+
console.log(info.severity); // 'mild'
|
|
382
|
+
console.log(info.alternatives); // ['darn', 'dang']
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Get suggestions
|
|
386
|
+
const suggestions = moderator.getSuggestions('damn');
|
|
387
|
+
// Output: ['darn', 'dang']
|
|
388
|
+
|
|
389
|
+
// Get words by severity
|
|
390
|
+
const mildWords = moderator.getWordsBySeverity(WordSeverity.MILD);
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Quick Utilities (No Instance Required)
|
|
394
|
+
|
|
395
|
+
For one-off operations without creating a moderator instance:
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import {
|
|
399
|
+
quickCheck,
|
|
400
|
+
quickSanitize,
|
|
401
|
+
quickScore,
|
|
402
|
+
quickValidate
|
|
403
|
+
} from 'nobadword';
|
|
404
|
+
|
|
405
|
+
// Quick checks
|
|
406
|
+
if (quickCheck("Hello world")) {
|
|
407
|
+
console.log("Clean!");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Quick sanitization
|
|
411
|
+
const clean = quickSanitize("This damn text");
|
|
412
|
+
|
|
413
|
+
// Quick scoring
|
|
414
|
+
const score = quickScore("This is bad");
|
|
415
|
+
|
|
416
|
+
// Quick validation
|
|
417
|
+
const isValid = quickValidate(content, {
|
|
418
|
+
maxProfanityScore: 20,
|
|
419
|
+
maxSeverity: WordSeverity.MILD
|
|
420
|
+
});
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
> **Note:** Quick utilities are convenient but less efficient for repeated use. For better performance with multiple operations, create a NoBadWord instance and reuse it.
|
|
424
|
+
|
|
425
|
+
### Match Details
|
|
426
|
+
|
|
427
|
+
Get precise information about each detected word:
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
const result = moderator.moderate("This damn text has shit");
|
|
431
|
+
|
|
432
|
+
result.matches.forEach(match => {
|
|
433
|
+
console.log(`Word: "${match.word}"`);
|
|
434
|
+
console.log(`Severity: ${match.severity}`);
|
|
435
|
+
console.log(`Position: ${match.position}`);
|
|
436
|
+
});
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Dynamic Moderation Level
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
// Start with relaxed moderation
|
|
443
|
+
moderator.setModerationLevel(ModerationLevel.RELAXED);
|
|
444
|
+
|
|
445
|
+
// Switch to strict for sensitive content
|
|
446
|
+
moderator.setModerationLevel(ModerationLevel.STRICT);
|
|
447
|
+
|
|
448
|
+
// Check current level
|
|
449
|
+
const level = moderator.getModerationLevel();
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## 🔧 TypeScript Support
|
|
453
|
+
|
|
454
|
+
Full TypeScript support with comprehensive type definitions:
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
import {
|
|
458
|
+
NoBadWord,
|
|
459
|
+
ModerationLevel,
|
|
460
|
+
WordSeverity,
|
|
461
|
+
ModerationResult,
|
|
462
|
+
WordEntry,
|
|
463
|
+
Match
|
|
464
|
+
} from 'nobadword';
|
|
465
|
+
|
|
466
|
+
const moderator: NoBadWord = new NoBadWord();
|
|
467
|
+
const result: ModerationResult = moderator.moderate("text");
|
|
468
|
+
const entry: WordEntry = {
|
|
469
|
+
word: "example",
|
|
470
|
+
severity: WordSeverity.MODERATE,
|
|
471
|
+
alternatives: ["alt"]
|
|
472
|
+
};
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
## 📚 API Reference
|
|
476
|
+
|
|
477
|
+
### NoBadWord Class
|
|
478
|
+
|
|
479
|
+
#### Constructor
|
|
480
|
+
```typescript
|
|
481
|
+
new NoBadWord(moderationLevel?: ModerationLevel, censorChar?: string)
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
#### Core Methods
|
|
485
|
+
|
|
486
|
+
| Method | Description | Returns |
|
|
487
|
+
|--------|-------------|---------|
|
|
488
|
+
| `moderate(content)` | Full moderation with details | `ModerationResult` |
|
|
489
|
+
| `isClean(content)` | Quick clean check | `boolean` |
|
|
490
|
+
| `sanitize(content)` | Get censored content | `string` |
|
|
491
|
+
| `moderateSentence(sentence, preserveStructure?)` | Moderate with alternatives | `ModerationResult` |
|
|
492
|
+
|
|
493
|
+
#### Analysis Methods
|
|
494
|
+
|
|
495
|
+
| Method | Description | Returns |
|
|
496
|
+
|--------|-------------|---------|
|
|
497
|
+
| `getProfanityScore(content)` | Calculate profanity score (0-100) | `number` |
|
|
498
|
+
| `isWithinThreshold(content, threshold)` | Check if score within limit | `boolean` |
|
|
499
|
+
| `countBySeverity(content)` | Count words by severity | `Object` |
|
|
500
|
+
| `getDetailedReport(content)` | Comprehensive analysis report | `Object` |
|
|
501
|
+
| `validate(content, options)` | Validate against criteria | `Object` |
|
|
502
|
+
|
|
503
|
+
#### Manipulation Methods
|
|
504
|
+
|
|
505
|
+
| Method | Description | Returns |
|
|
506
|
+
|--------|-------------|---------|
|
|
507
|
+
| `highlight(content, openTag?, closeTag?)` | Wrap profanity in tags | `string` |
|
|
508
|
+
| `replaceWithAlternatives(content)` | Replace with random alternatives | `string` |
|
|
509
|
+
| `getCleanSentences(content, delimiter?)` | Extract clean sentences | `string[]` |
|
|
510
|
+
|
|
511
|
+
#### Batch Methods
|
|
512
|
+
|
|
513
|
+
| Method | Description | Returns |
|
|
514
|
+
|--------|-------------|---------|
|
|
515
|
+
| `moderateBatch(contents)` | Moderate multiple items | `ModerationResult[]` |
|
|
516
|
+
| `filterClean(contents)` | Filter to clean items only | `string[]` |
|
|
517
|
+
| `filterProfane(contents)` | Filter to profane items only | `string[]` |
|
|
518
|
+
|
|
519
|
+
#### Library Methods
|
|
520
|
+
|
|
521
|
+
| Method | Description | Returns |
|
|
522
|
+
|--------|-------------|---------|
|
|
523
|
+
| `addWord(word, severity, alternatives?)` | Add single word | `void` |
|
|
524
|
+
| `addWords(entries)` | Add multiple words | `void` |
|
|
525
|
+
| `removeWord(word)` | Remove a word | `boolean` |
|
|
526
|
+
| `hasWord(word)` | Check if word exists | `boolean` |
|
|
527
|
+
| `getWordInfo(word)` | Get word details | `WordEntry \| null` |
|
|
528
|
+
| `getSuggestions(word)` | Get alternatives for word | `string[]` |
|
|
529
|
+
| `getWordsBySeverity(severity)` | Get all words by severity | `WordEntry[]` |
|
|
530
|
+
| `importLibrary(jsonData)` | Import word list | `void` |
|
|
531
|
+
| `exportLibrary()` | Export word list | `WordEntry[]` |
|
|
532
|
+
| `clearLibrary()` | Clear all words | `void` |
|
|
533
|
+
| `setModerationLevel(level)` | Change moderation level | `void` |
|
|
534
|
+
| `getModerationLevel()` | Get current level | `ModerationLevel` |
|
|
535
|
+
| `getStats()` | Get library statistics | `LibraryStats` |
|
|
536
|
+
|
|
537
|
+
### Quick Utility Functions
|
|
538
|
+
|
|
539
|
+
| Function | Description | Returns |
|
|
540
|
+
|----------|-------------|---------|
|
|
541
|
+
| `quickModerate(content, level?)` | One-off moderation | `ModerationResult` |
|
|
542
|
+
| `quickCheck(content, level?)` | Quick clean check | `boolean` |
|
|
543
|
+
| `quickSanitize(content, level?, char?)` | Quick sanitization | `string` |
|
|
544
|
+
| `quickScore(content, level?)` | Quick profanity score | `number` |
|
|
545
|
+
| `quickValidate(content, options)` | Quick validation | `boolean` |
|
|
546
|
+
|
|
547
|
+
### Types
|
|
548
|
+
|
|
549
|
+
#### ModerationResult
|
|
550
|
+
```typescript
|
|
551
|
+
{
|
|
552
|
+
isClean: boolean;
|
|
553
|
+
foundWords: string[];
|
|
554
|
+
severity: WordSeverity | null;
|
|
555
|
+
sanitized: string;
|
|
556
|
+
matches: Match[];
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
#### WordEntry
|
|
561
|
+
```typescript
|
|
562
|
+
{
|
|
563
|
+
word: string;
|
|
564
|
+
severity: WordSeverity;
|
|
565
|
+
alternatives?: string[];
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
#### Match
|
|
570
|
+
```typescript
|
|
571
|
+
{
|
|
572
|
+
word: string;
|
|
573
|
+
severity: WordSeverity;
|
|
574
|
+
position: number;
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
## 🎨 Use Cases
|
|
579
|
+
|
|
580
|
+
### Comment Moderation
|
|
581
|
+
```typescript
|
|
582
|
+
function moderateComment(comment: string): string {
|
|
583
|
+
const moderator = new NoBadWord(ModerationLevel.MODERATE);
|
|
584
|
+
const result = moderator.moderate(comment);
|
|
585
|
+
|
|
586
|
+
if (!result.isClean) {
|
|
587
|
+
logModerationEvent(result);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return result.sanitized;
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Chat Filter
|
|
595
|
+
```typescript
|
|
596
|
+
function filterChatMessage(message: string): { allowed: boolean; filtered: string } {
|
|
597
|
+
const moderator = new NoBadWord(ModerationLevel.STRICT);
|
|
598
|
+
const result = moderator.moderateSentence(message, true);
|
|
599
|
+
|
|
600
|
+
return {
|
|
601
|
+
allowed: result.severity !== 'severe',
|
|
602
|
+
filtered: result.sanitized
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### User-Generated Content
|
|
608
|
+
```typescript
|
|
609
|
+
function validateUsername(username: string): { valid: boolean; reason?: string } {
|
|
610
|
+
const moderator = new NoBadWord(ModerationLevel.STRICT);
|
|
611
|
+
|
|
612
|
+
if (!moderator.isClean(username)) {
|
|
613
|
+
return { valid: false, reason: 'Username contains inappropriate language' };
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return { valid: true };
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
## ⚛️ React Hooks
|
|
621
|
+
|
|
622
|
+
Profanity Guard provides a comprehensive set of React hooks for seamless integration into React applications.
|
|
623
|
+
|
|
624
|
+
### useProfanityGuard
|
|
625
|
+
|
|
626
|
+
Main hook for content moderation with full access to all moderation features.
|
|
627
|
+
|
|
628
|
+
```tsx
|
|
629
|
+
import { useProfanityGuard, ModerationLevel } from '@useverse/profanity-guard';
|
|
630
|
+
|
|
631
|
+
function CommentSection() {
|
|
632
|
+
const [comment, setComment] = useState('');
|
|
633
|
+
const { moderate, sanitize, isClean, addWord } = useProfanityGuard({
|
|
634
|
+
level: ModerationLevel.MODERATE,
|
|
635
|
+
censorChar: '*'
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
const result = moderate(comment);
|
|
639
|
+
|
|
640
|
+
const handleSubmit = () => {
|
|
641
|
+
if (result.isClean) {
|
|
642
|
+
// Submit comment
|
|
643
|
+
submitComment(comment);
|
|
644
|
+
} else {
|
|
645
|
+
alert(`Please remove: ${result.foundWords.join(', ')}`);
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
return (
|
|
650
|
+
<div>
|
|
651
|
+
<textarea value={comment} onChange={e => setComment(e.target.value)} />
|
|
652
|
+
<div className={result.isClean ? 'clean' : 'flagged'}>
|
|
653
|
+
{result.isClean ? '✓ Clean' : `⚠️ Found: ${result.foundWords.join(', ')}`}
|
|
654
|
+
</div>
|
|
655
|
+
<button onClick={handleSubmit} disabled={!result.isClean}>
|
|
656
|
+
Post Comment
|
|
657
|
+
</button>
|
|
658
|
+
</div>
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### useModeratedInput
|
|
664
|
+
|
|
665
|
+
Real-time input moderation with built-in state management.
|
|
666
|
+
|
|
667
|
+
```tsx
|
|
668
|
+
import { useModeratedInput, ModerationLevel } from '@useverse/profanity-guard';
|
|
669
|
+
|
|
670
|
+
function ChatInput() {
|
|
671
|
+
const {
|
|
672
|
+
content,
|
|
673
|
+
setContent,
|
|
674
|
+
result,
|
|
675
|
+
sanitizedContent,
|
|
676
|
+
isClean,
|
|
677
|
+
foundWords,
|
|
678
|
+
severity
|
|
679
|
+
} = useModeratedInput('', {
|
|
680
|
+
level: ModerationLevel.STRICT
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
return (
|
|
684
|
+
<div className="chat-input">
|
|
685
|
+
<input
|
|
686
|
+
value={content}
|
|
687
|
+
onChange={e => setContent(e.target.value)}
|
|
688
|
+
placeholder="Type your message..."
|
|
689
|
+
className={isClean ? '' : 'has-profanity'}
|
|
690
|
+
/>
|
|
691
|
+
|
|
692
|
+
{!isClean && (
|
|
693
|
+
<div className="warning">
|
|
694
|
+
<span>⚠️ Contains: {foundWords.join(', ')}</span>
|
|
695
|
+
<span className="severity-badge">{severity}</span>
|
|
696
|
+
</div>
|
|
697
|
+
)}
|
|
698
|
+
|
|
699
|
+
<div className="preview">
|
|
700
|
+
Preview: {sanitizedContent}
|
|
701
|
+
</div>
|
|
702
|
+
|
|
703
|
+
<button disabled={!isClean}>Send</button>
|
|
704
|
+
</div>
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
### useBatchModeration
|
|
710
|
+
|
|
711
|
+
Batch processing for lists of content.
|
|
712
|
+
|
|
713
|
+
```tsx
|
|
714
|
+
import { useBatchModeration } from '@useverse/profanity-guard';
|
|
715
|
+
|
|
716
|
+
function CommentList({ comments }) {
|
|
717
|
+
const { moderateBatch, filterClean, filterProfane } = useBatchModeration({
|
|
718
|
+
level: ModerationLevel.MODERATE
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
const results = moderateBatch(comments);
|
|
722
|
+
const cleanComments = filterClean(comments);
|
|
723
|
+
const flaggedComments = filterProfane(comments);
|
|
724
|
+
|
|
725
|
+
return (
|
|
726
|
+
<div>
|
|
727
|
+
<h3>Clean Comments ({cleanComments.length})</h3>
|
|
728
|
+
{cleanComments.map((comment, i) => (
|
|
729
|
+
<div key={i} className="comment clean">{comment}</div>
|
|
730
|
+
))}
|
|
731
|
+
|
|
732
|
+
<h3>Flagged Comments ({flaggedComments.length})</h3>
|
|
733
|
+
{flaggedComments.map((comment, i) => (
|
|
734
|
+
<div key={i} className="comment flagged">
|
|
735
|
+
{results[comments.indexOf(comment)].sanitized}
|
|
736
|
+
</div>
|
|
737
|
+
))}
|
|
738
|
+
</div>
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
### useProfanityValidation
|
|
744
|
+
|
|
745
|
+
Form validation with profanity checking.
|
|
746
|
+
|
|
747
|
+
```tsx
|
|
748
|
+
import { useProfanityValidation } from '@useverse/profanity-guard';
|
|
749
|
+
|
|
750
|
+
function SignupForm() {
|
|
751
|
+
const [formData, setFormData] = useState({ username: '', bio: '' });
|
|
752
|
+
const { validateField, errors, hasErrors, clearError } = useProfanityValidation({
|
|
753
|
+
level: ModerationLevel.STRICT
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
const handleSubmit = (e) => {
|
|
757
|
+
e.preventDefault();
|
|
758
|
+
|
|
759
|
+
const usernameValid = validateField('username', formData.username);
|
|
760
|
+
const bioValid = validateField('bio', formData.bio);
|
|
761
|
+
|
|
762
|
+
if (usernameValid && bioValid) {
|
|
763
|
+
// Submit form
|
|
764
|
+
console.log('Form is valid!');
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
return (
|
|
769
|
+
<form onSubmit={handleSubmit}>
|
|
770
|
+
<div>
|
|
771
|
+
<label>Username</label>
|
|
772
|
+
<input
|
|
773
|
+
value={formData.username}
|
|
774
|
+
onChange={e => {
|
|
775
|
+
setFormData({...formData, username: e.target.value});
|
|
776
|
+
clearError('username');
|
|
777
|
+
}}
|
|
778
|
+
/>
|
|
779
|
+
{errors.username && <span className="error">{errors.username}</span>}
|
|
780
|
+
</div>
|
|
781
|
+
|
|
782
|
+
<div>
|
|
783
|
+
<label>Bio</label>
|
|
784
|
+
<textarea
|
|
785
|
+
value={formData.bio}
|
|
786
|
+
onChange={e => {
|
|
787
|
+
setFormData({...formData, bio: e.target.value});
|
|
788
|
+
clearError('bio');
|
|
789
|
+
}}
|
|
790
|
+
/>
|
|
791
|
+
{errors.bio && <span className="error">{errors.bio}</span>}
|
|
792
|
+
</div>
|
|
793
|
+
|
|
794
|
+
<button type="submit" disabled={hasErrors}>
|
|
795
|
+
Sign Up
|
|
796
|
+
</button>
|
|
797
|
+
</form>
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
### useLiveSanitizer
|
|
803
|
+
|
|
804
|
+
Debounced live sanitization for performance.
|
|
805
|
+
|
|
806
|
+
```tsx
|
|
807
|
+
import { useLiveSanitizer } from '@useverse/profanity-guard';
|
|
808
|
+
|
|
809
|
+
function LiveEditor() {
|
|
810
|
+
const { content, setContent, sanitized, isProcessing } = useLiveSanitizer(300, {
|
|
811
|
+
level: ModerationLevel.MODERATE
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
return (
|
|
815
|
+
<div className="editor">
|
|
816
|
+
<div className="input-section">
|
|
817
|
+
<label>Your Content</label>
|
|
818
|
+
<textarea
|
|
819
|
+
value={content}
|
|
820
|
+
onChange={e => setContent(e.target.value)}
|
|
821
|
+
placeholder="Type here..."
|
|
822
|
+
/>
|
|
823
|
+
{isProcessing && <span className="processing">Processing...</span>}
|
|
824
|
+
</div>
|
|
825
|
+
|
|
826
|
+
<div className="preview-section">
|
|
827
|
+
<label>Sanitized Preview</label>
|
|
828
|
+
<div className="preview">{sanitized}</div>
|
|
829
|
+
</div>
|
|
830
|
+
</div>
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
### useProfanityStats
|
|
836
|
+
|
|
837
|
+
Analytics and statistics tracking.
|
|
838
|
+
|
|
839
|
+
```tsx
|
|
840
|
+
import { useProfanityStats } from '@useverse/profanity-guard';
|
|
841
|
+
|
|
842
|
+
function ModerationDashboard() {
|
|
843
|
+
const { stats, analyzeContent, history, clearHistory } = useProfanityStats({
|
|
844
|
+
level: ModerationLevel.MODERATE
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
return (
|
|
848
|
+
<div className="dashboard">
|
|
849
|
+
<h2>Moderation Statistics</h2>
|
|
850
|
+
|
|
851
|
+
<div className="stats-grid">
|
|
852
|
+
<div className="stat-card">
|
|
853
|
+
<h3>Total Analyzed</h3>
|
|
854
|
+
<p className="stat-number">{stats.analyzed.total}</p>
|
|
855
|
+
</div>
|
|
856
|
+
|
|
857
|
+
<div className="stat-card">
|
|
858
|
+
<h3>Flagged Content</h3>
|
|
859
|
+
<p className="stat-number">{stats.analyzed.flagged}</p>
|
|
860
|
+
<p className="stat-percent">{stats.analyzed.flaggedPercentage.toFixed(1)}%</p>
|
|
861
|
+
</div>
|
|
862
|
+
|
|
863
|
+
<div className="stat-card">
|
|
864
|
+
<h3>Clean Content</h3>
|
|
865
|
+
<p className="stat-number">{stats.analyzed.clean}</p>
|
|
866
|
+
</div>
|
|
867
|
+
</div>
|
|
868
|
+
|
|
869
|
+
<div className="library-stats">
|
|
870
|
+
<h3>Word Library</h3>
|
|
871
|
+
<ul>
|
|
872
|
+
<li>Total Words: {stats.library.total}</li>
|
|
873
|
+
<li>Mild: {stats.library.mild}</li>
|
|
874
|
+
<li>Moderate: {stats.library.moderate}</li>
|
|
875
|
+
<li>Severe: {stats.library.severe}</li>
|
|
876
|
+
<li>WTF: {stats.library.wtf}</li>
|
|
877
|
+
</ul>
|
|
878
|
+
</div>
|
|
879
|
+
|
|
880
|
+
<button onClick={clearHistory}>Clear History</button>
|
|
881
|
+
</div>
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
### useContentReplacement
|
|
887
|
+
|
|
888
|
+
Smart word replacement with alternatives.
|
|
889
|
+
|
|
890
|
+
```tsx
|
|
891
|
+
import { useContentReplacement } from '@useverse/profanity-guard';
|
|
892
|
+
|
|
893
|
+
function SmartEditor() {
|
|
894
|
+
const [text, setText] = useState('');
|
|
895
|
+
const { replaceWithAlternatives, getSuggestions, getWordInfo } = useContentReplacement({
|
|
896
|
+
level: ModerationLevel.MODERATE
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
const handleAutoFix = () => {
|
|
900
|
+
const cleaned = replaceWithAlternatives(text);
|
|
901
|
+
setText(cleaned);
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
const handleWordClick = (word) => {
|
|
905
|
+
const suggestions = getSuggestions(word);
|
|
906
|
+
const info = getWordInfo(word);
|
|
907
|
+
|
|
908
|
+
if (suggestions.length > 0) {
|
|
909
|
+
// Show suggestions modal
|
|
910
|
+
showSuggestionsModal(word, suggestions, info);
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
return (
|
|
915
|
+
<div>
|
|
916
|
+
<textarea value={text} onChange={e => setText(e.target.value)} />
|
|
917
|
+
<button onClick={handleAutoFix}>Auto-Fix Profanity</button>
|
|
918
|
+
</div>
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
## 🌍 Real-World Use Cases
|
|
924
|
+
|
|
925
|
+
### 1. Social Media Platform
|
|
926
|
+
|
|
927
|
+
```tsx
|
|
928
|
+
// Complete comment moderation system
|
|
929
|
+
import { useProfanityGuard, useModeratedInput } from '@useverse/profanity-guard';
|
|
930
|
+
|
|
931
|
+
function SocialMediaPost() {
|
|
932
|
+
const {
|
|
933
|
+
content,
|
|
934
|
+
setContent,
|
|
935
|
+
result,
|
|
936
|
+
sanitizedContent,
|
|
937
|
+
isClean
|
|
938
|
+
} = useModeratedInput('', {
|
|
939
|
+
level: ModerationLevel.MODERATE
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
const [showWarning, setShowWarning] = useState(false);
|
|
943
|
+
|
|
944
|
+
const handlePost = async () => {
|
|
945
|
+
if (!isClean) {
|
|
946
|
+
setShowWarning(true);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
await api.createPost({ content });
|
|
951
|
+
setContent('');
|
|
952
|
+
};
|
|
953
|
+
|
|
954
|
+
return (
|
|
955
|
+
<div className="post-creator">
|
|
956
|
+
<textarea
|
|
957
|
+
value={content}
|
|
958
|
+
onChange={e => setContent(e.target.value)}
|
|
959
|
+
placeholder="What's on your mind?"
|
|
960
|
+
maxLength={280}
|
|
961
|
+
/>
|
|
962
|
+
|
|
963
|
+
{showWarning && !isClean && (
|
|
964
|
+
<div className="warning-banner">
|
|
965
|
+
<p>Your post contains inappropriate language: {result.foundWords.join(', ')}</p>
|
|
966
|
+
<p>Sanitized version: {sanitizedContent}</p>
|
|
967
|
+
<button onClick={() => setContent(sanitizedContent)}>
|
|
968
|
+
Use Sanitized Version
|
|
969
|
+
</button>
|
|
970
|
+
</div>
|
|
971
|
+
)}
|
|
972
|
+
|
|
973
|
+
<div className="post-footer">
|
|
974
|
+
<span className={isClean ? 'status-ok' : 'status-warning'}>
|
|
975
|
+
{isClean ? '✓ Ready to post' : '⚠️ Contains profanity'}
|
|
976
|
+
</span>
|
|
977
|
+
<button onClick={handlePost} disabled={!isClean}>
|
|
978
|
+
Post
|
|
979
|
+
</button>
|
|
980
|
+
</div>
|
|
981
|
+
</div>
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
### 2. Live Chat Application
|
|
987
|
+
|
|
988
|
+
```tsx
|
|
989
|
+
// Real-time chat with moderation
|
|
990
|
+
import { useModeratedInput, useBatchModeration } from '@useverse/profanity-guard';
|
|
991
|
+
|
|
992
|
+
function ChatRoom({ messages }) {
|
|
993
|
+
const { content, setContent, isClean, sanitizedContent } = useModeratedInput('', {
|
|
994
|
+
level: ModerationLevel.STRICT
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
const { filterClean } = useBatchModeration({
|
|
998
|
+
level: ModerationLevel.STRICT
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
const [chatHistory, setChatHistory] = useState(messages);
|
|
1002
|
+
|
|
1003
|
+
const sendMessage = () => {
|
|
1004
|
+
if (isClean) {
|
|
1005
|
+
const newMessage = { text: content, user: currentUser, timestamp: Date.now() };
|
|
1006
|
+
setChatHistory([...chatHistory, newMessage]);
|
|
1007
|
+
setContent('');
|
|
1008
|
+
} else {
|
|
1009
|
+
// Auto-sanitize and send
|
|
1010
|
+
const newMessage = { text: sanitizedContent, user: currentUser, timestamp: Date.now() };
|
|
1011
|
+
setChatHistory([...chatHistory, newMessage]);
|
|
1012
|
+
setContent('');
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
|
|
1016
|
+
return (
|
|
1017
|
+
<div className="chat-room">
|
|
1018
|
+
<div className="messages">
|
|
1019
|
+
{chatHistory.map((msg, i) => (
|
|
1020
|
+
<div key={i} className="message">
|
|
1021
|
+
<strong>{msg.user}:</strong> {msg.text}
|
|
1022
|
+
</div>
|
|
1023
|
+
))}
|
|
1024
|
+
</div>
|
|
1025
|
+
|
|
1026
|
+
<div className="input-area">
|
|
1027
|
+
<input
|
|
1028
|
+
value={content}
|
|
1029
|
+
onChange={e => setContent(e.target.value)}
|
|
1030
|
+
onKeyPress={e => e.key === 'Enter' && sendMessage()}
|
|
1031
|
+
placeholder="Type a message..."
|
|
1032
|
+
/>
|
|
1033
|
+
<button onClick={sendMessage}>Send</button>
|
|
1034
|
+
</div>
|
|
1035
|
+
</div>
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
### 3. User Profile Management
|
|
1041
|
+
|
|
1042
|
+
```tsx
|
|
1043
|
+
// Profile creation with validation
|
|
1044
|
+
import { useProfanityValidation } from '@useverse/profanity-guard';
|
|
1045
|
+
|
|
1046
|
+
function ProfileEditor({ initialProfile }) {
|
|
1047
|
+
const [profile, setProfile] = useState(initialProfile);
|
|
1048
|
+
const { validateField, errors, hasErrors } = useProfanityValidation({
|
|
1049
|
+
level: ModerationLevel.STRICT
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
const handleSave = async () => {
|
|
1053
|
+
const fields = ['username', 'displayName', 'bio', 'location'];
|
|
1054
|
+
const validations = fields.map(field => validateField(field, profile[field]));
|
|
1055
|
+
|
|
1056
|
+
if (validations.every(v => v)) {
|
|
1057
|
+
await api.updateProfile(profile);
|
|
1058
|
+
toast.success('Profile updated successfully!');
|
|
1059
|
+
} else {
|
|
1060
|
+
toast.error('Please fix the errors before saving');
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
|
|
1064
|
+
return (
|
|
1065
|
+
<div className="profile-editor">
|
|
1066
|
+
<h2>Edit Profile</h2>
|
|
1067
|
+
|
|
1068
|
+
<div className="form-group">
|
|
1069
|
+
<label>Username</label>
|
|
1070
|
+
<input
|
|
1071
|
+
value={profile.username}
|
|
1072
|
+
onChange={e => setProfile({...profile, username: e.target.value})}
|
|
1073
|
+
onBlur={() => validateField('username', profile.username)}
|
|
1074
|
+
/>
|
|
1075
|
+
{errors.username && <span className="error">{errors.username}</span>}
|
|
1076
|
+
</div>
|
|
1077
|
+
|
|
1078
|
+
<div className="form-group">
|
|
1079
|
+
<label>Display Name</label>
|
|
1080
|
+
<input
|
|
1081
|
+
value={profile.displayName}
|
|
1082
|
+
onChange={e => setProfile({...profile, displayName: e.target.value})}
|
|
1083
|
+
onBlur={() => validateField('displayName', profile.displayName)}
|
|
1084
|
+
/>
|
|
1085
|
+
{errors.displayName && <span className="error">{errors.displayName}</span>}
|
|
1086
|
+
</div>
|
|
1087
|
+
|
|
1088
|
+
<div className="form-group">
|
|
1089
|
+
<label>Bio</label>
|
|
1090
|
+
<textarea
|
|
1091
|
+
value={profile.bio}
|
|
1092
|
+
onChange={e => setProfile({...profile, bio: e.target.value})}
|
|
1093
|
+
onBlur={() => validateField('bio', profile.bio)}
|
|
1094
|
+
maxLength={500}
|
|
1095
|
+
/>
|
|
1096
|
+
{errors.bio && <span className="error">{errors.bio}</span>}
|
|
1097
|
+
</div>
|
|
1098
|
+
|
|
1099
|
+
<button onClick={handleSave} disabled={hasErrors}>
|
|
1100
|
+
Save Profile
|
|
1101
|
+
</button>
|
|
1102
|
+
</div>
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
### 4. Content Moderation Dashboard
|
|
1108
|
+
|
|
1109
|
+
```tsx
|
|
1110
|
+
// Admin moderation dashboard
|
|
1111
|
+
import { useProfanityStats, useBatchModeration } from '@useverse/profanity-guard';
|
|
1112
|
+
|
|
1113
|
+
function ModerationPanel({ reportedContent }) {
|
|
1114
|
+
const { stats, analyzeContent } = useProfanityStats({
|
|
1115
|
+
level: ModerationLevel.MODERATE
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
const { moderateBatch } = useBatchModeration({
|
|
1119
|
+
level: ModerationLevel.MODERATE
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
const [selectedLevel, setSelectedLevel] = useState(ModerationLevel.MODERATE);
|
|
1123
|
+
const results = moderateBatch(reportedContent.map(r => r.content));
|
|
1124
|
+
|
|
1125
|
+
const handleBulkAction = (action) => {
|
|
1126
|
+
const flaggedItems = results
|
|
1127
|
+
.map((result, index) => ({ result, item: reportedContent[index] }))
|
|
1128
|
+
.filter(({ result }) => !result.isClean);
|
|
1129
|
+
|
|
1130
|
+
if (action === 'approve') {
|
|
1131
|
+
flaggedItems.forEach(({ item }) => approveContent(item.id));
|
|
1132
|
+
} else if (action === 'reject') {
|
|
1133
|
+
flaggedItems.forEach(({ item }) => rejectContent(item.id));
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
return (
|
|
1138
|
+
<div className="moderation-panel">
|
|
1139
|
+
<div className="stats-header">
|
|
1140
|
+
<h2>Moderation Dashboard</h2>
|
|
1141
|
+
<div className="stats-summary">
|
|
1142
|
+
<div>Total Analyzed: {stats.analyzed.total}</div>
|
|
1143
|
+
<div>Flagged: {stats.analyzed.flagged} ({stats.analyzed.flaggedPercentage.toFixed(1)}%)</div>
|
|
1144
|
+
<div>Clean: {stats.analyzed.clean}</div>
|
|
1145
|
+
</div>
|
|
1146
|
+
</div>
|
|
1147
|
+
|
|
1148
|
+
<div className="controls">
|
|
1149
|
+
<select value={selectedLevel} onChange={e => setSelectedLevel(e.target.value)}>
|
|
1150
|
+
<option value={ModerationLevel.RELAXED}>Relaxed</option>
|
|
1151
|
+
<option value={ModerationLevel.MODERATE}>Moderate</option>
|
|
1152
|
+
<option value={ModerationLevel.STRICT}>Strict</option>
|
|
1153
|
+
</select>
|
|
1154
|
+
|
|
1155
|
+
<button onClick={() => handleBulkAction('approve')}>Approve All Clean</button>
|
|
1156
|
+
<button onClick={() => handleBulkAction('reject')}>Reject All Flagged</button>
|
|
1157
|
+
</div>
|
|
1158
|
+
|
|
1159
|
+
<div className="content-list">
|
|
1160
|
+
{results.map((result, index) => (
|
|
1161
|
+
<div key={index} className={`content-item ${result.isClean ? 'clean' : 'flagged'}`}>
|
|
1162
|
+
<div className="content-text">{reportedContent[index].content}</div>
|
|
1163
|
+
<div className="content-meta">
|
|
1164
|
+
<span className="status">{result.isClean ? '✓ Clean' : '⚠️ Flagged'}</span>
|
|
1165
|
+
{!result.isClean && (
|
|
1166
|
+
<>
|
|
1167
|
+
<span className="severity">{result.severity}</span>
|
|
1168
|
+
<span className="words">Found: {result.foundWords.join(', ')}</span>
|
|
1169
|
+
</>
|
|
1170
|
+
)}
|
|
1171
|
+
</div>
|
|
1172
|
+
<div className="sanitized">Sanitized: {result.sanitized}</div>
|
|
1173
|
+
</div>
|
|
1174
|
+
))}
|
|
1175
|
+
</div>
|
|
1176
|
+
</div>
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
### 5. E-commerce Product Reviews
|
|
1182
|
+
|
|
1183
|
+
```tsx
|
|
1184
|
+
// Product review submission with moderation
|
|
1185
|
+
import { useModeratedInput, useProfanityGuard } from '@useverse/profanity-guard';
|
|
1186
|
+
|
|
1187
|
+
function ProductReview({ productId }) {
|
|
1188
|
+
const {
|
|
1189
|
+
content: reviewText,
|
|
1190
|
+
setContent: setReviewText,
|
|
1191
|
+
isClean,
|
|
1192
|
+
sanitizedContent,
|
|
1193
|
+
severity
|
|
1194
|
+
} = useModeratedInput('', {
|
|
1195
|
+
level: ModerationLevel.MODERATE
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
const [rating, setRating] = useState(5);
|
|
1199
|
+
const [submitted, setSubmitted] = useState(false);
|
|
1200
|
+
|
|
1201
|
+
const handleSubmit = async () => {
|
|
1202
|
+
if (!isClean && severity === 'severe') {
|
|
1203
|
+
alert('Your review contains inappropriate language and cannot be submitted.');
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
const finalReview = isClean ? reviewText : sanitizedContent;
|
|
1208
|
+
|
|
1209
|
+
await api.submitReview({
|
|
1210
|
+
productId,
|
|
1211
|
+
rating,
|
|
1212
|
+
text: finalReview,
|
|
1213
|
+
flagged: !isClean
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
setSubmitted(true);
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
return (
|
|
1220
|
+
<div className="review-form">
|
|
1221
|
+
<h3>Write a Review</h3>
|
|
1222
|
+
|
|
1223
|
+
<div className="rating-selector">
|
|
1224
|
+
<label>Rating:</label>
|
|
1225
|
+
{[1, 2, 3, 4, 5].map(star => (
|
|
1226
|
+
<button
|
|
1227
|
+
key={star}
|
|
1228
|
+
onClick={() => setRating(star)}
|
|
1229
|
+
className={rating >= star ? 'star-filled' : 'star-empty'}
|
|
1230
|
+
>
|
|
1231
|
+
★
|
|
1232
|
+
</button>
|
|
1233
|
+
))}
|
|
1234
|
+
</div>
|
|
1235
|
+
|
|
1236
|
+
<div className="review-input">
|
|
1237
|
+
<label>Your Review:</label>
|
|
1238
|
+
<textarea
|
|
1239
|
+
value={reviewText}
|
|
1240
|
+
onChange={e => setReviewText(e.target.value)}
|
|
1241
|
+
placeholder="Share your experience..."
|
|
1242
|
+
rows={6}
|
|
1243
|
+
/>
|
|
1244
|
+
|
|
1245
|
+
{!isClean && (
|
|
1246
|
+
<div className="moderation-notice">
|
|
1247
|
+
<p className="warning">
|
|
1248
|
+
⚠️ Your review contains language that may be inappropriate.
|
|
1249
|
+
</p>
|
|
1250
|
+
{severity !== 'severe' && (
|
|
1251
|
+
<p className="info">
|
|
1252
|
+
We'll sanitize it automatically: "{sanitizedContent}"
|
|
1253
|
+
</p>
|
|
1254
|
+
)}
|
|
1255
|
+
</div>
|
|
1256
|
+
)}
|
|
1257
|
+
</div>
|
|
1258
|
+
|
|
1259
|
+
<button
|
|
1260
|
+
onClick={handleSubmit}
|
|
1261
|
+
disabled={!reviewText || (severity === 'severe')}
|
|
1262
|
+
>
|
|
1263
|
+
Submit Review
|
|
1264
|
+
</button>
|
|
1265
|
+
|
|
1266
|
+
{submitted && (
|
|
1267
|
+
<div className="success-message">
|
|
1268
|
+
✓ Thank you for your review!
|
|
1269
|
+
{!isClean && ' (Your review was moderated for appropriate language)'}
|
|
1270
|
+
</div>
|
|
1271
|
+
)}
|
|
1272
|
+
</div>
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
```
|
|
1276
|
+
|
|
1277
|
+
## 🤝 Contributing
|
|
1278
|
+
|
|
1279
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
1280
|
+
|
|
1281
|
+
## 📄 License
|
|
1282
|
+
|
|
1283
|
+
MIT © [Your Name]
|
|
1284
|
+
|
|
1285
|
+
## 🔗 Links
|
|
1286
|
+
|
|
1287
|
+
- [Documentation](https://github.com/yourusername/nobadword#readme)
|
|
1288
|
+
- [NPM Package](https://www.npmjs.com/package/nobadword)
|
|
1289
|
+
- [Issue Tracker](https://github.com/yourusername/nobadword/issues)
|
|
1290
|
+
|
|
1291
|
+
## ⚠️ Disclaimer
|
|
1292
|
+
|
|
1293
|
+
This library provides content moderation capabilities but should not be the only line of defense in production applications. Always combine automated moderation with:
|
|
1294
|
+
- Human review for borderline cases
|
|
1295
|
+
- Context-aware moderation logic
|
|
1296
|
+
- Regular library updates
|
|
1297
|
+
- User reporting systems
|
|
1298
|
+
- Community guidelines
|
|
1299
|
+
|
|
1300
|
+
## 🌟 Support
|
|
1301
|
+
|
|
1302
|
+
If you find this library helpful, please consider giving it a star on GitHub!
|