Files
ReSkate/Server/server_words.cpp
T
AyeZeeBB c27d6cb053 Word filter: fewer false flags, servers' own word lists; protocol 49
The chat filter matched short listed words anywhere inside a longer one, so
entries like "gger", "spik", "crip" and "coon" in the backend's lists blocked
"bigger", "spike", "script" and "raccoon".

- A listed word of four or five letters now matches inside a longer word only
  when the rest of it is what goes around a swear word ("motherfuckers",
  "bullshit", "shithead", "shitty", "xxfuckxx"). Words of six letters or more
  still match anywhere.
- An ordinary word that equals a listed phrase without its spaces
  ("the rapist" / "therapist") no longer matches.
- bad_words.txt is gone: the filter has no words until the backend's lists
  arrive.

Dedicated servers:
- "use_global_word_filter" (default true) turns the backend's lists off for a
  server. Console: words global on|off.
- data/words_filtered.txt and data/words_blocked.txt hold a server's own
  words, read at start and by "words reload". Blocked words follow
  word_warnings. Filtered words are sent to players (new packet, kind 33),
  whose games star them for players with the chat filter on and drop them on
  leaving.

Multiplayer outfits: a player whose mods add or remove skater or board slots
no longer makes everyone look like the local player. A peer's items are put
in this game's slots by slot hash; slots the peer lacks stay empty and items
in slots this game lacks are left out.
2026-10-11 13:17:28 -05:00

75 lines
3.7 KiB
C++

#include "server_words.h"
#include "Engine/Core/Text/word_filter.h"
#include "Extension/Multiplayer/Net/protocol.h"
#include <fstream>
#include <sstream>
#include <string_view>
#include <vector>
namespace dingosdk::server {
namespace {
// As many words of as many bytes as one of the team's lists holds.
constexpr std::size_t most_words = 5000, longest_word = 64;
constexpr std::string_view filtered_help =
"# Words this server filters in chat, beside the ReSkate team's list.\n"
"# One word to a line. Messages with them are still passed on: players who have their chat filter on\n"
"# see the word as stars. The first 256 words of up to 32 letters are sent to players' games.\n"
"# Read when the server starts and by the console command: words reload\n";
constexpr std::string_view blocked_help =
"# Words that are not allowed in this server's chat at all, beside the ReSkate team's list.\n"
"# One word to a line. A message with one is not passed on and its player is warned\n"
"# (see word_warnings in the README).\n"
"# Read when the server starts and by the console command: words reload\n";
std::vector<std::string> read(const std::filesystem::path &file, std::string_view help, std::string &problem) {
std::vector<std::string> words;
std::error_code error;
if (!std::filesystem::exists(file, error)) {
std::filesystem::create_directories(file.parent_path(), error);
std::ofstream out(file, std::ios::binary);
if (!(out << help)) problem = "could not write " + file.filename().string();
return words;
}
std::ifstream in(file, std::ios::binary);
if (!in) {
problem = "could not read " + file.filename().string();
return words;
}
for (std::string line; std::getline(in, line) && words.size() < most_words;) {
if (const auto comment = line.find('#'); comment != std::string::npos) line.erase(comment);
const auto first = line.find_first_not_of(" \t\r\xEF\xBB\xBF"), last = line.find_last_not_of(" \t\r");
if (first == std::string::npos) continue;
line = line.substr(first, last - first + 1);
if (line.size() <= longest_word) words.push_back(std::move(line));
}
return words;
}
} // namespace
std::filesystem::path filtered_words_file(const ServerConfig &c) { return c.file.parent_path() / "data" / "words_filtered.txt"; }
std::filesystem::path blocked_words_file(const ServerConfig &c) { return c.file.parent_path() / "data" / "words_blocked.txt"; }
OwnWords use_own_words(const ServerConfig &config) {
OwnWords result;
const auto filtered = read(filtered_words_file(config), filtered_help, result.problem);
const auto blocked = read(blocked_words_file(config), blocked_help, result.problem);
result.filtered = filtered.size();
result.blocked = blocked.size();
for (const auto &word : filtered)
if (result.sent.size() < multiplayer::max_server_words && multiplayer::valid_server_word(word)) result.sent.push_back(word);
text::set_own_words(filtered, blocked, config.global_words);
return result;
}
std::string own_words_text(const ServerConfig &config, const OwnWords &words) {
std::ostringstream out;
out << "Chat words: " << (config.global_words ? "the ReSkate team's lists are used"
: "the ReSkate team's lists are off (\"use_global_word_filter\": false)")
<< "; this server filters " << words.filtered << " word(s) of its own (data/words_filtered.txt, " << words.sent.size()
<< " sent to players) and blocks " << words.blocked << " (data/words_blocked.txt).";
if (!words.problem.empty()) out << " Problem: " << words.problem << ".";
return out.str();
}
} // namespace dingosdk::server