From 29473aa7f5398e5a6c99b92a90b486093b66717c Mon Sep 17 00:00:00 2001 From: Vayle <76063792+Valorith@users.noreply.github.com> Date: Wed, 18 Jan 2023 23:42:09 -0500 Subject: [PATCH] [Rules] Add rule to allow players to permanently save chat channels to database, up to a limit. (#2706) * Initial code * Tweak * Rule description tweak * More channel work * More adjustments * Auto-join saved permanent player channels * Fix UCS crash if player has no channels to load from table. * Implemented channel blocking feature * Update database when player channel's owner or password change * First round of requested changes. * Logic tweak to ensure player channels are sets to permanent when appropraite * name_filter table integration and some refactoring * Use new `reserved_channel_names` table to block specific channel names. * Remove some legacy channel block code * Setup required SQL update to create `reserved_channel_names` table. * Update db_update_manifest.txt * Update db_update_manifest.txt * Update chatchannel.cpp * Code review * Database to UCSDatabase * Repository SaveChatChannel * CurrentPlayerChannelCount repository * Cleanup name filter * CreateChannel * Update websocketpp * Increment CURRENT_BINARY_DATABASE_VERSION Set to 9216 * Minor tweaks to blocked channel name checks & other related areas. - Enforce blocked channel names on channel creation. - Also enforce blocked channel names on channel join. - Add channel status check to Debug logging. - Minor formatting adjustments. - Add single quotes to column name value in query. * Minor log change * Increment DB Version * Formatting Tweaks - Made formatting adjustments consistent with KinglyKrab's recommended changes. - This compiles successfully with these changes, but unable to test the changes until this weekend. Co-authored-by: Akkadius --- common/CMakeLists.txt | 4 + ...se_chatchannel_reserved_names_repository.h | 334 +++++++++++++++ .../base/base_chatchannels_repository.h | 364 +++++++++++++++++ .../chatchannel_reserved_names_repository.h | 50 +++ common/repositories/chatchannels_repository.h | 50 +++ common/ruletypes.h | 1 + common/version.h | 2 +- ucs/chatchannel.cpp | 383 ++++++++++++------ ucs/chatchannel.h | 62 +-- ucs/clientlist.cpp | 224 +++++----- ucs/clientlist.h | 9 +- ucs/database.cpp | 168 ++++++-- ucs/database.h | 28 +- ucs/worldserver.cpp | 2 +- utils/sql/db_update_manifest.txt | 1 + .../2023_01_15_chatchannel_reserved_names.sql | 13 + 16 files changed, 1409 insertions(+), 286 deletions(-) create mode 100644 common/repositories/base/base_chatchannel_reserved_names_repository.h create mode 100644 common/repositories/base/base_chatchannels_repository.h create mode 100644 common/repositories/chatchannel_reserved_names_repository.h create mode 100644 common/repositories/chatchannels_repository.h create mode 100644 utils/sql/git/required/2023_01_15_chatchannel_reserved_names.sql diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index dfbdfa1d1..b9b4fa21c 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -182,6 +182,8 @@ SET(repositories repositories/base/base_char_create_combinations_repository.h repositories/base/base_char_create_point_allocations_repository.h repositories/base/base_char_recipe_list_repository.h + repositories/base/base_chatchannels_repository.h + repositories/base/base_chatchannel_reserved_names_repository.h repositories/base/base_completed_shared_tasks_repository.h repositories/base/base_completed_shared_task_activity_state_repository.h repositories/base/base_completed_shared_task_members_repository.h @@ -358,6 +360,8 @@ SET(repositories repositories/char_create_combinations_repository.h repositories/char_create_point_allocations_repository.h repositories/char_recipe_list_repository.h + repositories/chatchannels_repository.h + repositories/chatchannel_reserved_names_repository.h repositories/completed_shared_tasks_repository.h repositories/completed_shared_task_activity_state_repository.h repositories/completed_shared_task_members_repository.h diff --git a/common/repositories/base/base_chatchannel_reserved_names_repository.h b/common/repositories/base/base_chatchannel_reserved_names_repository.h new file mode 100644 index 000000000..df5cb9241 --- /dev/null +++ b/common/repositories/base/base_chatchannel_reserved_names_repository.h @@ -0,0 +1,334 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + */ + +#ifndef EQEMU_BASE_CHATCHANNEL_RESERVED_NAMES_REPOSITORY_H +#define EQEMU_BASE_CHATCHANNEL_RESERVED_NAMES_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + + +class BaseChatchannelReservedNamesRepository { +public: + struct ChatchannelReservedNames { + int32_t id; + std::string name; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "name", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "name", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("chatchannel_reserved_names"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static ChatchannelReservedNames NewEntity() + { + ChatchannelReservedNames e{}; + + e.id = 0; + e.name = ""; + + return e; + } + + static ChatchannelReservedNames GetChatchannelReservedNames( + const std::vector &chatchannel_reserved_namess, + int chatchannel_reserved_names_id + ) + { + for (auto &chatchannel_reserved_names : chatchannel_reserved_namess) { + if (chatchannel_reserved_names.id == chatchannel_reserved_names_id) { + return chatchannel_reserved_names; + } + } + + return NewEntity(); + } + + static ChatchannelReservedNames FindOne( + Database& db, + int chatchannel_reserved_names_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + chatchannel_reserved_names_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + ChatchannelReservedNames e{}; + + e.id = static_cast(atoi(row[0])); + e.name = row[1] ? row[1] : ""; + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int chatchannel_reserved_names_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + chatchannel_reserved_names_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const ChatchannelReservedNames &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = '" + Strings::Escape(e.name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static ChatchannelReservedNames InsertOne( + Database& db, + ChatchannelReservedNames e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + ChatchannelReservedNames e{}; + + e.id = static_cast(atoi(row[0])); + e.name = row[1] ? row[1] : ""; + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + ChatchannelReservedNames e{}; + + e.id = static_cast(atoi(row[0])); + e.name = row[1] ? row[1] : ""; + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + +}; + +#endif //EQEMU_BASE_CHATCHANNEL_RESERVED_NAMES_REPOSITORY_H diff --git a/common/repositories/base/base_chatchannels_repository.h b/common/repositories/base/base_chatchannels_repository.h new file mode 100644 index 000000000..a767d589a --- /dev/null +++ b/common/repositories/base/base_chatchannels_repository.h @@ -0,0 +1,364 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + */ + +#ifndef EQEMU_BASE_CHATCHANNELS_REPOSITORY_H +#define EQEMU_BASE_CHATCHANNELS_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + + +class BaseChatchannelsRepository { +public: + struct Chatchannels { + int32_t id; + std::string name; + std::string owner; + std::string password; + int32_t minstatus; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "name", + "owner", + "password", + "minstatus", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "name", + "owner", + "password", + "minstatus", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("chatchannels"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static Chatchannels NewEntity() + { + Chatchannels e{}; + + e.id = 0; + e.name = ""; + e.owner = ""; + e.password = ""; + e.minstatus = 0; + + return e; + } + + static Chatchannels GetChatchannels( + const std::vector &chatchannelss, + int chatchannels_id + ) + { + for (auto &chatchannels : chatchannelss) { + if (chatchannels.id == chatchannels_id) { + return chatchannels; + } + } + + return NewEntity(); + } + + static Chatchannels FindOne( + Database& db, + int chatchannels_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + chatchannels_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + Chatchannels e{}; + + e.id = static_cast(atoi(row[0])); + e.name = row[1] ? row[1] : ""; + e.owner = row[2] ? row[2] : ""; + e.password = row[3] ? row[3] : ""; + e.minstatus = static_cast(atoi(row[4])); + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int chatchannels_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + chatchannels_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const Chatchannels &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = '" + Strings::Escape(e.name) + "'"); + v.push_back(columns[2] + " = '" + Strings::Escape(e.owner) + "'"); + v.push_back(columns[3] + " = '" + Strings::Escape(e.password) + "'"); + v.push_back(columns[4] + " = " + std::to_string(e.minstatus)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static Chatchannels InsertOne( + Database& db, + Chatchannels e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + v.push_back("'" + Strings::Escape(e.owner) + "'"); + v.push_back("'" + Strings::Escape(e.password) + "'"); + v.push_back(std::to_string(e.minstatus)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + v.push_back("'" + Strings::Escape(e.owner) + "'"); + v.push_back("'" + Strings::Escape(e.password) + "'"); + v.push_back(std::to_string(e.minstatus)); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + Chatchannels e{}; + + e.id = static_cast(atoi(row[0])); + e.name = row[1] ? row[1] : ""; + e.owner = row[2] ? row[2] : ""; + e.password = row[3] ? row[3] : ""; + e.minstatus = static_cast(atoi(row[4])); + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + Chatchannels e{}; + + e.id = static_cast(atoi(row[0])); + e.name = row[1] ? row[1] : ""; + e.owner = row[2] ? row[2] : ""; + e.password = row[3] ? row[3] : ""; + e.minstatus = static_cast(atoi(row[4])); + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + +}; + +#endif //EQEMU_BASE_CHATCHANNELS_REPOSITORY_H diff --git a/common/repositories/chatchannel_reserved_names_repository.h b/common/repositories/chatchannel_reserved_names_repository.h new file mode 100644 index 000000000..44fa80621 --- /dev/null +++ b/common/repositories/chatchannel_reserved_names_repository.h @@ -0,0 +1,50 @@ +#ifndef EQEMU_CHATCHANNEL_RESERVED_NAMES_REPOSITORY_H +#define EQEMU_CHATCHANNEL_RESERVED_NAMES_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_chatchannel_reserved_names_repository.h" + +class ChatchannelReservedNamesRepository: public BaseChatchannelReservedNamesRepository { +public: + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * ChatchannelReservedNamesRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * ChatchannelReservedNamesRepository::GetWhereNeverExpires() + * ChatchannelReservedNamesRepository::GetWhereXAndY() + * ChatchannelReservedNamesRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + +}; + +#endif //EQEMU_CHATCHANNEL_RESERVED_NAMES_REPOSITORY_H diff --git a/common/repositories/chatchannels_repository.h b/common/repositories/chatchannels_repository.h new file mode 100644 index 000000000..0b388d301 --- /dev/null +++ b/common/repositories/chatchannels_repository.h @@ -0,0 +1,50 @@ +#ifndef EQEMU_CHATCHANNELS_REPOSITORY_H +#define EQEMU_CHATCHANNELS_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_chatchannels_repository.h" + +class ChatchannelsRepository: public BaseChatchannelsRepository { +public: + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * ChatchannelsRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * ChatchannelsRepository::GetWhereNeverExpires() + * ChatchannelsRepository::GetWhereXAndY() + * ChatchannelsRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + +}; + +#endif //EQEMU_CHATCHANNELS_REPOSITORY_H diff --git a/common/ruletypes.h b/common/ruletypes.h index f01bb40ef..ae35f39b6 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -625,6 +625,7 @@ RULE_BOOL(Chat, EnableVoiceMacros, true, "Enable voice macros") RULE_BOOL(Chat, EnableMailKeyIPVerification, true, "Setting whether the authenticity of the client should be verified via its IP address when accessing the InGame mailbox") RULE_BOOL(Chat, EnableAntiSpam, true, "Enable anti-spam system for chat") RULE_BOOL(Chat, SuppressCommandErrors, false, "Do not suppress command errors by default") +RULE_INT(Chat, MaxPermanentPlayerChannels, 0, "Maximum number of permanent chat channels a player can make. Default 0.") RULE_INT(Chat, MinStatusToBypassAntiSpam, 100, "Minimum status to bypass the anti-spam system") RULE_INT(Chat, MinimumMessagesPerInterval, 4, "Minimum number of chat messages allowed per interval. The karma value is added to this value") RULE_INT(Chat, MaximumMessagesPerInterval, 12, "Maximum value of chat messages allowed per interval") diff --git a/common/version.h b/common/version.h index 839de550a..3a28d32d0 100644 --- a/common/version.h +++ b/common/version.h @@ -34,7 +34,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9216 +#define CURRENT_BINARY_DATABASE_VERSION 9217 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9035 diff --git a/ucs/chatchannel.cpp b/ucs/chatchannel.cpp index 233870612..92b34687c 100644 --- a/ucs/chatchannel.cpp +++ b/ucs/chatchannel.cpp @@ -33,33 +33,33 @@ void ServerToClient50SayLink(std::string& clientSayLink, const std::string& serv void ServerToClient55SayLink(std::string& clientSayLink, const std::string& serverSayLink); ChatChannel::ChatChannel(std::string inName, std::string inOwner, std::string inPassword, bool inPermanent, int inMinimumStatus) : - DeleteTimer(0) { + m_delete_timer(0) { - Name = inName; + m_name = inName; - Owner = inOwner; + m_owner = inOwner; - Password = inPassword; + m_password = inPassword; - Permanent = inPermanent; + m_permanent = inPermanent; - MinimumStatus = inMinimumStatus; + m_minimum_status = inMinimumStatus; - Moderated = false; + m_moderated = false; LogDebug( - "New ChatChannel created: Name: [[{}]], Owner: [[{}]], Password: [[{}]], MinStatus: [{}]", - Name.c_str(), - Owner.c_str(), - Password.c_str(), - MinimumStatus + "New ChatChannel created: Name: [{}], Owner: [{}], Password: [{}], MinStatus: [{}]", + m_name.c_str(), + m_owner.c_str(), + m_password.c_str(), + m_minimum_status ); } ChatChannel::~ChatChannel() { - LinkedListIterator iterator(ClientsInChannel); + LinkedListIterator iterator(m_clients_in_channel); iterator.Reset(); @@ -67,18 +67,79 @@ ChatChannel::~ChatChannel() { iterator.RemoveCurrent(false); } -ChatChannel* ChatChannelList::CreateChannel(std::string Name, std::string Owner, std::string Password, bool Permanent, int MinimumStatus) { +ChatChannel *ChatChannelList::CreateChannel( + const std::string& name, + const std::string& owner, + const std::string& password, + bool permanent, + int minimum_status, + bool save_to_db +) +{ + uint8 max_perm_player_channels = RuleI(Chat, MaxPermanentPlayerChannels); - ChatChannel *NewChannel = new ChatChannel(CapitaliseName(Name), Owner, Password, Permanent, MinimumStatus); + if (!database.CheckChannelNameFilter(name)) { + if (!(owner == SYSTEM_OWNER)) { + return nullptr; + } + else { + LogDebug("Ignoring Name Filter as channel is owned by System..."); + } + } - ChatChannels.Insert(NewChannel); + if (IsOnChannelBlockList(name)) { + if (!(owner == SYSTEM_OWNER)) { + LogInfo("Channel name [{}] is a reserved/blocked channel name. Channel creation canceled.", name); + return nullptr; + } + else { + LogInfo("Ignoring reserved/blocked channel name [{}] as channel is owned by System...", name); + } + } + else { + LogDebug("Channel name [{}] passed the reserved/blocked channel name check...", name); + } - return NewChannel; + + auto *new_channel = new ChatChannel(CapitaliseName(name), owner, password, permanent, minimum_status); + + ChatChannels.Insert(new_channel); + + if (owner == SYSTEM_OWNER) { + save_to_db = false; + } + + // If permanent player channels are enabled (and not a system channel) + // save channel to database if not exceeding limit. + bool can_save_channel = (max_perm_player_channels > 0) && !(owner == SYSTEM_OWNER) && save_to_db; + if (can_save_channel) { + + // Ensure there is room to save another chat channel to the database. + bool player_under_channel_limit = database.CurrentPlayerChannelCount(owner) + 1 <= max_perm_player_channels; + if (player_under_channel_limit) { + database.SaveChatChannel( + CapitaliseName(name), + owner, + password, + minimum_status + ); + } + else { + LogDebug( + "Maximum number of channels [{}] reached for player [{}], channel [{}] save to database aborted.", + max_perm_player_channels, + owner, + CapitaliseName(name) + ); + } + } + + return new_channel; } ChatChannel* ChatChannelList::FindChannel(std::string Name) { - std::string NormalisedName = CapitaliseName(Name); + std::string normalized_name = CapitaliseName(Name); LinkedListIterator iterator(ChatChannels); @@ -86,9 +147,9 @@ ChatChannel* ChatChannelList::FindChannel(std::string Name) { while(iterator.MoreElements()) { - ChatChannel *CurrentChannel = iterator.GetData(); + auto *current_channel = iterator.GetData(); - if(CurrentChannel && (CurrentChannel->Name == NormalisedName)) + if(current_channel && (current_channel->m_name == normalized_name)) return iterator.GetData(); iterator.Advance(); @@ -159,7 +220,7 @@ void ChatChannelList::SendAllChannels(Client *c) { void ChatChannelList::RemoveChannel(ChatChannel *Channel) { - LogDebug("RemoveChannel ([{}])", Channel->GetName().c_str()); + LogDebug("Remove channel [{}]", Channel->GetName().c_str()); LinkedListIterator iterator(ChatChannels); @@ -194,7 +255,7 @@ int ChatChannel::MemberCount(int Status) { int Count = 0; - LinkedListIterator iterator(ClientsInChannel); + LinkedListIterator iterator(m_clients_in_channel); iterator.Reset(); @@ -211,30 +272,43 @@ int ChatChannel::MemberCount(int Status) { return Count; } -void ChatChannel::SetPassword(std::string inPassword) { +void ChatChannel::SetPassword(const std::string& in_password) { - Password = inPassword; + m_password = in_password; - if(Permanent) + if(m_permanent) { - RemoveApostrophes(Password); - database.SetChannelPassword(Name, Password); + RemoveApostrophes(m_password); + database.SetChannelPassword(m_name, m_password); } } -void ChatChannel::SetOwner(std::string inOwner) { +void ChatChannel::SetOwner(std::string& in_owner) { - Owner = inOwner; + m_owner = in_owner; - if(Permanent) - database.SetChannelOwner(Name, Owner); + if(m_permanent) + database.SetChannelOwner(m_name, m_owner); +} + +// Returns the owner's name in type std::string() +std::string& ChatChannel::GetOwnerName() { + return m_owner; +} + +void ChatChannel::SetTemporary() { + m_permanent = false; +} + +void ChatChannel::SetPermanent() { + m_permanent = true; } void ChatChannel::AddClient(Client *c) { if(!c) return; - DeleteTimer.Disable(); + m_delete_timer.Disable(); if(IsClientInChannel(c)) { @@ -247,9 +321,9 @@ void ChatChannel::AddClient(Client *c) { int AccountStatus = c->GetAccountStatus(); - LogDebug("Adding [{}] to channel [{}]", c->GetName().c_str(), Name.c_str()); + LogDebug("Adding [{}] to channel [{}]", c->GetName().c_str(), m_name.c_str()); - LinkedListIterator iterator(ClientsInChannel); + LinkedListIterator iterator(m_clients_in_channel); iterator.Reset(); @@ -264,7 +338,7 @@ void ChatChannel::AddClient(Client *c) { iterator.Advance(); } - ClientsInChannel.Insert(c); + m_clients_in_channel.Insert(c); } @@ -272,46 +346,46 @@ bool ChatChannel::RemoveClient(Client *c) { if(!c) return false; - LogDebug("RemoveClient [{}] from channel [{}]", c->GetName().c_str(), GetName().c_str()); + LogDebug("Remove client [{}] from channel [{}]", c->GetName().c_str(), GetName().c_str()); - bool HideMe = c->GetHideMe(); + bool hide_me = c->GetHideMe(); - int AccountStatus = c->GetAccountStatus(); + int account_status = c->GetAccountStatus(); - int PlayersInChannel = 0; + int players_in_channel = 0; - LinkedListIterator iterator(ClientsInChannel); + LinkedListIterator iterator(m_clients_in_channel); iterator.Reset(); while(iterator.MoreElements()) { - Client *CurrentClient = iterator.GetData(); + auto *current_client = iterator.GetData(); - if(CurrentClient == c) { + if(current_client == c) { iterator.RemoveCurrent(false); } - else if(CurrentClient) { + else if(current_client) { - PlayersInChannel++; + players_in_channel++; - if(CurrentClient->IsAnnounceOn()) - if(!HideMe || (CurrentClient->GetAccountStatus() > AccountStatus)) - CurrentClient->AnnounceLeave(this, c); + if(current_client->IsAnnounceOn()) + if(!hide_me || (current_client->GetAccountStatus() > account_status)) + current_client->AnnounceLeave(this, c); iterator.Advance(); } } - if((PlayersInChannel == 0) && !Permanent) { + if((players_in_channel == 0) && !m_permanent) { - if((Password.length() == 0) || (RuleI(Channels, DeleteTimer) == 0)) + if((m_password.length() == 0) || (RuleI(Channels, DeleteTimer) == 0)) return false; - LogDebug("Starting delete timer for empty password protected channel [{}]", Name.c_str()); + LogDebug("Starting delete timer for empty password protected channel [{}]", m_name.c_str()); - DeleteTimer.Start(RuleI(Channels, DeleteTimer) * 60000); + m_delete_timer.Start(RuleI(Channels, DeleteTimer) * 60000); } return true; @@ -322,9 +396,9 @@ void ChatChannel::SendOPList(Client *c) if (!c) return; - c->GeneralChannelMessage("Channel " + Name + " op-list: (Owner=" + Owner + ")"); + c->GeneralChannelMessage("Channel " + m_name + " op-list: (Owner=" + m_owner + ")"); - for (auto &&m : Moderators) + for (auto &&m : m_moderators) c->GeneralChannelMessage(m); } @@ -350,7 +424,7 @@ void ChatChannel::SendChannelMembers(Client *c) { int MembersInLine = 0; - LinkedListIterator iterator(ClientsInChannel); + LinkedListIterator iterator(m_clients_in_channel); iterator.Reset(); @@ -389,7 +463,7 @@ void ChatChannel::SendChannelMembers(Client *c) { } -void ChatChannel::SendMessageToChannel(std::string Message, Client* Sender) { +void ChatChannel::SendMessageToChannel(const std::string& Message, Client* Sender) { if(!Sender) return; @@ -397,40 +471,40 @@ void ChatChannel::SendMessageToChannel(std::string Message, Client* Sender) { ChatMessagesSent++; - LinkedListIterator iterator(ClientsInChannel); + LinkedListIterator iterator(m_clients_in_channel); iterator.Reset(); while(iterator.MoreElements()) { - Client *ChannelClient = iterator.GetData(); + auto *channel_client = iterator.GetData(); - if(ChannelClient) + if(channel_client) { LogDebug("Sending message to [{}] from [{}]", - ChannelClient->GetName().c_str(), Sender->GetName().c_str()); + channel_client->GetName().c_str(), Sender->GetName().c_str()); - if (cv_messages[static_cast(ChannelClient->GetClientVersion())].length() == 0) { - switch (ChannelClient->GetClientVersion()) { + if (cv_messages[static_cast(channel_client->GetClientVersion())].length() == 0) { + switch (channel_client->GetClientVersion()) { case EQ::versions::ClientVersion::Titanium: - ServerToClient45SayLink(cv_messages[static_cast(ChannelClient->GetClientVersion())], Message); + ServerToClient45SayLink(cv_messages[static_cast(channel_client->GetClientVersion())], Message); break; case EQ::versions::ClientVersion::SoF: case EQ::versions::ClientVersion::SoD: case EQ::versions::ClientVersion::UF: - ServerToClient50SayLink(cv_messages[static_cast(ChannelClient->GetClientVersion())], Message); + ServerToClient50SayLink(cv_messages[static_cast(channel_client->GetClientVersion())], Message); break; case EQ::versions::ClientVersion::RoF: - ServerToClient55SayLink(cv_messages[static_cast(ChannelClient->GetClientVersion())], Message); + ServerToClient55SayLink(cv_messages[static_cast(channel_client->GetClientVersion())], Message); break; case EQ::versions::ClientVersion::RoF2: default: - cv_messages[static_cast(ChannelClient->GetClientVersion())] = Message; + cv_messages[static_cast(channel_client->GetClientVersion())] = Message; break; } } - ChannelClient->SendChannelMessage(Name, cv_messages[static_cast(ChannelClient->GetClientVersion())], Sender); + channel_client->SendChannelMessage(m_name, cv_messages[static_cast(channel_client->GetClientVersion())], Sender); } iterator.Advance(); @@ -439,9 +513,9 @@ void ChatChannel::SendMessageToChannel(std::string Message, Client* Sender) { void ChatChannel::SetModerated(bool inModerated) { - Moderated = inModerated; + m_moderated = inModerated; - LinkedListIterator iterator(ClientsInChannel); + LinkedListIterator iterator(m_clients_in_channel); iterator.Reset(); @@ -451,10 +525,10 @@ void ChatChannel::SetModerated(bool inModerated) { if(ChannelClient) { - if(Moderated) - ChannelClient->GeneralChannelMessage("Channel " + Name + " is now moderated."); + if(m_moderated) + ChannelClient->GeneralChannelMessage("Channel " + m_name + " is now moderated."); else - ChannelClient->GeneralChannelMessage("Channel " + Name + " is no longer moderated."); + ChannelClient->GeneralChannelMessage("Channel " + m_name + " is no longer moderated."); } iterator.Advance(); @@ -465,7 +539,7 @@ bool ChatChannel::IsClientInChannel(Client *c) { if(!c) return false; - LinkedListIterator iterator(ClientsInChannel); + LinkedListIterator iterator(m_clients_in_channel); iterator.Reset(); @@ -480,54 +554,80 @@ bool ChatChannel::IsClientInChannel(Client *c) { return false; } -ChatChannel *ChatChannelList::AddClientToChannel(std::string ChannelName, Client *c) { +ChatChannel *ChatChannelList::AddClientToChannel(std::string channel_name, Client *c, bool command_directed) { if(!c) return nullptr; - if((ChannelName.length() > 0) && (isdigit(ChannelName[0]))) { - + if ((channel_name.length() > 0) && (isdigit(channel_name[0]))) { // Ensure channel name does not start with a number c->GeneralChannelMessage("The channel name can not begin with a number."); - + return nullptr; + } + else if (channel_name.empty()) { // Ensure channel name is not empty return nullptr; } - std::string NormalisedName, Password; + std::string normalized_name, password; - std::string::size_type Colon = ChannelName.find_first_of(":"); + std::string::size_type Colon = channel_name.find_first_of(":"); if(Colon == std::string::npos) - NormalisedName = CapitaliseName(ChannelName); + normalized_name = CapitaliseName(channel_name); else { - NormalisedName = CapitaliseName(ChannelName.substr(0, Colon)); + normalized_name = CapitaliseName(channel_name.substr(0, Colon)); - Password = ChannelName.substr(Colon + 1); + password = channel_name.substr(Colon + 1); } - if((NormalisedName.length() > 64) || (Password.length() > 64)) { + if((normalized_name.length() > 64) || (password.length() > 64)) { c->GeneralChannelMessage("The channel name or password cannot exceed 64 characters."); return nullptr; } - LogDebug("AddClient to channel [[{}]] with password [[{}]]", NormalisedName.c_str(), Password.c_str()); + ChatChannel *RequiredChannel = FindChannel(normalized_name); - ChatChannel *RequiredChannel = FindChannel(NormalisedName); + if (RequiredChannel) { + if (IsOnChannelBlockList(channel_name)) { // Ensure channel name is not blocked + if (!(RequiredChannel->GetOwnerName() == SYSTEM_OWNER)) { + c->GeneralChannelMessage("That channel name is blocked by the server operator."); + return nullptr; + } + else { + LogDebug("Reserved/blocked channel name check for [{}] ignored due to channel being owned by System...", normalized_name); + } + } + } - if(!RequiredChannel) - RequiredChannel = CreateChannel(NormalisedName, c->GetName(), Password, false, 0); + const std::string& channel_owner = c->GetName(); - if(RequiredChannel->GetMinStatus() > c->GetAccountStatus()) { + bool permanent = false; + if (command_directed && RuleI(Chat, MaxPermanentPlayerChannels) > 0) { + permanent = true; + } - std::string Message = "You do not have the required account status to join channel " + NormalisedName; + if (!RequiredChannel) { + RequiredChannel = CreateChannel(normalized_name, channel_owner, password, permanent, 0, command_directed); + if (RequiredChannel == nullptr) { + LogDebug("Failed to create new channel with name: {}. Possible blocked or reserved channel name.", normalized_name); + c->GeneralChannelMessage("Failed to create new channel with provided name. Possible blocked or reserved channel name."); + return nullptr; + } + LogDebug("Created and added Client to channel [{}] with password [{}]. Owner: {}. Command Directed: {}", normalized_name.c_str(), password.c_str(), channel_owner, command_directed); + } + + LogDebug("Checking status requirement of channel: {}. Channel status required: {}, player status: {}.", normalized_name, std::to_string(RequiredChannel->GetMinStatus()), std::to_string(c->GetAccountStatus())); + if (RequiredChannel->GetMinStatus() > c->GetAccountStatus()) { + std::string Message = "You do not have the required account status to join channel " + normalized_name; c->GeneralChannelMessage(Message); - + LogInfo("Client [{}] connection to channel [{}] refused due to insufficient status.", c->GetName(), normalized_name); return nullptr; } - if(RequiredChannel->IsClientInChannel(c)) + if (RequiredChannel->IsClientInChannel(c)) { return nullptr; + } if(RequiredChannel->IsInvitee(c->GetName())) { @@ -538,7 +638,7 @@ ChatChannel *ChatChannelList::AddClientToChannel(std::string ChannelName, Client return RequiredChannel; } - if(RequiredChannel->CheckPassword(Password) || RequiredChannel->IsOwner(c->GetName()) || RequiredChannel->IsModerator(c->GetName()) || + if(RequiredChannel->CheckPassword(password) || RequiredChannel->IsOwner(c->GetName()) || RequiredChannel->IsModerator(c->GetName()) || c->IsChannelAdmin()) { RequiredChannel->AddClient(c); @@ -546,32 +646,42 @@ ChatChannel *ChatChannelList::AddClientToChannel(std::string ChannelName, Client return RequiredChannel; } - c->GeneralChannelMessage("Incorrect password for channel " + (NormalisedName)); + c->GeneralChannelMessage("Incorrect password for channel " + (normalized_name)); return nullptr; } -ChatChannel *ChatChannelList::RemoveClientFromChannel(std::string inChannelName, Client *c) { - +ChatChannel *ChatChannelList::RemoveClientFromChannel(const std::string& in_channel_name, Client *c, bool command_directed) { if(!c) return nullptr; - std::string ChannelName = inChannelName; + std::string channel_name = in_channel_name; - if((inChannelName.length() > 0) && isdigit(ChannelName[0])) - ChannelName = c->ChannelSlotName(atoi(inChannelName.c_str())); + if (in_channel_name.length() > 0 && isdigit(channel_name[0])) { + channel_name = c->ChannelSlotName(atoi(in_channel_name.c_str())); + } - ChatChannel *RequiredChannel = FindChannel(ChannelName); + auto *required_channel = FindChannel(channel_name); - if(!RequiredChannel) + if (!required_channel) { return nullptr; + } + + LogDebug("Client [{}] removed from channel [{}]. Channel is owned by {}. Command directed: {}", c->GetName(), channel_name, required_channel->GetOwnerName(), command_directed); + if (c->GetName() == required_channel->GetOwnerName() && command_directed) { // Check if the client that is leaving is the the channel owner + LogDebug("Owner left the channel [{}], removing channel from database...", channel_name); + database.DeleteChatChannel(channel_name); // Remove the channel from the database. + LogDebug("Flagging [{}] channel as temporary...", channel_name); + required_channel->SetTemporary(); + } // RemoveClient will return false if there is no-one left in the channel, and the channel is not permanent and has // no password. - // - if(!RequiredChannel->RemoveClient(c)) - RemoveChannel(RequiredChannel); + if (!required_channel->RemoveClient(c)) { + LogDebug("Noone left in the temporary channel [{}] and no password is set; removing temporary channel.", channel_name); + RemoveChannel(required_channel); + } - return RequiredChannel; + return required_channel; } void ChatChannelList::Process() { @@ -600,76 +710,76 @@ void ChatChannelList::Process() { void ChatChannel::AddInvitee(const std::string &Invitee) { if (!IsInvitee(Invitee)) { - Invitees.push_back(Invitee); + m_invitees.push_back(Invitee); - LogDebug("Added [{}] as invitee to channel [{}]", Invitee.c_str(), Name.c_str()); + LogDebug("Added [{}] as invitee to channel [{}]", Invitee.c_str(), m_name.c_str()); } } void ChatChannel::RemoveInvitee(std::string Invitee) { - auto it = std::find(std::begin(Invitees), std::end(Invitees), Invitee); + auto it = std::find(std::begin(m_invitees), std::end(m_invitees), Invitee); - if(it != std::end(Invitees)) { - Invitees.erase(it); - LogDebug("Removed [{}] as invitee to channel [{}]", Invitee.c_str(), Name.c_str()); + if(it != std::end(m_invitees)) { + m_invitees.erase(it); + LogDebug("Removed [{}] as invitee to channel [{}]", Invitee.c_str(), m_name.c_str()); } } bool ChatChannel::IsInvitee(std::string Invitee) { - return std::find(std::begin(Invitees), std::end(Invitees), Invitee) != std::end(Invitees); + return std::find(std::begin(m_invitees), std::end(m_invitees), Invitee) != std::end(m_invitees); } void ChatChannel::AddModerator(const std::string &Moderator) { if (!IsModerator(Moderator)) { - Moderators.push_back(Moderator); + m_moderators.push_back(Moderator); - LogInfo("Added [{}] as moderator to channel [{}]", Moderator.c_str(), Name.c_str()); + LogInfo("Added [{}] as moderator to channel [{}]", Moderator.c_str(), m_name.c_str()); } } void ChatChannel::RemoveModerator(const std::string &Moderator) { - auto it = std::find(std::begin(Moderators), std::end(Moderators), Moderator); + auto it = std::find(std::begin(m_moderators), std::end(m_moderators), Moderator); - if (it != std::end(Moderators)) { - Moderators.erase(it); - LogInfo("Removed [{}] as moderator to channel [{}]", Moderator.c_str(), Name.c_str()); + if (it != std::end(m_moderators)) { + m_moderators.erase(it); + LogInfo("Removed [{}] as moderator to channel [{}]", Moderator.c_str(), m_name.c_str()); } } bool ChatChannel::IsModerator(std::string Moderator) { - return std::find(std::begin(Moderators), std::end(Moderators), Moderator) != std::end(Moderators); + return std::find(std::begin(m_moderators), std::end(m_moderators), Moderator) != std::end(m_moderators); } void ChatChannel::AddVoice(const std::string &inVoiced) { if (!HasVoice(inVoiced)) { - Voiced.push_back(inVoiced); + m_voiced.push_back(inVoiced); - LogInfo("Added [{}] as voiced to channel [{}]", inVoiced.c_str(), Name.c_str()); + LogInfo("Added [{}] as voiced to channel [{}]", inVoiced.c_str(), m_name.c_str()); } } void ChatChannel::RemoveVoice(const std::string &inVoiced) { - auto it = std::find(std::begin(Voiced), std::end(Voiced), inVoiced); + auto it = std::find(std::begin(m_voiced), std::end(m_voiced), inVoiced); - if (it != std::end(Voiced)) { - Voiced.erase(it); + if (it != std::end(m_voiced)) { + m_voiced.erase(it); - LogInfo("Removed [{}] as voiced to channel [{}]", inVoiced.c_str(), Name.c_str()); + LogInfo("Removed [{}] as voiced to channel [{}]", inVoiced.c_str(), m_name.c_str()); } } bool ChatChannel::HasVoice(std::string inVoiced) { - return std::find(std::begin(Voiced), std::end(Voiced), inVoiced) != std::end(Voiced); + return std::find(std::begin(m_voiced), std::end(m_voiced), inVoiced) != std::end(m_voiced); } std::string CapitaliseName(std::string inString) { @@ -687,6 +797,31 @@ std::string CapitaliseName(std::string inString) { return NormalisedName; } +bool ChatChannelList::IsOnChannelBlockList(const std::string& channel_name) { + if (channel_name.empty()) { + return false; + } + + // Check if channel_name is already in the BlockedChannelNames vector + return Strings::Contains(ChatChannelList::GetBlockedChannelNames(), channel_name); +} + +void ChatChannelList::AddToChannelBlockList(const std::string& channel_name) { + if (channel_name.empty()) { + return; + } + + // Check if channelName is already in the BlockedChannelNames vector + bool is_found = Strings::Contains(ChatChannelList::GetBlockedChannelNames(), channel_name); + + // Add channelName to the BlockedChannelNames vector if it is not already present + if (!is_found) { + auto blocked_channel_names = GetBlockedChannelNames(); // Get current blocked list + blocked_channel_names.push_back(channel_name); // Add new name to local blocked list + SetChannelBlockList(blocked_channel_names); // Set blocked list to match local blocked list + } +} + void ServerToClient45SayLink(std::string& clientSayLink, const std::string& serverSayLink) { if (serverSayLink.find('\x12') == std::string::npos) { clientSayLink = serverSayLink; diff --git a/ucs/chatchannel.h b/ucs/chatchannel.h index 718ab8e4c..873c7da16 100644 --- a/ucs/chatchannel.h +++ b/ucs/chatchannel.h @@ -9,6 +9,8 @@ class Client; +#define SYSTEM_OWNER std::string("*System*") + class ChatChannel { public: @@ -21,15 +23,19 @@ public: bool IsClientInChannel(Client *c); int MemberCount(int Status); - const std::string &GetName() { return Name; } - void SendMessageToChannel(std::string Message, Client* Sender); - bool CheckPassword(std::string inPassword) { return Password.empty() || Password == inPassword; } - void SetPassword(std::string inPassword); - bool IsOwner(std::string Name) { return (Owner == Name); } - void SetOwner(std::string inOwner); + const std::string &GetName() { return m_name; } + void SendMessageToChannel(const std::string& Message, Client* Sender); + bool CheckPassword(const std::string& in_password) { return m_password.empty() || m_password == in_password; } + void SetPassword(const std::string& in_password); + bool IsOwner(const std::string& name) { return (m_owner == name); } + const std::string& GetPassword() { return m_password; } + void SetOwner(std::string& inOwner); + std::string& GetOwnerName(); + void SetTemporary(); + void SetPermanent(); void SendChannelMembers(Client *c); - int GetMinStatus() { return MinimumStatus; } - bool ReadyToDelete() { return DeleteTimer.Check(); } + int GetMinStatus() { return m_minimum_status; } + bool ReadyToDelete() { return m_delete_timer.Check(); } void SendOPList(Client *c); void AddInvitee(const std::string &Invitee); void RemoveInvitee(std::string Invitee); @@ -40,48 +46,52 @@ public: void AddVoice(const std::string &Voiced); void RemoveVoice(const std::string &Voiced); bool HasVoice(std::string Voiced); - inline bool IsModerated() { return Moderated; } + inline bool IsModerated() { return m_moderated; } void SetModerated(bool inModerated); friend class ChatChannelList; private: - std::string Name; - std::string Owner; - std::string Password; + std::string m_name; + std::string m_owner; + std::string m_password; - bool Permanent; - bool Moderated; + bool m_permanent; + bool m_moderated; - int MinimumStatus; + int m_minimum_status; - Timer DeleteTimer; + Timer m_delete_timer; - LinkedList ClientsInChannel; + LinkedList m_clients_in_channel; - std::vector Moderators; - std::vector Invitees; - std::vector Voiced; + std::vector m_moderators; + std::vector m_invitees; + std::vector m_voiced; }; class ChatChannelList { public: - ChatChannel* CreateChannel(std::string Name, std::string Owner, std::string Passwordi, bool Permanent, int MinimumStatus = 0); - ChatChannel* FindChannel(std::string Name); - ChatChannel* AddClientToChannel(std::string Channel, Client *c); - ChatChannel* RemoveClientFromChannel(std::string Channel, Client *c); - void RemoveClientFromAllChannels(Client *c); + ChatChannel* CreateChannel(const std::string& name, const std::string& owner, const std::string& password, bool permanent, int minimum_status, bool save_to_database = false); + ChatChannel* FindChannel(std::string name); + ChatChannel* AddClientToChannel(std::string channel_name, Client* c, bool command_directed = false); + ChatChannel* RemoveClientFromChannel(const std::string& in_channel_name, Client* c, bool command_directed = false); void RemoveChannel(ChatChannel *Channel); void RemoveAllChannels(); void SendAllChannels(Client *c); void Process(); - + static inline std::vector GetBlockedChannelNames() { return m_blocked_channel_names; } + static inline void ClearChannelBlockList() { m_blocked_channel_names.clear(); }; + static void AddToChannelBlockList(const std::string& channel_name); + static bool IsOnChannelBlockList(const std::string& channel_name); + static inline void SetChannelBlockList(std::vector new_list) { m_blocked_channel_names = new_list; } private: LinkedList ChatChannels; + static inline std::vector m_blocked_channel_names; }; diff --git a/ucs/clientlist.cpp b/ucs/clientlist.cpp index 6713aa7a6..dd4220bd7 100644 --- a/ucs/clientlist.cpp +++ b/ucs/clientlist.cpp @@ -48,8 +48,10 @@ int LookupCommand(const char *ChatCommand) { for (int i = 0; i < CommandEndOfList; i++) { - if (!strcasecmp(Commands[i].CommandString, ChatCommand)) + if (!strcasecmp(Commands[i].CommandString, ChatCommand)) { return Commands[i].CommandCode; + } + } return -1; @@ -594,6 +596,28 @@ void Clientlist::CheckForStaleConnections(Client *c) { } } +std::string RemoveDuplicateChannels(const std::string& in_channels) { + // Split the string by ", " and store the names in a vector + std::vector channel_names = Strings::Split(in_channels, ", "); + + // Remove duplicates by inserting the names of the channels into an unordered set + // and then copying the unique elements back into the original vector + std::unordered_set unique_channels; + channel_names.erase( + std::remove_if( + channel_names.begin(), channel_names.end(), + [&unique_channels](const std::string &channel) { + return !unique_channels.insert(channel).second; + } + ), channel_names.end() + ); + + // Concatenate the names of the unique channels into a single string + std::string unique_channels_string = Strings::Implode(", ", channel_names); + + return unique_channels_string; +} + void Clientlist::Process() { auto it = ClientChatConnections.begin(); @@ -682,10 +706,18 @@ void Clientlist::Process() CheckForStaleConnections((*it)); break; } - case OP_Mail: { - std::string CommandString = (const char *)app->pBuffer + 1; - ProcessOPMailCommand((*it), CommandString); + std::string command_string = (const char *)app->pBuffer + 1; + bool command_directed = false; + if (command_string.empty()) { + break; + } + + if (Strings::Contains(Strings::ToLower(command_string), "leave")) { + command_directed = true; + } + + ProcessOPMailCommand((*it), command_string, command_directed); break; } @@ -716,106 +748,112 @@ void Clientlist::Process() } } -void Clientlist::ProcessOPMailCommand(Client *c, std::string CommandString) +void Clientlist::ProcessOPMailCommand(Client *c, std::string command_string, bool command_directed) { - if (CommandString.length() == 0) + if (command_string.length() == 0) return; - if (isdigit(CommandString[0])) + if (isdigit(command_string[0])) { - c->SendChannelMessageByNumber(CommandString); + c->SendChannelMessageByNumber(command_string); return; } - if (CommandString[0] == '#') { + if (command_string[0] == '#') { - c->SendChannelMessage(CommandString); + c->SendChannelMessage(command_string); return; } - std::string Command, Parameters; + std::string command, parameters; - std::string::size_type Space = CommandString.find_first_of(" "); + std::string::size_type Space = command_string.find_first_of(" "); if (Space != std::string::npos) { - Command = CommandString.substr(0, Space); + command = command_string.substr(0, Space); - std::string::size_type ParametersStart = CommandString.find_first_not_of(" ", Space); + std::string::size_type parameters_start = command_string.find_first_not_of(" ", Space); - if (ParametersStart != std::string::npos) - Parameters = CommandString.substr(ParametersStart); + if (parameters_start != std::string::npos) + parameters = command_string.substr(parameters_start); + } + else { + command = command_string; } - else - Command = CommandString; - int CommandCode = LookupCommand(Command.c_str()); - - switch (CommandCode) { + auto command_code = LookupCommand(command.c_str()); + switch (command_code) { case CommandJoin: - c->JoinChannels(Parameters); + if (!command_directed) { + //Append saved channels to params + parameters = parameters + ", " + database.CurrentPlayerChannels(c->GetName()); + parameters = RemoveDuplicateChannels(parameters); + } + c->JoinChannels(parameters, command_directed); break; case CommandLeaveAll: - c->LeaveAllChannels(); + c->LeaveAllChannels(true, true); break; case CommandLeave: - c->LeaveChannels(Parameters); + c->LeaveChannels(parameters, command_directed); break; case CommandListAll: + ChannelList->SendAllChannels(c); break; case CommandList: - c->ProcessChannelList(Parameters); + c->ProcessChannelList(parameters); break; case CommandSet: c->LeaveAllChannels(false); - c->JoinChannels(Parameters); + c->JoinChannels(parameters, command_directed); break; case CommandAnnounce: - c->ToggleAnnounce(Parameters); + c->ToggleAnnounce(parameters); break; case CommandSetOwner: - c->SetChannelOwner(Parameters); + c->SetChannelOwner(parameters); break; case CommandOPList: - c->OPList(Parameters); + c->OPList(parameters); break; case CommandInvite: - c->ChannelInvite(Parameters); + c->ChannelInvite(parameters); break; case CommandGrant: - c->ChannelGrantModerator(Parameters); + c->ChannelGrantModerator(parameters); break; case CommandModerate: - c->ChannelModerate(Parameters); + c->ChannelModerate(parameters); break; case CommandVoice: - c->ChannelGrantVoice(Parameters); + c->ChannelGrantVoice(parameters); break; case CommandKick: - c->ChannelKick(Parameters); + c->ChannelKick(parameters); break; case CommandPassword: - c->SetChannelPassword(Parameters); + c->SetChannelPassword(parameters); break; case CommandToggleInvites: @@ -834,43 +872,44 @@ void Clientlist::ProcessOPMailCommand(Client *c, std::string CommandString) break; case CommandGetBody: - database.SendBody(c, atoi(Parameters.c_str())); + database.SendBody(c, atoi(parameters.c_str())); break; case CommandMailTo: - ProcessMailTo(c, Parameters); + ProcessMailTo(c, parameters); break; case CommandSetMessageStatus: - LogInfo("Set Message Status, Params: [{}]", Parameters.c_str()); - ProcessSetMessageStatus(Parameters); + LogInfo("Set Message Status, Params: [{}]", parameters.c_str()); + ProcessSetMessageStatus(parameters); break; case CommandSelectMailBox: { - std::string::size_type NumStart = Parameters.find_first_of("0123456789"); - c->ChangeMailBox(atoi(Parameters.substr(NumStart).c_str())); + std::string::size_type NumStart = parameters.find_first_of("0123456789"); + c->ChangeMailBox(atoi(parameters.substr(NumStart).c_str())); break; } case CommandSetMailForwarding: break; case CommandBuddy: - RemoveApostrophes(Parameters); - ProcessCommandBuddy(c, Parameters); + RemoveApostrophes(parameters); + ProcessCommandBuddy(c, parameters); break; case CommandIgnorePlayer: - RemoveApostrophes(Parameters); - ProcessCommandIgnore(c, Parameters); + RemoveApostrophes(parameters); + ProcessCommandIgnore(c, parameters); break; default: c->SendHelp(); - LogInfo("Unhandled OP_Mail command: [{}]", CommandString.c_str()); + LogInfo("Unhandled OP_Mail command: [{}]", command_string.c_str()); } } + void Clientlist::CloseAllConnections() { @@ -992,51 +1031,51 @@ int Client::ChannelCount() { } -void Client::JoinChannels(std::string ChannelNameList) { +void Client::JoinChannels(std::string& channel_name_list, bool command_directed) { - for (auto &elem : ChannelNameList) { + for (auto &elem : channel_name_list) { if (elem == '%') { elem = '/'; } } - LogInfo("Client: [{}] joining channels [{}]", GetName().c_str(), ChannelNameList.c_str()); + LogInfo("Client: [{}] joining channels [{}]", GetName().c_str(), channel_name_list.c_str()); - int NumberOfChannels = ChannelCount(); + auto number_of_channels = ChannelCount(); - std::string::size_type CurrentPos = ChannelNameList.find_first_not_of(" "); + auto current_pos = channel_name_list.find_first_not_of(" "); - while (CurrentPos != std::string::npos) { + while (current_pos != std::string::npos) { - if (NumberOfChannels == MAX_JOINED_CHANNELS) { + if (number_of_channels == MAX_JOINED_CHANNELS) { GeneralChannelMessage("You have joined the maximum number of channels. /leave one before trying to join another."); break; } - std::string::size_type Comma = ChannelNameList.find_first_of(", ", CurrentPos); + auto comma = channel_name_list.find_first_of(", ", current_pos); - if (Comma == std::string::npos) { + if (comma == std::string::npos) { + auto* joined_channel = ChannelList->AddClientToChannel(channel_name_list.substr(current_pos), this, command_directed); - ChatChannel* JoinedChannel = ChannelList->AddClientToChannel(ChannelNameList.substr(CurrentPos), this); - - if (JoinedChannel) - AddToChannelList(JoinedChannel); + if (joined_channel) { + AddToChannelList(joined_channel); + } break; } - ChatChannel* JoinedChannel = ChannelList->AddClientToChannel(ChannelNameList.substr(CurrentPos, Comma - CurrentPos), this); + auto* joined_channel = ChannelList->AddClientToChannel(channel_name_list.substr(current_pos, comma - current_pos), this, command_directed); - if (JoinedChannel) { + if (joined_channel) { - AddToChannelList(JoinedChannel); + AddToChannelList(joined_channel); - NumberOfChannels++; + number_of_channels++; } - CurrentPos = ChannelNameList.find_first_not_of(", ", Comma); + current_pos = channel_name_list.find_first_not_of(", ", comma); } std::string JoinedChannelsList, ChannelMessage; @@ -1097,37 +1136,36 @@ void Client::JoinChannels(std::string ChannelNameList) { safe_delete(outapp); } -void Client::LeaveChannels(std::string ChannelNameList) { +void Client::LeaveChannels(std::string& channel_name_list, bool command_directed) { + LogInfo("Client: [{}] leaving channels [{}]", GetName().c_str(), channel_name_list.c_str()); - LogInfo("Client: [{}] leaving channels [{}]", GetName().c_str(), ChannelNameList.c_str()); + auto current_pos = 0; - std::string::size_type CurrentPos = 0; + while (current_pos != std::string::npos) { - while (CurrentPos != std::string::npos) { - - std::string::size_type Comma = ChannelNameList.find_first_of(", ", CurrentPos); + std::string::size_type Comma = channel_name_list.find_first_of(", ", current_pos); if (Comma == std::string::npos) { - ChatChannel* JoinedChannel = ChannelList->RemoveClientFromChannel(ChannelNameList.substr(CurrentPos), this); + auto* joined_channel = ChannelList->RemoveClientFromChannel(channel_name_list.substr(current_pos), this, command_directed); - if (JoinedChannel) - RemoveFromChannelList(JoinedChannel); + if (joined_channel) + RemoveFromChannelList(joined_channel); break; } - ChatChannel* JoinedChannel = ChannelList->RemoveClientFromChannel(ChannelNameList.substr(CurrentPos, Comma - CurrentPos), this); + auto* joined_channel = ChannelList->RemoveClientFromChannel(channel_name_list.substr(current_pos, Comma - current_pos), this, command_directed); - if (JoinedChannel) - RemoveFromChannelList(JoinedChannel); + if (joined_channel) + RemoveFromChannelList(joined_channel); - CurrentPos = ChannelNameList.find_first_not_of(", ", Comma); + current_pos = channel_name_list.find_first_not_of(", ", Comma); } - std::string JoinedChannelsList, ChannelMessage; + std::string joined_channels_list, channel_message; - ChannelMessage = "Channels: "; + channel_message = "Channels: "; char tmp[200]; @@ -1139,26 +1177,26 @@ void Client::LeaveChannels(std::string ChannelNameList) { if (ChannelCount) { - JoinedChannelsList = JoinedChannelsList + ","; + joined_channels_list = joined_channels_list + ","; - ChannelMessage = ChannelMessage + ","; + channel_message = channel_message + ","; } - JoinedChannelsList = JoinedChannelsList + JoinedChannels[i]->GetName(); + joined_channels_list = joined_channels_list + JoinedChannels[i]->GetName(); sprintf(tmp, "%i=%s(%i)", i + 1, JoinedChannels[i]->GetName().c_str(), JoinedChannels[i]->MemberCount(Status)); - ChannelMessage += tmp; + channel_message += tmp; ChannelCount++; } } - auto outapp = new EQApplicationPacket(OP_Mail, JoinedChannelsList.length() + 1); + auto outapp = new EQApplicationPacket(OP_Mail, joined_channels_list.length() + 1); char *PacketBuffer = (char *)outapp->pBuffer; - sprintf(PacketBuffer, "%s", JoinedChannelsList.c_str()); + sprintf(PacketBuffer, "%s", joined_channels_list.c_str()); QueuePacket(outapp); @@ -1166,15 +1204,15 @@ void Client::LeaveChannels(std::string ChannelNameList) { safe_delete(outapp); if (ChannelCount == 0) - ChannelMessage = "You are not on any channels."; + channel_message = "You are not on any channels."; - outapp = new EQApplicationPacket(OP_ChannelMessage, ChannelMessage.length() + 3); + outapp = new EQApplicationPacket(OP_ChannelMessage, channel_message.length() + 3); PacketBuffer = (char *)outapp->pBuffer; VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0x00); VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0x00); - VARSTRUCT_ENCODE_STRING(PacketBuffer, ChannelMessage.c_str()); + VARSTRUCT_ENCODE_STRING(PacketBuffer, channel_message.c_str()); QueuePacket(outapp); @@ -1182,19 +1220,19 @@ void Client::LeaveChannels(std::string ChannelNameList) { safe_delete(outapp); } -void Client::LeaveAllChannels(bool SendUpdatedChannelList) { +void Client::LeaveAllChannels(bool send_updated_channel_list, bool command_directed) { for (auto &elem : JoinedChannels) { if (elem) { - ChannelList->RemoveClientFromChannel(elem->GetName(), this); + ChannelList->RemoveClientFromChannel(elem->GetName(), this, command_directed); elem = nullptr; } } - if (SendUpdatedChannelList) + if (send_updated_channel_list) SendChannelList(); } @@ -1643,6 +1681,7 @@ void Client::SetChannelPassword(std::string ChannelPassword) { } RequiredChannel->SetPassword(Password); + database.SaveChatChannel(RequiredChannel->GetName(), RequiredChannel->GetOwnerName(), Password, RequiredChannel->GetMinStatus()); // Update DB with new password GeneralChannelMessage(Message); @@ -1703,6 +1742,7 @@ void Client::SetChannelOwner(std::string CommandString) { } RequiredChannel->SetOwner(NewOwner); + database.SaveChatChannel(RequiredChannel->GetName(), NewOwner, RequiredChannel->GetPassword(), RequiredChannel->GetMinStatus()); // Update DB with new owner if (RequiredChannel->IsModerator(NewOwner)) RequiredChannel->RemoveModerator(NewOwner); @@ -2121,7 +2161,7 @@ void Client::ChannelKick(std::string CommandString) { GeneralChannelMessage("Kicked " + Kickee + " from channel " + ChannelName); - RequiredClient->LeaveChannels(ChannelName); + RequiredClient->LeaveChannels(ChannelName, false); } void Client::ToggleInvites() { diff --git a/ucs/clientlist.h b/ucs/clientlist.h index 13d31784c..ac511176c 100644 --- a/ucs/clientlist.h +++ b/ucs/clientlist.h @@ -92,9 +92,9 @@ public: void SendMailBoxes(); inline void QueuePacket(const EQApplicationPacket *p, bool ack_req=true) { ClientStream->QueuePacket(p, ack_req); } std::string GetName() { if(Characters.size()) return Characters[0].Name; else return ""; } - void JoinChannels(std::string ChannelList); - void LeaveChannels(std::string ChannelList); - void LeaveAllChannels(bool SendUpdatedChannelList = true); + void JoinChannels(std::string& channel_name_list, bool command_directed = false); + void LeaveChannels(std::string& channel_name_list, bool command_directed = false); + void LeaveAllChannels(bool send_updated_channel_list = true, bool command_directed = false); void AddToChannelList(ChatChannel *JoinedChannel); void RemoveFromChannelList(ChatChannel *JoinedChannel); void SendChannelMessage(std::string Message); @@ -112,6 +112,7 @@ public: void ProcessChannelList(std::string CommandString); void AccountUpdate(); int ChannelCount(); + std::string RemoveDuplicateChannels(std::string& in_channels); inline void SetAccountID(int inAccountID) { AccountID = inAccountID; } inline int GetAccountID() { return AccountID; } inline void SetAccountStatus(int inStatus) { Status = inStatus; } @@ -187,7 +188,7 @@ public: void CheckForStaleConnectionsAll(); void CheckForStaleConnections(Client *c); Client *IsCharacterOnline(std::string CharacterName); - void ProcessOPMailCommand(Client *c, std::string CommandString); + void ProcessOPMailCommand(Client* c, std::string command_string, bool command_directed = false); private: EQ::Net::EQStreamManager *chatsf; diff --git a/ucs/database.cpp b/ucs/database.cpp index c682f5939..135a33ab4 100644 --- a/ucs/database.cpp +++ b/ucs/database.cpp @@ -49,6 +49,9 @@ #include "../common/misc_functions.h" #include "../common/strings.h" #include "chatchannel.h" +#include "../common/repositories/chatchannel_reserved_names_repository.h" +#include "../common/repositories/chatchannels_repository.h" +#include "../common/repositories/name_filter_repository.h" extern Clientlist *g_Clientlist; extern std::string GetMailPrefix(); @@ -219,51 +222,160 @@ bool UCSDatabase::GetVariable(const char *varname, char *varvalue, uint16 varval return true; } + bool UCSDatabase::LoadChatChannels() { - + LoadReservedNamesFromDB(); LogInfo("Loading chat channels from the database"); const std::string query = "SELECT `name`, `owner`, `password`, `minstatus` FROM `chatchannels`"; - auto results = QueryDatabase(query); + auto results = QueryDatabase(query); if (!results.Success()) { return false; } for (auto row = results.begin(); row != results.end(); ++row) { - std::string channelName = row[0]; - std::string channelOwner = row[1]; - std::string channelPassword = row[2]; + std::string channel_name = row[0]; + std::string channel_owner = row[1]; + std::string channel_password = row[2]; + auto channel_min_status = row[3]; - ChannelList->CreateChannel(channelName, channelOwner, channelPassword, true, atoi(row[3])); + if (!ChannelList->FindChannel(channel_name)) { + ChannelList->CreateChannel(channel_name, channel_owner, channel_password, true, atoi(channel_min_status), false); + } } - return true; } -void UCSDatabase::SetChannelPassword(std::string channelName, std::string password) +void UCSDatabase::LoadReservedNamesFromDB() { - LogInfo("UCSDatabase::SetChannelPassword([{}], [{}])", channelName.c_str(), password.c_str()); + ChatChannelList::ClearChannelBlockList(); - std::string query = StringFormat( - "UPDATE `chatchannels` SET `password` = '%s' WHERE `name` = '%s'", - password.c_str(), channelName.c_str()); + auto channels = ChatchannelReservedNamesRepository::All(*this); + if (channels.empty()) { + LogDebug("No reserved names exist in the database..."); + } + + for (auto &e: channels) { + ChatChannelList::AddToChannelBlockList(e.name); + LogInfo("Adding channel [{}] to blocked list from database...", e.name); + } + + LogInfo("Loaded [{}] reserved channel name(s)", channels.size()); +} + +bool UCSDatabase::IsChatChannelInDB(const std::string& channel_name) +{ + auto r = ChatchannelsRepository::Count( + *this, + fmt::format( + "name = {}", Strings::Escape(channel_name) + ) + ); + + return r > 0; +} + +void UCSDatabase::SaveChatChannel( + const std::string& channel_name, + const std::string& channel_owner, + const std::string& channel_password, + const uint16& min_status +) +{ + auto e = ChatchannelsRepository::GetWhere( + *this, + fmt::format( + "`name` = '{}' LIMIT 1", Strings::Escape(channel_name) + ) + ); + + // If channel name is blocked, do not save it to the database + if (ChatChannelList::IsOnChannelBlockList(channel_name)) { + LogInfo("Channel [{}] already found on the block list, ignoring", channel_name); + return; + } + + // update if exists, create new if it doesn't + auto c = !e.empty() ? e[0] : ChatchannelsRepository::NewEntity(); + c.name = channel_name; + c.owner = channel_owner; + c.password = channel_password; + c.minstatus = min_status; + + if (e.empty()) { + ChatchannelsRepository::InsertOne(*this, c); + return; + } + + ChatchannelsRepository::UpdateOne(*this, c); +} + +void UCSDatabase::DeleteChatChannel(const std::string& channel_name) +{ + ChatchannelsRepository::DeleteWhere(*this, fmt::format("`name` = '{}'", Strings::Escape(channel_name))); + LogInfo("Deleting channel [{}] from the database.", channel_name); +} + +std::string UCSDatabase::CurrentPlayerChannels(const std::string& player_name) { + int current_player_channel_count = CurrentPlayerChannelCount(player_name); + if (current_player_channel_count == 0) { + return ""; + } + const auto rquery = fmt::format("SELECT GROUP_CONCAT(`name` SEPARATOR ', ') FROM chatchannels WHERE `owner` = '{}'; ", Strings::Escape(player_name)); + auto results = QueryDatabase(rquery); + auto row = results.begin(); + std::string channels = row[0]; + LogDebug("Player [{}] has the following permanent channels saved to the database: [{}].", player_name, channels); + return channels; +} + +int UCSDatabase::CurrentPlayerChannelCount(const std::string& player_name) +{ + return (int) ChatchannelsRepository::Count(*this, fmt::format("`owner` = '{}'", Strings::Escape(player_name))); +} + +void UCSDatabase::SetChannelPassword(const std::string& channel_name, const std::string& password) +{ + LogInfo("UCSDatabase::SetChannelPassword([{}], [{}])", channel_name.c_str(), password.c_str()); + + std::string query = fmt::format( + "UPDATE `chatchannels` SET `password` = '{}' WHERE `name` = '{}'", + Strings::Escape(password), Strings::Escape(channel_name)); QueryDatabase(query); } -void UCSDatabase::SetChannelOwner(std::string channelName, std::string owner) +void UCSDatabase::SetChannelOwner(const std::string& channel_name, const std::string& owner) { - LogInfo("UCSDatabase::SetChannelOwner([{}], [{}])", channelName.c_str(), owner.c_str()); + LogInfo("Setting channel [{}] owner to [{}]", channel_name, owner); - std::string query = StringFormat( - "UPDATE `chatchannels` SET `owner` = '%s' WHERE `name` = '%s'", - owner.c_str(), - channelName.c_str() + std::string query = fmt::format( + "UPDATE `chatchannels` SET `owner` = '{}' WHERE `name` = '{}'", + Strings::Escape(owner), + Strings::Escape(channel_name) ); QueryDatabase(query); } +bool UCSDatabase::CheckChannelNameFilter(const std::string& channel_name) +{ + LogDebug("Checking if [{}] is on the name filter", channel_name); + + // TODO: This should potentially just be pulled into memory at some other point + // This if fine for now + for (auto &e: NameFilterRepository::All(*this)) { + if (Strings::Contains(Strings::ToLower(channel_name), Strings::ToLower(e.name))) { + LogInfo("Failed to pass name filter check for [{}] against word [{}]", channel_name, e.name); + return false; + } + } + + LogDebug("Name Filter Check Passed!"); + + return true; +} + void UCSDatabase::SendHeaders(Client *client) { @@ -356,7 +468,7 @@ void UCSDatabase::SendHeaders(Client *client) } -void UCSDatabase::SendBody(Client *client, int messageNumber) +void UCSDatabase::SendBody(Client *client, const int& messageNumber) { int characterID = FindCharacter(client->MailBoxName().c_str()); @@ -412,11 +524,11 @@ void UCSDatabase::SendBody(Client *client, int messageNumber) } bool UCSDatabase::SendMail( - std::string recipient, - std::string from, - std::string subject, - std::string body, - std::string recipientsString + const std::string& recipient, + const std::string& from, + const std::string& subject, + const std::string& body, + const std::string& recipientsString ) { @@ -484,7 +596,7 @@ bool UCSDatabase::SendMail( return true; } -void UCSDatabase::SetMessageStatus(int messageNumber, int status) +void UCSDatabase::SetMessageStatus(const int& messageNumber, const int& status) { LogInfo("SetMessageStatus [{}] [{}]", messageNumber, status); @@ -557,7 +669,7 @@ void UCSDatabase::ExpireMail() } } -void UCSDatabase::AddFriendOrIgnore(int charID, int type, std::string name) +void UCSDatabase::AddFriendOrIgnore(const int& charID, const int& type, const std::string& name) { std::string query = StringFormat( "INSERT INTO `friends` (`charid`, `type`, `name`) " @@ -579,7 +691,7 @@ void UCSDatabase::AddFriendOrIgnore(int charID, int type, std::string name) } } -void UCSDatabase::RemoveFriendOrIgnore(int charID, int type, std::string name) +void UCSDatabase::RemoveFriendOrIgnore(const int& charID, const int& type, const std::string& name) { std::string query = StringFormat( "DELETE FROM `friends` WHERE `charid` = %i AND `type` = %i AND `name` = '%s'", @@ -600,7 +712,7 @@ void UCSDatabase::RemoveFriendOrIgnore(int charID, int type, std::string name) } } -void UCSDatabase::GetFriendsAndIgnore(int charID, std::vector &friends, std::vector &ignorees) +void UCSDatabase::GetFriendsAndIgnore(const int& charID, std::vector &friends, std::vector &ignorees) { std::string query = StringFormat("select `type`, `name` FROM `friends` WHERE `charid`=%i", charID); diff --git a/ucs/database.h b/ucs/database.h index e16c11845..0966cff4f 100644 --- a/ucs/database.h +++ b/ucs/database.h @@ -7,7 +7,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY except by those people which sell it, which - are required to give you total support for your newly bought product; + are required to give you total support for your newly bought product without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. @@ -27,7 +27,9 @@ #include "../common/types.h" #include "../common/database.h" #include "../common/linked_list.h" +#include "../common/database.h" #include "clientlist.h" +#include "chatchannel.h" #include #include #include @@ -42,18 +44,24 @@ public: bool VerifyMailKey(std::string CharacterName, int IPAddress, std::string MailKey); bool GetVariable(const char* varname, char* varvalue, uint16 varvalue_len); bool LoadChatChannels(); + void LoadReservedNamesFromDB(); + bool IsChatChannelInDB(const std::string& channel_name); + bool CheckChannelNameFilter(const std::string& channel_name); + void SaveChatChannel(const std::string& channel_name, const std::string& channel_owner, const std::string& channel_password, const uint16& min_status); + void DeleteChatChannel(const std::string& channel_name); + int CurrentPlayerChannelCount(const std::string& player_name); + std::string CurrentPlayerChannels(const std::string& player_name); void GetAccountStatus(Client *c); - void SetChannelPassword(std::string ChannelName, std::string Password); - void SetChannelOwner(std::string ChannelName, std::string Owner); + void SetChannelPassword(const std::string& channel_name, const std::string& password); + void SetChannelOwner(const std::string& channel_name, const std::string& owner); void SendHeaders(Client *c); - void SendBody(Client *c, int MessageNumber); - bool SendMail(std::string Recipient, std::string From, std::string Subject, std::string Body, std::string RecipientsString); - void SetMessageStatus(int MessageNumber, int Status); + void SendBody(Client *c, const int& message_number); + bool SendMail(const std::string& recipient, const std::string& from, const std::string& subject, const std::string& body, const std::string& recipients_string); + void SetMessageStatus(const int& message_number, const int& Status); void ExpireMail(); - void AddFriendOrIgnore(int CharID, int Type, std::string Name); - void RemoveFriendOrIgnore(int CharID, int Type, std::string Name); - void GetFriendsAndIgnore(int CharID, std::vector &Friends, std::vector &Ignorees); - + void AddFriendOrIgnore(const int& char_id, const int& type, const std::string& name); + void RemoveFriendOrIgnore(const int& char_id, const int& type, const std::string& name); + void GetFriendsAndIgnore(const int& char_id, std::vector &Friends, std::vector &Ignorees); protected: void HandleMysqlError(uint32 errnum); diff --git a/ucs/worldserver.cpp b/ucs/worldserver.cpp index 0aa4b5477..f7522040c 100644 --- a/ucs/worldserver.cpp +++ b/ucs/worldserver.cpp @@ -138,7 +138,7 @@ void WorldServer::ProcessMessage(uint16 opcode, EQ::Net::Packet &p) } else if (Message[0] == '[') { - g_Clientlist->ProcessOPMailCommand(c, Message.substr(1, std::string::npos)); + g_Clientlist->ProcessOPMailCommand(c, Message.substr(1, std::string::npos), true); // Flag as command_directed } break; diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index a842c6950..1302574d3 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -470,6 +470,7 @@ 9214|2022_12_24_character_exp_toggle.sql|SHOW COLUMNS FROM `character_data` LIKE 'exp_enabled'|empty| 9215|2023_01_08_zone_max_level.sql|SHOW COLUMNS FROM `zone` LIKE 'max_level'|empty| 9216|2023_01_15_merc_data.sql|SHOW TABLES LIKE 'mercs'|empty| +9217|2023_01_15_chatchannel_reserved_names.sql|SHOW TABLES LIKE 'chatchannel_reserved_names'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/2023_01_15_chatchannel_reserved_names.sql b/utils/sql/git/required/2023_01_15_chatchannel_reserved_names.sql new file mode 100644 index 000000000..c39424d8f --- /dev/null +++ b/utils/sql/git/required/2023_01_15_chatchannel_reserved_names.sql @@ -0,0 +1,13 @@ +CREATE TABLE `chatchannel_reserved_names` +( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(64) NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +ALTER TABLE `chatchannels` + ADD COLUMN `id` int(11) NOT NULL AUTO_INCREMENT FIRST, +DROP PRIMARY KEY, +ADD PRIMARY KEY (`id`) USING BTREE, +ADD UNIQUE INDEX(`name`)