mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
feat(chat-filter): the server filters phrases in normal chat both ways
The client's word check before sending is a light one; the server is the full filter. In normal (whitelist) chat, block list phrases are now stopped even when each word is allowed, and allowed phrases (from the dashboard or a line with spaces in the allowed words file) let their words through together. The dashboard can allow phrases and its message test explains both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -142,12 +142,42 @@ TEST(ChatFilterCoreTest, DashboardPhrasesInWhitelistChat) {
|
||||
// Blocked on the dashboard: stopped in whitelist chat too, even though each word is allowed
|
||||
EXPECT_EQ(CheckMessage("hello bad phrase", true, lists), (Spans{ { 6, 10 } }));
|
||||
EXPECT_TRUE(CheckMessage("hello bad there phrase", true, lists).empty());
|
||||
// Whitelist chat checks one word at a time, as the client does
|
||||
// A word that isn't allowed is stopped in whitelist chat
|
||||
EXPECT_EQ(CheckMessage("hello stranger", true, lists), (Spans{ { 6, 8 } }));
|
||||
lists.customAllowed.AddEntry("stranger");
|
||||
EXPECT_TRUE(CheckMessage("hello stranger", true, lists).empty());
|
||||
}
|
||||
|
||||
TEST(ChatFilterCoreTest, BlockListPhrasesInWhitelistChat) {
|
||||
// Each word is allowed, the phrase is on the block list: the server stops it in whitelist chat too
|
||||
const auto lists = FreeChatLists("bad phrase\nfriend\n");
|
||||
EXPECT_EQ(CheckMessage("hello bad phrase", true, lists), (Spans{ { 6, 10 } }));
|
||||
// A single blocked word only counts in free chat; whitelist chat goes by the allowed words
|
||||
EXPECT_TRUE(CheckMessage("hello friend", true, lists).empty());
|
||||
EXPECT_EQ(CheckMessage("hello friend", false, lists), (Spans{ { 6, 6 } }));
|
||||
}
|
||||
|
||||
TEST(ChatFilterCoreTest, AllowedPhrasesInWhitelistChat) {
|
||||
auto lists = FreeChatLists("");
|
||||
lists.customAllowed.AddEntry(NormalizeEntry("Nexus Tower"));
|
||||
// Neither word is allowed alone, together they are
|
||||
EXPECT_TRUE(CheckMessage("hello nexus tower", true, lists).empty());
|
||||
EXPECT_TRUE(CheckMessage("Nexus Tower!", true, lists).empty());
|
||||
EXPECT_EQ(CheckMessage("hello tower", true, lists), (Spans{ { 6, 5 } }));
|
||||
EXPECT_EQ(CheckMessage("nexus hello tower", true, lists), (Spans{ { 0, 5 }, { 12, 5 } }));
|
||||
// From the allowed words file as well
|
||||
Lists fromFile;
|
||||
fromFile.approved = dChatFilterDCF::AllowListFromText("hello\nnexus tower\n");
|
||||
EXPECT_TRUE(CheckMessage("hello nexus tower", true, fromFile).empty());
|
||||
// A blocked word breaks the phrase
|
||||
lists.customBlocked.AddEntry("tower");
|
||||
EXPECT_EQ(CheckMessage("nexus tower", true, lists), (Spans{ { 0, 5 }, { 6, 5 } }));
|
||||
// Free chat ignores the allowed lists
|
||||
auto freeChat = FreeChatLists("tower\n");
|
||||
freeChat.customAllowed.AddEntry(NormalizeEntry("Nexus Tower"));
|
||||
EXPECT_EQ(CheckMessage("nexus tower", false, freeChat), (Spans{ { 6, 5 } }));
|
||||
}
|
||||
|
||||
TEST(ChatFilterCoreTest, ShippedBlockListIsPortable) {
|
||||
const auto parsed = dChatFilterDCF::ReadFile(std::string(DLU_SOURCE_DIR) + "/resources/blocklist.dcf");
|
||||
ASSERT_EQ(parsed.status, dChatFilterDCF::eStatus::OK);
|
||||
|
||||
@@ -123,7 +123,25 @@ TEST(ChatFilterWordsTest, ExplainPhrases) {
|
||||
EXPECT_EQ(Reasons(verdicts), (std::vector<std::string>{ "ok allow_file", "x blocked_here", "x blocked_here", "x blocked_here" }));
|
||||
EXPECT_EQ(verdicts[1].phrase, "no way");
|
||||
EXPECT_EQ(verdicts[3].text, "way!");
|
||||
// The block file's phrases only in free chat
|
||||
// The block file's phrases in free chat and in normal chat, even when each word is allowed
|
||||
EXPECT_EQ(Reasons(ModerationTools::ExplainMessage("very rude", false, sources)), (std::vector<std::string>{ "x block_file", "x block_file" }));
|
||||
sources.allowFile = [](const std::string& w) { return w == "very" || w == "rude"; };
|
||||
EXPECT_EQ(Reasons(ModerationTools::ExplainMessage("very rude", true, sources)), (std::vector<std::string>{ "x block_file", "x block_file" }));
|
||||
EXPECT_EQ(Reasons(ModerationTools::ExplainMessage("rude very", false, sources)), (std::vector<std::string>{ "ok not_in_block_file", "ok not_in_block_file" }));
|
||||
}
|
||||
|
||||
TEST(ChatFilterWordsTest, ExplainAllowedPhrases) {
|
||||
auto sources = Sources();
|
||||
sources.dashboard = [](const std::string& w) -> std::optional<bool> {
|
||||
if (w == "nexus tower") return true;
|
||||
return std::nullopt;
|
||||
};
|
||||
sources.maxAllowedWords = 2;
|
||||
// Neither word is allowed alone; together they are
|
||||
auto verdicts = ModerationTools::ExplainMessage("hello Nexus Tower", true, sources);
|
||||
EXPECT_EQ(Reasons(verdicts), (std::vector<std::string>{ "ok allow_file", "ok allowed_here", "ok allowed_here", "ok allowed_here" }));
|
||||
EXPECT_EQ(verdicts[1].phrase, "nexus tower");
|
||||
EXPECT_EQ(Reasons(ModerationTools::ExplainMessage("tower", true, sources)), (std::vector<std::string>{ "x not_allowed" }));
|
||||
// Free chat doesn't use allowed phrases
|
||||
EXPECT_EQ(Reasons(ModerationTools::ExplainMessage("nexus tower", false, sources)), (std::vector<std::string>{ "ok not_in_block_file", "ok not_in_block_file" }));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user