[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 <akkadius1@gmail.com>
This commit is contained in:
Vayle 2023-01-18 23:42:09 -05:00 committed by GitHub
parent 03a27b02ff
commit 29473aa7f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1409 additions and 286 deletions

View File

@ -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

View File

@ -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 <ctime>
class BaseChatchannelReservedNamesRepository {
public:
struct ChatchannelReservedNames {
int32_t id;
std::string name;
};
static std::string PrimaryKey()
{
return std::string("id");
}
static std::vector<std::string> Columns()
{
return {
"id",
"name",
};
}
static std::vector<std::string> 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<ChatchannelReservedNames> &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<int32_t>(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<std::string> 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<std::string> 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<ChatchannelReservedNames> &entries
)
{
std::vector<std::string> insert_chunks;
for (auto &e: entries) {
std::vector<std::string> 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<std::string> v;
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES {}",
BaseInsert(),
Strings::Implode(",", insert_chunks)
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static std::vector<ChatchannelReservedNames> All(Database& db)
{
std::vector<ChatchannelReservedNames> 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<int32_t>(atoi(row[0]));
e.name = row[1] ? row[1] : "";
all_entries.push_back(e);
}
return all_entries;
}
static std::vector<ChatchannelReservedNames> GetWhere(Database& db, const std::string &where_filter)
{
std::vector<ChatchannelReservedNames> 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<int32_t>(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

View File

@ -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 <ctime>
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<std::string> Columns()
{
return {
"id",
"name",
"owner",
"password",
"minstatus",
};
}
static std::vector<std::string> 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<Chatchannels> &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<int32_t>(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<int32_t>(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<std::string> 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<std::string> 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<Chatchannels> &entries
)
{
std::vector<std::string> insert_chunks;
for (auto &e: entries) {
std::vector<std::string> 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<std::string> v;
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES {}",
BaseInsert(),
Strings::Implode(",", insert_chunks)
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static std::vector<Chatchannels> All(Database& db)
{
std::vector<Chatchannels> 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<int32_t>(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<int32_t>(atoi(row[4]));
all_entries.push_back(e);
}
return all_entries;
}
static std::vector<Chatchannels> GetWhere(Database& db, const std::string &where_filter)
{
std::vector<Chatchannels> 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<int32_t>(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<int32_t>(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

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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<Client*> iterator(ClientsInChannel);
LinkedListIterator<Client*> 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<ChatChannel*> 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<ChatChannel*> iterator(ChatChannels);
@ -194,7 +255,7 @@ int ChatChannel::MemberCount(int Status) {
int Count = 0;
LinkedListIterator<Client*> iterator(ClientsInChannel);
LinkedListIterator<Client*> 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<Client*> iterator(ClientsInChannel);
LinkedListIterator<Client*> 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<Client*> iterator(ClientsInChannel);
LinkedListIterator<Client*> 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<Client*> iterator(ClientsInChannel);
LinkedListIterator<Client*> 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<Client*> iterator(ClientsInChannel);
LinkedListIterator<Client*> 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<uint32>(ChannelClient->GetClientVersion())].length() == 0) {
switch (ChannelClient->GetClientVersion()) {
if (cv_messages[static_cast<uint32>(channel_client->GetClientVersion())].length() == 0) {
switch (channel_client->GetClientVersion()) {
case EQ::versions::ClientVersion::Titanium:
ServerToClient45SayLink(cv_messages[static_cast<uint32>(ChannelClient->GetClientVersion())], Message);
ServerToClient45SayLink(cv_messages[static_cast<uint32>(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<uint32>(ChannelClient->GetClientVersion())], Message);
ServerToClient50SayLink(cv_messages[static_cast<uint32>(channel_client->GetClientVersion())], Message);
break;
case EQ::versions::ClientVersion::RoF:
ServerToClient55SayLink(cv_messages[static_cast<uint32>(ChannelClient->GetClientVersion())], Message);
ServerToClient55SayLink(cv_messages[static_cast<uint32>(channel_client->GetClientVersion())], Message);
break;
case EQ::versions::ClientVersion::RoF2:
default:
cv_messages[static_cast<uint32>(ChannelClient->GetClientVersion())] = Message;
cv_messages[static_cast<uint32>(channel_client->GetClientVersion())] = Message;
break;
}
}
ChannelClient->SendChannelMessage(Name, cv_messages[static_cast<uint32>(ChannelClient->GetClientVersion())], Sender);
channel_client->SendChannelMessage(m_name, cv_messages[static_cast<uint32>(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<Client*> iterator(ClientsInChannel);
LinkedListIterator<Client*> 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<Client*> iterator(ClientsInChannel);
LinkedListIterator<Client*> 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;

View File

@ -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<Client*> ClientsInChannel;
LinkedList<Client*> m_clients_in_channel;
std::vector<std::string> Moderators;
std::vector<std::string> Invitees;
std::vector<std::string> Voiced;
std::vector<std::string> m_moderators;
std::vector<std::string> m_invitees;
std::vector<std::string> 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<std::string> 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<std::string> new_list) { m_blocked_channel_names = new_list; }
private:
LinkedList<ChatChannel*> ChatChannels;
static inline std::vector<std::string> m_blocked_channel_names;
};

View File

@ -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<std::string> 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<std::string> 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() {

View File

@ -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;

View File

@ -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<std::string> &friends, std::vector<std::string> &ignorees)
void UCSDatabase::GetFriendsAndIgnore(const int& charID, std::vector<std::string> &friends, std::vector<std::string> &ignorees)
{
std::string query = StringFormat("select `type`, `name` FROM `friends` WHERE `charid`=%i", charID);

View File

@ -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 <string>
#include <vector>
#include <map>
@ -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<std::string> &Friends, std::vector<std::string> &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<std::string> &Friends, std::vector<std::string> &Ignorees);
protected:
void HandleMysqlError(uint32 errnum);

View File

@ -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;

View File

@ -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

View File

@ -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`)