mirror of
https://github.com/liyunfan1223/azerothcore-wotlk.git
synced 2026-08-07 15:28:02 +00:00
feat(Core/Chat): Add chat filter with Aho-Corasick matching (#26051)
This commit is contained in:
157
src/common/Utilities/AhoCorasick.h
Normal file
157
src/common/Utilities/AhoCorasick.h
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AhoCorasick_h__
|
||||
#define AhoCorasick_h__
|
||||
|
||||
#include "Define.h"
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Acore
|
||||
{
|
||||
/**
|
||||
* @class AhoCorasick
|
||||
*
|
||||
* @brief Multi-pattern substring matcher.
|
||||
*
|
||||
* Insert all patterns, call Build() once, then query in O(text length)
|
||||
* regardless of pattern count. Insertion after Build() is not supported
|
||||
* (it would invalidate failure links — call Build() again if you must).
|
||||
*
|
||||
* Matching is exact on the @p CharT alphabet — pre-lowercase patterns and
|
||||
* inputs in the caller if you need case-insensitive matching.
|
||||
*
|
||||
* Usage:
|
||||
* Acore::AhoCorasick<wchar_t> a;
|
||||
* a.Insert(L"foo");
|
||||
* a.Insert(L"bar");
|
||||
* a.Build();
|
||||
* bool hit = a.ContainsAny(L"the bar is open"); // -> true
|
||||
*/
|
||||
template<typename CharT>
|
||||
class AhoCorasick
|
||||
{
|
||||
public:
|
||||
using StringType = std::basic_string<CharT>;
|
||||
using StringViewType = std::basic_string_view<CharT>;
|
||||
|
||||
AhoCorasick() { _nodes.emplace_back(); }
|
||||
|
||||
void Insert(StringViewType pattern)
|
||||
{
|
||||
if (pattern.empty())
|
||||
return;
|
||||
|
||||
uint32 cur = 0;
|
||||
for (CharT c : pattern)
|
||||
{
|
||||
auto it = _nodes[cur].next.find(c);
|
||||
if (it != _nodes[cur].next.end())
|
||||
{
|
||||
cur = it->second;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32 idx = static_cast<uint32>(_nodes.size());
|
||||
_nodes.emplace_back();
|
||||
_nodes[cur].next.emplace(c, idx);
|
||||
cur = idx;
|
||||
}
|
||||
_nodes[cur].output = true;
|
||||
}
|
||||
|
||||
void Build()
|
||||
{
|
||||
std::queue<uint32> bfs;
|
||||
for (auto const& kv : _nodes[0].next)
|
||||
{
|
||||
_nodes[kv.second].fail = 0;
|
||||
bfs.push(kv.second);
|
||||
}
|
||||
|
||||
while (!bfs.empty())
|
||||
{
|
||||
uint32 u = bfs.front();
|
||||
bfs.pop();
|
||||
|
||||
for (auto const& kv : _nodes[u].next)
|
||||
{
|
||||
CharT c = kv.first;
|
||||
uint32 v = kv.second;
|
||||
|
||||
uint32 f = _nodes[u].fail;
|
||||
while (f != 0 && _nodes[f].next.find(c) == _nodes[f].next.end())
|
||||
f = _nodes[f].fail;
|
||||
|
||||
auto it = _nodes[f].next.find(c);
|
||||
_nodes[v].fail = (it != _nodes[f].next.end() && it->second != v) ? it->second : 0;
|
||||
|
||||
if (_nodes[_nodes[v].fail].output)
|
||||
_nodes[v].output = true;
|
||||
|
||||
bfs.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ContainsAny(StringViewType text) const
|
||||
{
|
||||
if (_nodes.size() <= 1)
|
||||
return false;
|
||||
|
||||
uint32 cur = 0;
|
||||
for (CharT c : text)
|
||||
{
|
||||
while (cur != 0 && _nodes[cur].next.find(c) == _nodes[cur].next.end())
|
||||
cur = _nodes[cur].fail;
|
||||
|
||||
auto it = _nodes[cur].next.find(c);
|
||||
if (it != _nodes[cur].next.end())
|
||||
cur = it->second;
|
||||
|
||||
if (_nodes[cur].output)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Empty() const { return _nodes.size() <= 1; }
|
||||
|
||||
void Clear()
|
||||
{
|
||||
_nodes.clear();
|
||||
_nodes.emplace_back();
|
||||
}
|
||||
|
||||
private:
|
||||
struct Node
|
||||
{
|
||||
std::unordered_map<CharT, uint32> next;
|
||||
uint32 fail = 0;
|
||||
bool output = false;
|
||||
};
|
||||
|
||||
std::vector<Node> _nodes;
|
||||
};
|
||||
}
|
||||
//! namespace Acore
|
||||
|
||||
#endif // AhoCorasick_h__
|
||||
@@ -4381,6 +4381,21 @@ PreserveCustomChannels = 0
|
||||
|
||||
PreserveCustomChannelDuration = 14
|
||||
|
||||
#
|
||||
# ChatFilter
|
||||
# Description: Blocks chat messages when they contain any entry from the `chat_filter`
|
||||
# table (substring, case-insensitive).
|
||||
# Manage entries with .chatfilter add / remove / list and .reload chat_filter.
|
||||
# Blizzlike is only whispers are enabled.
|
||||
# Default: 0 - (Disbaled)
|
||||
# 1 - (Enabled)
|
||||
#
|
||||
|
||||
ChatFilter.Whisper = 1
|
||||
ChatFilter.Say = 0
|
||||
ChatFilter.Yell = 0
|
||||
ChatFilter.Emote = 0
|
||||
|
||||
#
|
||||
###################################################################################################
|
||||
|
||||
|
||||
@@ -609,6 +609,12 @@ void CharacterDatabaseConnection::DoPrepareStatements()
|
||||
PrepareStatement(CHAR_INS_RESERVED_PLAYER_NAME, "INSERT IGNORE INTO reserved_name (name) VALUES (?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_INS_PROFANITY_PLAYER_NAME, "INSERT IGNORE INTO profanity_name (name) VALUES (?)", CONNECTION_ASYNC);
|
||||
|
||||
// Chat filter
|
||||
PrepareStatement(CHAR_SEL_CHAT_FILTER, "SELECT ID, Word FROM chat_filter ORDER BY ID", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_SEL_CHAT_FILTER_WORD, "SELECT ID FROM chat_filter WHERE Word = ?", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_INS_CHAT_FILTER_WORD, "INSERT INTO chat_filter (Word) VALUES (?)", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_DEL_CHAT_FILTER_WORD, "DELETE FROM chat_filter WHERE Word = ?", CONNECTION_SYNCH);
|
||||
|
||||
// Character settings
|
||||
PrepareStatement(CHAR_SEL_CHAR_SETTINGS, "SELECT source, data FROM character_settings WHERE guid = ?", CONNECTION_BOTH);
|
||||
PrepareStatement(CHAR_REP_CHAR_SETTINGS, "REPLACE INTO character_settings (guid, source, data) VALUES (?, ?, ?)", CONNECTION_ASYNC);
|
||||
|
||||
@@ -523,6 +523,11 @@ enum CharacterDatabaseStatements : uint32
|
||||
CHAR_INS_RESERVED_PLAYER_NAME,
|
||||
CHAR_INS_PROFANITY_PLAYER_NAME,
|
||||
|
||||
CHAR_SEL_CHAT_FILTER,
|
||||
CHAR_SEL_CHAT_FILTER_WORD,
|
||||
CHAR_INS_CHAT_FILTER_WORD,
|
||||
CHAR_DEL_CHAT_FILTER_WORD,
|
||||
|
||||
CHAR_SEL_CHAR_SETTINGS,
|
||||
CHAR_REP_CHAR_SETTINGS,
|
||||
CHAR_DEL_CHAR_SETTINGS,
|
||||
|
||||
@@ -684,6 +684,9 @@ enum RBACPermissions
|
||||
RBAC_PERM_COMMAND_DEBUG_INFO = 920,
|
||||
RBAC_PERM_COMMAND_DEBUG_COSMETIC = 921,
|
||||
RBAC_PERM_COMMAND_PET_RENAME = 922,
|
||||
RBAC_PERM_COMMAND_CHATFILTER_LIST = 923,
|
||||
RBAC_PERM_COMMAND_CHATFILTER_ADD = 924,
|
||||
RBAC_PERM_COMMAND_CHATFILTER_REMOVE = 925,
|
||||
// custom permissions 1000+
|
||||
RBAC_PERM_MAX
|
||||
};
|
||||
|
||||
@@ -9390,6 +9390,12 @@ void Player::Say(std::string_view text, Language language, WorldObject const* /*
|
||||
if (!sScriptMgr->OnPlayerCanUseChat(this, CHAT_MSG_SAY, language, _text))
|
||||
return;
|
||||
|
||||
if (sWorld->getBoolConfig(CONFIG_CHAT_FILTER_SAY) && IsChatFiltered(text))
|
||||
{
|
||||
ChatHandler(GetSession()).SendSysMessage(LANG_CHATFILTER_SAY);
|
||||
return;
|
||||
}
|
||||
|
||||
WorldPacket data;
|
||||
ChatHandler::BuildChatPacket(data, CHAT_MSG_SAY, language, this, this, _text);
|
||||
|
||||
@@ -9412,6 +9418,12 @@ void Player::Yell(std::string_view text, Language language, WorldObject const* /
|
||||
if (!sScriptMgr->OnPlayerCanUseChat(this, CHAT_MSG_YELL, language, _text))
|
||||
return;
|
||||
|
||||
if (sWorld->getBoolConfig(CONFIG_CHAT_FILTER_YELL) && IsChatFiltered(text))
|
||||
{
|
||||
ChatHandler(GetSession()).SendSysMessage(LANG_CHATFILTER_YELL);
|
||||
return;
|
||||
}
|
||||
|
||||
WorldPacket data;
|
||||
ChatHandler::BuildChatPacket(data, CHAT_MSG_YELL, language, this, this, _text);
|
||||
|
||||
@@ -9434,6 +9446,12 @@ void Player::TextEmote(std::string_view text, WorldObject const* /*= nullptr*/,
|
||||
if (!sScriptMgr->OnPlayerCanUseChat(this, CHAT_MSG_EMOTE, LANG_UNIVERSAL, _text))
|
||||
return;
|
||||
|
||||
if (sWorld->getBoolConfig(CONFIG_CHAT_FILTER_EMOTE) && IsChatFiltered(text))
|
||||
{
|
||||
ChatHandler(GetSession()).SendSysMessage(LANG_CHATFILTER_EMOTE);
|
||||
return;
|
||||
}
|
||||
|
||||
WorldPacket data;
|
||||
ChatHandler::BuildChatPacket(data, CHAT_MSG_EMOTE, LANG_UNIVERSAL, this, this, _text);
|
||||
|
||||
@@ -9463,15 +9481,24 @@ void Player::Whisper(std::string_view text, Language language, Player* target, b
|
||||
if (!sScriptMgr->OnPlayerCanUseChat(this, CHAT_MSG_WHISPER, language, _text, target))
|
||||
return;
|
||||
|
||||
bool isFiltered = sWorld->getBoolConfig(CONFIG_CHAT_FILTER_WHISPER) && IsChatFiltered(text);
|
||||
|
||||
WorldPacket data;
|
||||
ChatHandler::BuildChatPacket(data, CHAT_MSG_WHISPER, language, this, this, _text);
|
||||
target->SendDirectMessage(&data);
|
||||
if (!isFiltered || isAddonMessage)
|
||||
{
|
||||
ChatHandler::BuildChatPacket(data, CHAT_MSG_WHISPER, language, this, this, _text);
|
||||
target->SendDirectMessage(&data);
|
||||
}
|
||||
|
||||
// rest stuff shouldn't happen in case of addon message
|
||||
if (isAddonMessage)
|
||||
return;
|
||||
|
||||
ChatHandler::BuildChatPacket(data, CHAT_MSG_WHISPER_INFORM, Language(language), target, target, _text);
|
||||
ChatMsg msgType = CHAT_MSG_WHISPER_INFORM;
|
||||
if (isFiltered)
|
||||
msgType = CHAT_MSG_FILTERED;
|
||||
|
||||
ChatHandler::BuildChatPacket(data, msgType, Language(language), target, target, _text);
|
||||
SendDirectMessage(&data);
|
||||
|
||||
if (!isAcceptWhispers() && !IsGameMaster() && !target->IsGameMaster())
|
||||
@@ -9512,6 +9539,11 @@ void Player::Whisper(uint32 textId, Player* target, bool isBossWhisper)
|
||||
target->SendDirectMessage(&data);
|
||||
}
|
||||
|
||||
bool Player::IsChatFiltered(std::string_view text)
|
||||
{
|
||||
return sObjectMgr->IsChatFiltered(text);
|
||||
}
|
||||
|
||||
void Player::PetSpellInitialize()
|
||||
{
|
||||
Pet* pet = GetPet();
|
||||
|
||||
@@ -1245,6 +1245,8 @@ public:
|
||||
/// Handles whispers from Addons and players based on sender, receiver's guid and language.
|
||||
void Whisper(std::string_view text, Language language, Player* receiver, bool = false) override;
|
||||
void Whisper(uint32 textId, Player* target, bool isBossWhisper = false) override;
|
||||
/// Returns true if @p text contains any word from the `chat_filter` DB table (substring, case-insensitive).
|
||||
static bool IsChatFiltered(std::string_view text);
|
||||
|
||||
/*********************************************************/
|
||||
/*** STORAGE SYSTEM ***/
|
||||
|
||||
@@ -9197,6 +9197,73 @@ void ObjectMgr::AddProfanityPlayerName(std::string const& name)
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectMgr::LoadChatFilter()
|
||||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
||||
_chatFilterAutomaton.reset(); // need for reload case
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAT_FILTER);
|
||||
PreparedQueryResult result = CharacterDatabase.Query(stmt);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
LOG_WARN("server.loading", ">> Loaded 0 chat filter words. DB table `chat_filter` is empty!");
|
||||
LOG_INFO("server.loading", " ");
|
||||
return;
|
||||
}
|
||||
|
||||
auto automaton = std::make_unique<Acore::AhoCorasick<wchar_t>>();
|
||||
uint32 count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
std::string word = fields[1].Get<std::string>();
|
||||
|
||||
if (word.empty())
|
||||
continue;
|
||||
|
||||
std::wstring wstr;
|
||||
if (!Utf8toWStr(word, wstr))
|
||||
{
|
||||
LOG_ERROR("sql.sql", "Table `chat_filter` has invalid word: {}", word);
|
||||
continue;
|
||||
}
|
||||
|
||||
wstrToLower(wstr);
|
||||
|
||||
if (wstr.empty())
|
||||
continue;
|
||||
|
||||
automaton->Insert(wstr);
|
||||
++count;
|
||||
} while (result->NextRow());
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
automaton->Build();
|
||||
_chatFilterAutomaton = std::move(automaton);
|
||||
}
|
||||
|
||||
LOG_INFO("server.loading", ">> Loaded {} chat filter words in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||||
LOG_INFO("server.loading", " ");
|
||||
}
|
||||
|
||||
bool ObjectMgr::IsChatFiltered(std::string_view text) const
|
||||
{
|
||||
if (!_chatFilterAutomaton || text.empty())
|
||||
return false;
|
||||
|
||||
std::wstring wtext;
|
||||
if (!Utf8toWStr(text, wtext))
|
||||
return false;
|
||||
|
||||
wstrToLower(wtext);
|
||||
|
||||
return _chatFilterAutomaton->ContainsAny(wtext);
|
||||
}
|
||||
|
||||
enum LanguageType
|
||||
{
|
||||
LT_BASIC_LATIN = 0x0000,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#ifndef _OBJECTMGR_H
|
||||
#define _OBJECTMGR_H
|
||||
|
||||
#include "AhoCorasick.h"
|
||||
#include "Bag.h"
|
||||
#include "ConditionMgr.h"
|
||||
#include "Creature.h"
|
||||
@@ -38,6 +39,7 @@
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Item;
|
||||
@@ -1418,6 +1420,10 @@ public:
|
||||
[[nodiscard]] bool IsProfanityName(std::string_view name) const;
|
||||
void AddProfanityPlayerName(std::string const& name);
|
||||
|
||||
// chat filter (substring chat content filter)
|
||||
void LoadChatFilter();
|
||||
[[nodiscard]] bool IsChatFiltered(std::string_view text) const;
|
||||
|
||||
// name with valid structure and symbols
|
||||
static uint8 CheckPlayerName(std::string_view name, bool create = false);
|
||||
static PetNameInvalidReason CheckPetName(std::string_view name);
|
||||
@@ -1587,6 +1593,9 @@ private:
|
||||
typedef std::set<std::wstring> ProfanityNamesContainer;
|
||||
ProfanityNamesContainer _profanityNamesStore;
|
||||
|
||||
//chat filter (Aho-Corasick automaton; matches any banned word as substring of input)
|
||||
std::unique_ptr<Acore::AhoCorasick<wchar_t>> _chatFilterAutomaton;
|
||||
|
||||
GameTeleContainer _gameTeleStore;
|
||||
|
||||
ScriptNameContainer _scriptNamesStore;
|
||||
|
||||
@@ -1229,7 +1229,12 @@ enum AcoreStrings
|
||||
LANG_MAIL_RETURN_ALREADY_RETURNED = 5142,
|
||||
LANG_MAIL_RETURN_HOOK_BLOCKED = 5143,
|
||||
|
||||
// Room for more strings 5144-9999
|
||||
// Chat filter
|
||||
LANG_CHATFILTER_EMOTE = 5144,
|
||||
LANG_CHATFILTER_SAY = 5145,
|
||||
LANG_CHATFILTER_YELL = 5146,
|
||||
|
||||
// Room for more strings 5147-9999
|
||||
|
||||
// Level requirement notifications
|
||||
LANG_SAY_REQ = 6604,
|
||||
|
||||
@@ -764,6 +764,9 @@ void World::SetInitialWorldSettings()
|
||||
sObjectMgr->LoadProfanityNamesFromDB();
|
||||
sObjectMgr->LoadProfanityNamesFromDBC(); // Needs to be after LoadProfanityNamesFromDB()
|
||||
|
||||
LOG_INFO("server.loading", "Loading Chat Filter...");
|
||||
sObjectMgr->LoadChatFilter();
|
||||
|
||||
LOG_INFO("server.loading", "Loading GameObjects for Quests...");
|
||||
sObjectMgr->LoadGameObjectForQuests();
|
||||
|
||||
|
||||
@@ -198,6 +198,11 @@ void WorldConfig::BuildConfigCache()
|
||||
SetConfigValue<uint32>(CONFIG_STRICT_CHANNEL_NAMES, "StrictChannelNames", 0);
|
||||
SetConfigValue<uint32>(CONFIG_STRICT_PET_NAMES, "StrictPetNames", 0);
|
||||
|
||||
SetConfigValue<bool>(CONFIG_CHAT_FILTER_WHISPER, "ChatFilter.Whisper", true);
|
||||
SetConfigValue<bool>(CONFIG_CHAT_FILTER_SAY, "ChatFilter.Say", false);
|
||||
SetConfigValue<bool>(CONFIG_CHAT_FILTER_YELL, "ChatFilter.Yell", false);
|
||||
SetConfigValue<bool>(CONFIG_CHAT_FILTER_EMOTE, "ChatFilter.Emote", false);
|
||||
|
||||
SetConfigValue<bool>(CONFIG_ALLOW_TWO_SIDE_ACCOUNTS, "AllowTwoSide.Accounts", true);
|
||||
SetConfigValue<bool>(CONFIG_ALLOW_TWO_SIDE_INTERACTION_CALENDAR, "AllowTwoSide.Interaction.Calendar", false);
|
||||
SetConfigValue<bool>(CONFIG_ALLOW_TWO_SIDE_INTERACTION_CHAT, "AllowTwoSide.Interaction.Chat", false);
|
||||
|
||||
@@ -139,6 +139,10 @@ enum ServerConfigs
|
||||
CONFIG_OBJECT_QUEST_MARKERS,
|
||||
CONFIG_STRICT_NAMES_RESERVED,
|
||||
CONFIG_STRICT_NAMES_PROFANITY,
|
||||
CONFIG_CHAT_FILTER_WHISPER,
|
||||
CONFIG_CHAT_FILTER_SAY,
|
||||
CONFIG_CHAT_FILTER_YELL,
|
||||
CONFIG_CHAT_FILTER_EMOTE,
|
||||
CONFIG_ALLOWS_RANK_MOD_FOR_PET_HEALTH,
|
||||
CONFIG_MUNCHING_BLIZZLIKE,
|
||||
CONFIG_ENABLE_DAZE,
|
||||
|
||||
127
src/server/scripts/Commands/cs_chatfilter.cpp
Normal file
127
src/server/scripts/Commands/cs_chatfilter.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Chat.h"
|
||||
#include "CommandScript.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "ObjectMgr.h"
|
||||
|
||||
using namespace Acore::ChatCommands;
|
||||
|
||||
class chatfilter_commandscript : public CommandScript
|
||||
{
|
||||
public:
|
||||
chatfilter_commandscript() : CommandScript("chatfilter_commandscript") { }
|
||||
|
||||
ChatCommandTable GetCommands() const override
|
||||
{
|
||||
static ChatCommandTable chatfilterCommandTable =
|
||||
{
|
||||
{ "list", HandleChatFilterListCommand, rbac::RBAC_PERM_COMMAND_CHATFILTER_LIST, Console::Yes },
|
||||
{ "add", HandleChatFilterAddCommand, rbac::RBAC_PERM_COMMAND_CHATFILTER_ADD, Console::Yes },
|
||||
{ "remove", HandleChatFilterRemoveCommand, rbac::RBAC_PERM_COMMAND_CHATFILTER_REMOVE, Console::Yes }
|
||||
};
|
||||
|
||||
static ChatCommandTable commandTable =
|
||||
{
|
||||
{ "chatfilter", chatfilterCommandTable }
|
||||
};
|
||||
|
||||
return commandTable;
|
||||
}
|
||||
|
||||
static bool HandleChatFilterListCommand(ChatHandler* handler)
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAT_FILTER);
|
||||
PreparedQueryResult result = CharacterDatabase.Query(stmt);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
handler->SendSysMessage("No chat filter words found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
handler->SendSysMessage("Chat filter words:");
|
||||
uint32 count = 0;
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 id = fields[0].Get<uint32>();
|
||||
std::string word = fields[1].Get<std::string>();
|
||||
handler->PSendSysMessage(" ID: {} | Word: {}", id, word);
|
||||
++count;
|
||||
} while (result->NextRow());
|
||||
|
||||
handler->PSendSysMessage("{} chat filter word(s) total.", count);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleChatFilterAddCommand(ChatHandler* handler, Tail word)
|
||||
{
|
||||
if (word.empty())
|
||||
return false;
|
||||
|
||||
std::string text(word);
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAT_FILTER_WORD);
|
||||
stmt->SetData(0, text);
|
||||
if (CharacterDatabase.Query(stmt))
|
||||
{
|
||||
handler->SendErrorMessage("Chat filter word \"{}\" already exists.", text);
|
||||
return true;
|
||||
}
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAT_FILTER_WORD);
|
||||
stmt->SetData(0, text);
|
||||
CharacterDatabase.DirectExecute(stmt);
|
||||
|
||||
sObjectMgr->LoadChatFilter();
|
||||
|
||||
handler->PSendSysMessage("Chat filter word \"{}\" added.", text);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleChatFilterRemoveCommand(ChatHandler* handler, Tail word)
|
||||
{
|
||||
if (word.empty())
|
||||
return false;
|
||||
|
||||
std::string text(word);
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAT_FILTER_WORD);
|
||||
stmt->SetData(0, text);
|
||||
if (!CharacterDatabase.Query(stmt))
|
||||
{
|
||||
handler->SendErrorMessage("Chat filter word \"{}\" not found.", text);
|
||||
return true;
|
||||
}
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAT_FILTER_WORD);
|
||||
stmt->SetData(0, text);
|
||||
CharacterDatabase.DirectExecute(stmt);
|
||||
|
||||
sObjectMgr->LoadChatFilter();
|
||||
|
||||
handler->PSendSysMessage("Chat filter word \"{}\" removed.", text);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_chatfilter_commandscript()
|
||||
{
|
||||
new chatfilter_commandscript();
|
||||
}
|
||||
@@ -146,6 +146,7 @@ public:
|
||||
{ "reference_loot_template", HandleReloadLootTemplatesReferenceCommand, rbac::RBAC_PERM_COMMAND_RELOAD_REFERENCE_LOOT_TEMPLATE, Console::Yes },
|
||||
{ "reserved_name", HandleReloadReservedNameCommand, rbac::RBAC_PERM_COMMAND_RELOAD_RESERVED_NAME, Console::Yes },
|
||||
{ "profanity_name", HandleReloadProfanityNameCommand, rbac::RBAC_PERM_COMMAND_RELOAD, Console::Yes },
|
||||
{ "chat_filter", HandleReloadChatFilterCommand, rbac::RBAC_PERM_COMMAND_RELOAD, Console::Yes },
|
||||
{ "reputation_reward_rate", HandleReloadReputationRewardRateCommand, rbac::RBAC_PERM_COMMAND_RELOAD_REPUTATION_REWARD_RATE, Console::Yes },
|
||||
{ "reputation_spillover_template", HandleReloadReputationRewardRateCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SPILLOVER_TEMPLATE, Console::Yes },
|
||||
{ "skill_discovery_template", HandleReloadSkillDiscoveryTemplateCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SKILL_DISCOVERY_TEMPLATE, Console::Yes },
|
||||
@@ -210,6 +211,7 @@ public:
|
||||
HandleReloadCommandCommand(handler);
|
||||
HandleReloadReservedNameCommand(handler);
|
||||
HandleReloadProfanityNameCommand(handler);
|
||||
HandleReloadChatFilterCommand(handler);
|
||||
HandleReloadAcoreStringCommand(handler);
|
||||
HandleReloadGameTeleCommand(handler);
|
||||
HandleReloadCreatureMovementOverrideCommand(handler);
|
||||
@@ -832,6 +834,14 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleReloadChatFilterCommand(ChatHandler* handler)
|
||||
{
|
||||
LOG_INFO("server.loading", "Reloading Chat Filter!");
|
||||
sObjectMgr->LoadChatFilter();
|
||||
handler->SendGlobalGMSysMessage("Chat Filter reloaded.");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleReloadReputationRewardRateCommand(ChatHandler* handler)
|
||||
{
|
||||
LOG_INFO("server.loading", "Reloading `reputation_reward_rate` Table!" );
|
||||
|
||||
@@ -30,6 +30,7 @@ void AddSC_debug_commandscript();
|
||||
void AddSC_deserter_commandscript();
|
||||
void AddSC_disable_commandscript();
|
||||
void AddSC_event_commandscript();
|
||||
void AddSC_chatfilter_commandscript();
|
||||
void AddSC_gear_commandscript();
|
||||
void AddSC_gm_commandscript();
|
||||
void AddSC_go_commandscript();
|
||||
@@ -88,6 +89,7 @@ void AddCommandsScripts()
|
||||
AddSC_deserter_commandscript();
|
||||
AddSC_disable_commandscript();
|
||||
AddSC_event_commandscript();
|
||||
AddSC_chatfilter_commandscript();
|
||||
AddSC_gear_commandscript();
|
||||
AddSC_gm_commandscript();
|
||||
AddSC_go_commandscript();
|
||||
|
||||
Reference in New Issue
Block a user