diff --git a/conf/CMakeLists.txt b/conf/CMakeLists.txt index 4553b21..d12ff70 100644 --- a/conf/CMakeLists.txt +++ b/conf/CMakeLists.txt @@ -3,7 +3,7 @@ set(OUTPUT_CONFS ad.json anongame_infos.conf address_translation.conf autoupdate.conf bnalias.conf bnban.conf bnetd_default_user.plain bnissue.txt bnmaps.conf bnxpcalc.conf bnxplevel.conf channel.conf command_groups.conf realm.conf - sql_DB_layout.conf supportfile.conf topics.conf + sql_DB_layout.conf supportfile.conf topics.json tournament.conf versioncheck.json icons.conf email_verification.conf cacert.pem) foreach(CONF ${OUTPUT_CONFS}) @@ -20,7 +20,7 @@ if(WITH_BNETD) autoupdate.conf bnalias.conf bnban.conf bnetd_default_user.plain bnissue.txt bnmaps.conf bnxpcalc.conf bnxplevel.conf channel.conf command_groups.conf - realm.conf sql_DB_layout.conf supportfile.conf topics.conf + realm.conf sql_DB_layout.conf supportfile.conf topics.json tournament.conf versioncheck.json icons.conf email_verification.conf cacert.pem) endif(WITH_BNETD) diff --git a/conf/bnetd.conf.in b/conf/bnetd.conf.in index 3f20edb..b090239 100644 --- a/conf/bnetd.conf.in +++ b/conf/bnetd.conf.in @@ -76,7 +76,7 @@ i18ndir = "${SYSCONFDIR}/i18n" issuefile = "${SYSCONFDIR}/bnissue.txt" channelfile = "${SYSCONFDIR}/channel.conf" adfile = "${SYSCONFDIR}/ad.json" -topicfile = "${SYSCONFDIR}/topics.conf" +topicfile = "${SYSCONFDIR}/topics.json" ipbanfile = "${SYSCONFDIR}/bnban.conf" mpqfile = "${SYSCONFDIR}/autoupdate.conf" logfile = "${LOCALSTATEDIR}/bnetd.log" diff --git a/conf/bnetd.conf.win32 b/conf/bnetd.conf.win32 index abd603c..4e1c9d8 100644 --- a/conf/bnetd.conf.win32 +++ b/conf/bnetd.conf.win32 @@ -62,7 +62,7 @@ i18ndir = conf\i18n issuefile = conf\bnissue.txt channelfile = conf\channel.conf adfile = conf\ad.json -topicfile = conf\topics.conf +topicfile = conf\topics.json ipbanfile = conf\bnban.conf transfile = conf\address_translation.conf mpqfile = conf\autoupdate.conf diff --git a/conf/topics.conf.in b/conf/topics.conf.in deleted file mode 100644 index 314bb84..0000000 --- a/conf/topics.conf.in +++ /dev/null @@ -1,2 +0,0 @@ -Chat This is the public chat channel. Feel free to chat... -Moderated Support Support is provided in this channel... \ No newline at end of file diff --git a/conf/topics.json.in b/conf/topics.json.in new file mode 100644 index 0000000..95a2a92 --- /dev/null +++ b/conf/topics.json.in @@ -0,0 +1,6 @@ +{ + "topics": { + "Chat": "This is the public chat channel. Feel free to chat...", + "Moderated Support": "Support is provided in this channel..." + } +} \ No newline at end of file diff --git a/src/bnetd/channel.h b/src/bnetd/channel.h index 98646bd..6e452b2 100644 --- a/src/bnetd/channel.h +++ b/src/bnetd/channel.h @@ -30,6 +30,8 @@ # include "account.h" +#include + #ifdef CHANNEL_INTERNAL_ACCESS #include @@ -163,6 +165,9 @@ namespace pvpgn extern int channel_conn_has_tmpVOICE(t_channel const * channel, t_connection * c); extern t_connection * channel_get_first(t_channel const * channel); extern t_connection * channel_get_next(void); + extern nonstd::optional channel_get_topic(const t_channel* channel); // in topic.cpp + extern bool channel_set_topic(t_channel* channel, const std::string& topic); // in topic.cpp + extern bool channel_display_topic(t_channel* channel, t_connection* conn); // in topic.cpp extern int channellist_create(void); extern int channellist_destroy(void); diff --git a/src/bnetd/command.cpp b/src/bnetd/command.cpp index 2287f3c..ca8f9d1 100644 --- a/src/bnetd/command.cpp +++ b/src/bnetd/command.cpp @@ -4918,51 +4918,48 @@ namespace pvpgn static int _handle_topic_command(t_connection * c, char const * text) { - std::vector args = split_command(text, 1); - std::string topicstr = args[1]; - - - t_channel * channel = conn_get_channel(c); + t_channel* channel = conn_get_channel(c); if (channel == nullptr) { message_send_text(c, message_type_error, c, localize(c, "This command can only be used inside a channel.")); return -1; } - - class_topic Topic; - char const * channel_name = channel_get_name(channel); - // set channel topic - if (!topicstr.empty()) + const char* channel_name = channel_get_name(channel); + if (channel_name == nullptr) { - if ((topicstr.size() + 1) > MAX_TOPIC_LEN) - { - msgtemp = localize(c, "Max topic length exceeded (max {} symbols)", MAX_TOPIC_LEN); - message_send_text(c, message_type_error, c, msgtemp); - return -1; - } + message_send_text(c, message_type_error, c, localize(c, "An error has occurred.")); + return -1; + } + + if (std::strlen(text) > std::strlen("/topic ")) + { + // set channel topic if (!(account_is_operator_or_admin(conn_get_account(c), channel_name))) { msgtemp = localize(c, "You must be at least a Channel Operator of {} to set the topic", channel_name); message_send_text(c, message_type_error, c, msgtemp); + return -1; } - bool do_save; - if (channel_get_permanent(channel)) - do_save = true; - else - do_save = false; + const char* topic = text + std::strlen("/topic "); + if ((std::strlen(topic) + 1) > MAX_TOPIC_LEN) + { + msgtemp = localize(c, "Max topic length exceeded (max {} symbols)", MAX_TOPIC_LEN); + message_send_text(c, message_type_error, c, msgtemp); - Topic.set(std::string(channel_name), std::string(topicstr), do_save); + return -1; + } + + channel_set_topic(channel, topic); } - - // display channel topic - if (Topic.display(c, std::string(channel_name)) == false) + else { - msgtemp = localize(c, "{} topic: no topic", channel_name); - message_send_text(c, message_type_info, c, msgtemp); + // get channel topic + + channel_display_topic(channel, c); } return 0; diff --git a/src/bnetd/connection.cpp b/src/bnetd/connection.cpp index 61f8887..34ce915 100644 --- a/src/bnetd/connection.cpp +++ b/src/bnetd/connection.cpp @@ -2038,8 +2038,7 @@ namespace pvpgn if (conn_is_irc_variant(c) == 0) { - class_topic Topic; - Topic.display(c, std::string(channel_get_name(c->protocol.chat.channel))); + channel_display_topic(c->protocol.chat.channel, c); } if (c->protocol.chat.channel && (channel_get_flags(c->protocol.chat.channel) & channel_flags_moderated)) diff --git a/src/bnetd/handle_irc.cpp b/src/bnetd/handle_irc.cpp index 3bd4345..b951533 100644 --- a/src/bnetd/handle_irc.cpp +++ b/src/bnetd/handle_irc.cpp @@ -24,6 +24,9 @@ #include #include #include +#include + +#include #include "compat/strcasecmp.h" #include "common/irc_protocol.h" @@ -433,21 +436,19 @@ namespace pvpgn static int _handle_list_command(t_connection * conn, int numparams, char ** params, char * text) { - std::string tmp; irc_send(conn, RPL_LISTSTART, "Channel :Users Names"); /* backward compatibility */ if (numparams == 0) { t_elem const * curr; - class_topic Topic; LIST_TRAVERSE_CONST(channellist(), curr) { t_channel const * channel = (const t_channel*)elem_get_data(curr); char const * tempname = irc_convert_channel(channel, conn); - std::string topicstr = Topic.get(channel_get_name(channel)); + nonstd::optional topic = channel_get_topic(channel); /* FIXME: AARON: only list channels like in /channels command */ - tmp = std::string(tempname) + " " + std::to_string(channel_get_length(channel)) + " :" + topicstr; + std::string tmp = fmt::format("{} {} :{}", tempname, channel_get_length(channel), topic.value_or("")); if (tmp.length() > MAX_IRC_MESSAGE_LEN) eventlog(eventlog_level_warn, __FUNCTION__, "LISTREPLY length exceeded"); @@ -459,7 +460,6 @@ namespace pvpgn { int i; char ** e; - class_topic Topic; e = irc_get_listelems(params[0]); /* FIXME: support wildcards! */ @@ -474,10 +474,10 @@ namespace pvpgn if (!channel) continue; /* channel doesn't exist */ - std::string topicstr = Topic.get(channel_get_name(channel)); + nonstd::optional topic = channel_get_topic(channel); char const * tempname = irc_convert_channel(channel, conn); - tmp = std::string(tempname) + " " + std::to_string(channel_get_length(channel)) + " :" + topicstr; + std::string tmp = fmt::format("{} {} :{}", tempname, channel_get_length(channel), topic.value_or("")); if (tmp.length() > MAX_IRC_MESSAGE_LEN) eventlog(eventlog_level_warn, __FUNCTION__, "LISTREPLY length exceeded"); diff --git a/src/bnetd/handle_wol.cpp b/src/bnetd/handle_wol.cpp index 84d4c4f..16cfb69 100644 --- a/src/bnetd/handle_wol.cpp +++ b/src/bnetd/handle_wol.cpp @@ -26,6 +26,9 @@ #include #include #include +#include + +#include #include "compat/strcasecmp.h" #include "common/irc_protocol.h" @@ -482,20 +485,12 @@ namespace pvpgn return 0; } - class_topic Topic; - topicstr = Topic.get(channel_get_name(gamechannel)); + nonstd::optional topic = channel_get_topic(gamechannel); - if (topicstr.empty() == false) { - if (std::strlen(gamename) + 1 + 20 + 1 + 1 + std::strlen(topicstr.c_str()) > MAX_IRC_MESSAGE_LEN) { - WARN0("LISTREPLY length exceeded"); - return 0; - } - } - else { - if (std::strlen(gamename) + 1 + 20 + 1 + 1 > MAX_IRC_MESSAGE_LEN) { - WARN0("LISTREPLY length exceeded"); - return 0; - } + if (std::strlen(gamename) + 1 + 20 + 1 + 1 + topic.value_or("").length() > MAX_IRC_MESSAGE_LEN) + { + WARN0("LISTREPLY length exceeded"); + return 0; } /*** @@ -538,8 +533,9 @@ namespace pvpgn std::strcat(temp, "::"); - if (topicstr.c_str()) { - std::snprintf(temp_a, sizeof(temp_a), "%s", topicstr.c_str()); /* topic */ + if (topic.has_value()) + { + std::snprintf(temp_a, sizeof(temp_a), "%s", topic.value().c_str()); /* topic */ std::strcat(temp, temp_a); } diff --git a/src/bnetd/irc.cpp b/src/bnetd/irc.cpp index f8f5783..20e23cf 100644 --- a/src/bnetd/irc.cpp +++ b/src/bnetd/irc.cpp @@ -27,8 +27,10 @@ #include #include #include +#include #include +#include #include "compat/strcasecmp.h" @@ -1740,13 +1742,12 @@ namespace pvpgn extern int irc_send_topic(t_connection* c, t_channel const* channel) { - class_topic Topic; - std::string topicstr = Topic.get(channel_get_name(channel)); + nonstd::optional topic = channel_get_topic(channel); char temp[MAX_IRC_MESSAGE_LEN]; - if (topicstr.empty() == false) + if (topic.has_value()) { - std::snprintf(temp, sizeof(temp), "%s :%s", irc_convert_channel(channel, c), topicstr.c_str()); + std::snprintf(temp, sizeof(temp), "%s :%s", irc_convert_channel(channel, c), topic.value().c_str()); irc_send(c, RPL_TOPIC, temp); } else @@ -1765,15 +1766,17 @@ namespace pvpgn { char** e = NULL; char temp[MAX_IRC_MESSAGE_LEN]; - class_topic Topic; if (params && params[0]) { if (conn_get_wol(conn) == 1) { t_channel* channel = conn_get_channel(conn); if (channel) - Topic.set(std::string(channel_get_name(channel)), std::string(text), false); - else { + { + channel_set_topic(channel, text); + } + else + { std::snprintf(temp, sizeof(temp), "%s :You're not on that channel", params[0]); irc_send(conn, ERR_NOTONCHANNEL, temp); } diff --git a/src/bnetd/main.cpp b/src/bnetd/main.cpp index 2828e43..6182dd5 100644 --- a/src/bnetd/main.cpp +++ b/src/bnetd/main.cpp @@ -420,7 +420,7 @@ int pre_server_startup(void) teamlist_load(); if (realmlist_create(prefs_get_realmfile()) < 0) eventlog(eventlog_level_error, __FUNCTION__, "could not load realm list"); - //topiclist_load(std::string(prefs_get_topicfile())); + load_topic_conf(prefs_get_topicfile()); userlog_init(); if (prefs_get_verify_account_email() == 1) { @@ -460,7 +460,7 @@ void post_server_shutdown(int status) switch (status) { case 0: - //topiclist_unload(); + unload_topic_conf(); account_email_verification_unload(); smtp_cleanup(); account_email_verification_unload(); diff --git a/src/bnetd/server.cpp b/src/bnetd/server.cpp index bacad38..23d9f43 100644 --- a/src/bnetd/server.cpp +++ b/src/bnetd/server.cpp @@ -1329,6 +1329,7 @@ namespace pvpgn clanlist_save(); gamelist_check_voidgame(); ladders.save(); + topiclist_save(); next_savetime += prefs_get_user_sync_timer(); } accountlist_save(FS_NONE); diff --git a/src/bnetd/topic.cpp b/src/bnetd/topic.cpp index 8a38ade..07df581 100644 --- a/src/bnetd/topic.cpp +++ b/src/bnetd/topic.cpp @@ -1,248 +1,283 @@ /* -* Copyright (C) 2015 xboi209 -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ #include "common/setup_before.h" #include "topic.h" +#include #include #include #include #include #include +#include #include +#include +#include "json/json.hpp" + #include "compat/strcasecmp.h" #include "common/eventlog.h" #include "common/field_sizes.h" +#include "channel.h" +#include "i18n.h" #include "message.h" #include "prefs.h" #include "common/setup_after.h" + +using nlohmann::json; +using nonstd::optional; +using nonstd::nullopt; + namespace pvpgn { namespace bnetd { - std::vector> class_topic::class_topiclist::Head; + static std::unordered_map topic_head; - bool class_topic::class_topiclist::IsHeadLoaded = false; + static bool is_topic_conf_loaded = false; + static std::string topic_conf_filename; - class_topic::class_topic() + bool load_topic_conf(const std::string& filename) { - //Already loaded, do not load again - if (this->topiclist.IsHeadLoaded == true) - return; - - std::ifstream topicfile_stream(prefs_get_topicfile()); - if (!topicfile_stream) + if (is_topic_conf_loaded) { - eventlog(eventlog_level_error, __FUNCTION__, "couldn't open topic file"); - return; + eventlog(eventlog_level_warn, __FUNCTION__, "topic conf already loaded"); + return true; } - std::string strLine; - std::smatch match; - const std::regex rgx("(.*)\t(.*)"); // tab character separates the channel name and topic + auto t0 = std::chrono::steady_clock::now(); - //loop through each line in topic file - while (std::getline(topicfile_stream, strLine)) + try { - //skip empty lines - if (strLine.empty() == true) - continue; - - if (!std::regex_search(strLine, match, rgx)) + std::ifstream topicfile_stream(filename); + if (!topicfile_stream.is_open()) { - eventlog(eventlog_level_error, __FUNCTION__, "Invalid line in topic file ({})", strLine.c_str()); - continue; - } - - //check if current line in file already exists in Head - if (this->topiclist.get(match[1].str()) != nullptr) - { - eventlog(eventlog_level_error, __FUNCTION__, "Duplicate line for channel {} in topic file", match[1].str().c_str()); - continue; - } - - //save current line to Head - this->topiclist.add(match[1].str(), match[2].str(), true); - } - - this->topiclist.IsHeadLoaded = true; - } - - //Get channel_name's topic string - std::string class_topic::get(const std::string channel_name) - { - if ((channel_name.size() + 1) > MAX_CHANNELNAME_LEN || channel_name.empty() == true) - { - eventlog(eventlog_level_error, __FUNCTION__, "got invalid channel name length"); - return std::string(); - } - - auto topic = this->topiclist.get(channel_name); - if (topic == nullptr) - return std::string(); - - return topic->topicstr; - } - - //Sets channel_name's topic - bool class_topic::set(const std::string channel_name, const std::string topic_text, bool do_save) - { - if ((channel_name.size() + 1) > MAX_CHANNELNAME_LEN || channel_name.empty() == true) - { - eventlog(eventlog_level_error, __FUNCTION__, "got invalid channel name length"); - return false; - } - - if ((topic_text.size() + 1) > MAX_TOPIC_LEN || topic_text.empty() == true) - { - eventlog(eventlog_level_error, __FUNCTION__, "got invalid topic length"); - return false; - } - - auto topic = this->topiclist.get(channel_name); - - if (topic != nullptr) - { - eventlog(eventlog_level_trace, __FUNCTION__, "Setting <{}>'s topic to <{}>", channel_name.c_str(), topic_text.c_str()); - topic->topicstr = topic_text; - } - else - { - eventlog(eventlog_level_trace, __FUNCTION__, "Adding <{}:{}> to topiclist", channel_name.c_str(), topic_text.c_str()); - this->topiclist.add(channel_name, topic_text, do_save); - } - - if (do_save == true) - { - if (this->topiclist.save() == false) - { - eventlog(eventlog_level_error, __FUNCTION__, "error saving topic list"); + eventlog(eventlog_level_error, __FUNCTION__, "couldn't open topic file \"{}\"", filename); return false; } - } - return true; - } + json jconf; + topicfile_stream >> jconf; - // Displays channel_name's topic to connection c - bool class_topic::display(t_connection * c, const std::string channel_name) - { - if ((channel_name.size() + 1) > MAX_CHANNELNAME_LEN || channel_name.empty() == true) - { - eventlog(eventlog_level_error, __FUNCTION__, "got invalid channel name length"); - return false; - } - - auto topic = this->topiclist.get(channel_name); - if (topic == nullptr) - return false; - - auto topicstr = topic->topicstr; - - if (topicstr.empty()) - { - eventlog(eventlog_level_error, __FUNCTION__, "topic is empty"); - return false; - } - - //send parts of topic string as separate message if there's a newline character - std::regex rgx("\\\\n+"); - std::sregex_token_iterator iter(topicstr.begin(), topicstr.end(), rgx, -1), end; - for (bool first = true; iter != end; ++iter) - { - std::string msg(iter->str()); - if (first == true) + for (auto& entry : jconf.at("topics").items()) { - msg.insert(0, topic->channel_name + " topic: "); - first = false; - } - message_send_text(c, message_type_info, c, msg); - } - - return true; - } - - - //Get t_topic pointer of channel_name - std::shared_ptr class_topic::class_topiclist::get(const std::string channel_name) - { - for (auto topic : this->Head) - { - if (strcasecmp(channel_name.c_str(), topic->channel_name.c_str()) == 0) - return topic; - } - eventlog(eventlog_level_debug, __FUNCTION__, "returning nullptr"); - - return nullptr; - } - - //Saves data from Head vector to topic file - bool class_topic::class_topiclist::save() - { - std::fstream topicfile_stream(prefs_get_topicfile(), std::ofstream::app); - if (!topicfile_stream) - { - eventlog(eventlog_level_error, __FUNCTION__, "couldn't open topic file"); - return false; - } - - std::string strLine; - std::smatch match; - const std::regex rgx("(.*)\t(.*)"); // tab character separates the channel name and topic - - //Check if data in Head vector already exists in topic file - while (std::getline(topicfile_stream, strLine)) - { - if (!std::regex_search(strLine, match, rgx)) - { - eventlog(eventlog_level_error, __FUNCTION__, "Invalid line in topic file ({})", strLine.c_str()); - continue; - } - - for (auto topic : this->Head) - { - if (topic->save == true) + try { - if (match[1].str() == topic->channel_name) + auto success = topic_head.emplace(entry.key(), entry.value()); + if (!success.second) { - break; - } - else - { - topicfile_stream << topic->channel_name << "\t" << topic->topicstr << std::endl; - break; + eventlog(eventlog_level_warn, __FUNCTION__, "failed to load topic for channel \"{}\" (possible duplicate?)", entry.key()); } } + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "could not load topic for channel \"{}\"", entry.key()); + continue; + } } } - return true; + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "failed to load {} ({})", filename, e.what()); + return false; + } + + auto t1 = std::chrono::steady_clock::now(); + + eventlog(eventlog_level_info, __FUNCTION__, "Successfully loaded {} channel topics in {} milliseconds", topic_head.size(), std::chrono::duration_cast(t1 - t0).count()); + + topic_conf_filename = filename; + + is_topic_conf_loaded = true; + + return is_topic_conf_loaded; } - //Adds a new pointer to the Head vector - void class_topic::class_topiclist::add(std::string channel_name, std::string topic_text, bool do_save) + void unload_topic_conf() { - auto topic = std::make_shared(class_topic::t_topic{ channel_name, topic_text, do_save }); - this->Head.push_back(std::move(topic)); + topiclist_save(); + + topic_head.clear(); + + topic_conf_filename.clear(); + + is_topic_conf_loaded = false; + + eventlog(eventlog_level_info, __FUNCTION__, "Successfully unloaded all channel topics"); + } + + void topiclist_save() + { + if (!is_topic_conf_loaded) + { + eventlog(eventlog_level_error, __FUNCTION__, "topic conf not loaded"); + return; + } + + try + { + std::ofstream topicfile_stream(topic_conf_filename); + if (!topicfile_stream.is_open()) + { + eventlog(eventlog_level_error, __FUNCTION__, "couldn't open topic file"); + return; + } + + json jconf; + jconf["topics"] = topic_head; + + topicfile_stream << jconf.dump(1, '\t'); + } + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "failed to save topic_head to {} ({})", topic_conf_filename, e.what()); + return; + } + } + + nonstd::optional channel_get_topic(const t_channel* channel) + { + if (channel == nullptr) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL channel"); + return nonstd::nullopt; + } + + const char* channelname = channel_get_name(channel); + if (channelname == nullptr) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL channel name"); + return nonstd::nullopt; + } + + try + { + auto search = topic_head.find(channelname); + if (search == topic_head.end()) + { + return nonstd::nullopt; + } + + return nonstd::optional{search->second}; + } + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "failed to get channel topic for channel \"{}\"", channelname); + + return nonstd::nullopt; + } + } + + bool channel_set_topic(t_channel* channel, const std::string& topic) + { + if (channel == nullptr) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL channel"); + return false; + } + + const char* channelname = channel_get_name(channel); + if (channelname == nullptr) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL channel name"); + return false; + } + + try + { + auto success = topic_head.emplace(channelname, topic); + if (success.second == false) + { + // topic was not inserted because a topic for the channel already exists in topic_head + + success.first->second = topic; + } + + return true; + } + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "failed to set channel topic for channel \"{}\"", channelname); + + return false; + } + } + + bool channel_display_topic(t_channel* channel, t_connection* conn) + { + if (channel == nullptr) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL channel"); + return false; + } + + if (conn == nullptr) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL conn"); + return false; + } + + try + { + nonstd::optional topic = channel_get_topic(channel); + if (topic.has_value()) + { + const std::string delimiter = "\\n"; + const std::size_t delimiter_len = delimiter.length(); + const std::string topicstr = topic.value(); + + // support "\n" (not '\n') by individually sending substrings + std::size_t old_newline_pos = 0; + std::size_t curr_newline_pos = topicstr.find(delimiter, old_newline_pos); + while (curr_newline_pos != std::string::npos) + { + if (old_newline_pos == curr_newline_pos) + { + old_newline_pos += delimiter_len; + curr_newline_pos = topicstr.find(delimiter, old_newline_pos); + continue; + } + + std::string substr = topicstr.substr(old_newline_pos, curr_newline_pos - old_newline_pos); + message_send_text(conn, message_type_info, conn, substr); + + old_newline_pos = curr_newline_pos + delimiter_len; + curr_newline_pos = topicstr.find(delimiter, old_newline_pos); + } + + message_send_text(conn, message_type_info, conn, topicstr.substr(old_newline_pos, std::string::npos)); + } + + return true; + } + catch (const std::exception& e) + { + message_send_text(conn, message_type_error, conn, localize(conn, "An error has occurred.")); + + const char* channelname = channel_get_name(channel); + eventlog(eventlog_level_error, __FUNCTION__, "failed to display channel topic for channel \"{}\"", channelname ? channelname : "?"); + + return false; + } } } diff --git a/src/bnetd/topic.h b/src/bnetd/topic.h index e9226dc..bccea72 100644 --- a/src/bnetd/topic.h +++ b/src/bnetd/topic.h @@ -1,72 +1,37 @@ /* -* Copyright (C) 2015 xboi209 -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ -#ifndef INCLUDED_TOPIC_TYPES -#define INCLUDED_TOPIC_TYPES +#ifndef INCLUDED_TOPIC_H +#define INCLUDED_TOPIC_H -#include #include -#include -#include "connection.h" -#include "common/list.h" -#endif - -#ifndef JUST_NEED_TYPES -#ifndef INCLUDED_TOPIC_PROTOS -#define INCLUDED_TOPIC_PROTOS namespace pvpgn { namespace bnetd { - class class_topic - { - //all public member functions of class_topic verify channel name and topic length - public: - class_topic(); + bool load_topic_conf(const std::string& filename); + void unload_topic_conf(); + void topiclist_save(); - std::string get(const std::string channel_name); - bool set(const std::string channel_name, const std::string topic_text, bool do_save); - bool display(t_connection * c, const std::string channel_name); - private: - struct t_topic - { - std::string channel_name; - std::string topicstr; - bool save; - }; + } - class class_topiclist - { - public: - static bool IsHeadLoaded; - std::shared_ptr get(const std::string channel_name); - bool save(); - void add(std::string channel_name, std::string topic_text, bool do_save); - private: - static std::vector> Head; - } topiclist; - }; +} - } //namespace bnetd -} //namespace pvpgn - -#endif #endif \ No newline at end of file