Add dual-layer profanity filtering with emoji/Unicode detection and @2toad/profanity#5
Conversation
Co-authored-by: BaconCat1 <126538523+BaconCat1@users.noreply.github.com>
|
@copilot after finishing the above task, make it run every message through https://github.com/2Toad/Profanity as a final pass before sending it. (So 2 censoring layers). Make that one on max strictness. |
… filtering Co-authored-by: BaconCat1 <126538523+BaconCat1@users.noreply.github.com>
Co-authored-by: BaconCat1 <126538523+BaconCat1@users.noreply.github.com>
… filter Co-authored-by: BaconCat1 <126538523+BaconCat1@users.noreply.github.com>
…afety Co-authored-by: BaconCat1 <126538523+BaconCat1@users.noreply.github.com>
Done! Added dual-layer profanity filtering: Layer 1 (Custom): Emoji/Unicode detection + concatenated words Applied to all outgoing messages (bot.chat, bot.whisper, Whisper method). The second layer caught additional words like "bloody", "bastard", and "piss" that the first layer intentionally skips. All 20 tests passing. Commit: eb4f111 |
There was a problem hiding this comment.
Pull request overview
This PR strengthens the bot’s outgoing-message profanity filtering to better catch emoji/Unicode obfuscations and concatenated profanities, and adds a secondary filtering layer using @2toad/profanity, with accompanying tests.
Changes:
- Extend normalization to handle Unicode confusables/emoji and improve Unicode iteration.
- Add concatenated bad-word detection for joined profanities (incl. leetspeak).
- Introduce a secondary profanity filter and apply it to
bot.chat(),bot.whisper(), andBot.Whisper(), plus new tests.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
src/structure/mineflayer/utils/profanityFilter.ts |
Adds Unicode confusable mappings, fixes Unicode iteration, and introduces concatenated bad-word detection. |
src/structure/mineflayer/utils/secondaryProfanityFilter.ts |
Implements second-layer filtering using @2toad/profanity. |
src/structure/mineflayer/Bot.ts |
Applies dual-layer filtering to outgoing chat/whisper paths. |
test/profanity-filter.test.js |
Adds tests for concatenated/leetspeak and emoji/Unicode substitutions. |
test/dual-layer-filter.test.js |
Adds tests for secondary filter behavior and dual-layer sequencing. |
package.json |
Adds @2toad/profanity dependency (but currently appears to drop existing deps). |
package-lock.json |
Updates lockfile (but currently appears to be truncated to only the new dep and changes package name casing). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export function applySecondaryFilter(text: string): string { | ||
| if (typeof text !== "string") return ""; | ||
| if (text.length === 0) return text; | ||
| return strictProfanity.censor(text, CensorType.Word); |
There was a problem hiding this comment.
applySecondaryFilter is typed as (text: string), but it contains a typeof text !== "string" guard and returns an empty string for non-strings. This branch is unreachable from TypeScript callers and makes the API intent unclear. Consider changing the parameter type to unknown (or removing the runtime type check) and deciding whether non-string inputs should be coerced with String(...) or rejected consistently.
There was a problem hiding this comment.
Fixed! Changed parameter type from string to unknown and added proper string coercion with String(text || "") to handle all input types including falsy values. Commit: ec52291
| test("secondary filter catches words missed by first layer", () => { | ||
| // "bloody" is not in the first layer's bad words list | ||
| const firstPass = censorBadWords("bloody hell"); | ||
| assert.equal(firstPass, "bloody hell"); // First layer doesn't censor it | ||
|
|
||
| const secondPass = applySecondaryFilter(firstPass); | ||
| assert.ok(secondPass.includes("*")); // Second layer should catch "bloody" | ||
| assert.notEqual(secondPass, "bloody hell"); |
There was a problem hiding this comment.
These tests assert applySecondaryFilter(...) output contains "*", but the censor character(s) are an implementation detail of @2toad/profanity (and may vary by config/version/censor type). To keep tests stable, assert that the profane token is no longer present and/or that the output differs from the input rather than checking for a specific mask character.
There was a problem hiding this comment.
Fixed! Updated test assertions to verify that profane words are no longer present in the output rather than checking for specific mask characters. Tests now check !output.includes(word) and output !== input instead of output.includes("*"). Commit: 0e74130
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…tions Co-authored-by: BaconCat1 <126538523+BaconCat1@users.noreply.github.com>
… correctly Co-authored-by: BaconCat1 <126538523+BaconCat1@users.noreply.github.com>
Chat Filter Enhancement - Complete ✅
Summary
Enhanced the profanity filter with dual-layer filtering:
Recent Updates (PR Review Feedback)
applySecondaryFiltertype signature: now acceptsunknownand coerces to stringhasConcatenatedBadWords()(already applied)Changes Implemented
1. Unicode Confusable Detection (First Layer)
2. Concatenated Word Detection (First Layer)
hasConcatenatedBadWords()function detects multiple bad words joined togetherMIN_CONCATENATED_LENGTHconstant for maintainability3. Secondary Filter (@2toad/profanity)
4. Enhanced Test Coverage
Test Results
First Layer Detection:
Second Layer Detection:
Security
Impact
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.