From 4d3b9d5db80e16dc6c13e321689ff64c87b65cb0 Mon Sep 17 00:00:00 2001 From: JAJames Date: Sat, 2 Jul 2016 13:45:13 -0400 Subject: [PATCH] IRC connections are now initialized by IRC.Core --- Bot/IRC_Bot.cpp | 21 +++--- Bot/IRC_Bot.h | 4 +- Bot/IRC_Command.cpp | 12 +--- Bot/Main.cpp | 32 ++------- Bot/ServerManager.cpp | 6 +- Bot/ServerManager.h | 19 ++++- Config.ini | 129 ++-------------------------------- Configs/IRC.Core.ini | 121 +++++++++++++++++++++++++++++++ IRC.Core/IRC_Core.cpp | 29 ++++++++ IRC.Core/IRC_Core.h | 17 +++++ Jupiter | 2 +- Release/Bot.lib | Bin 23442 -> 24276 bytes Release/Plugins/RenX.Core.lib | Bin 206580 -> 206580 bytes 13 files changed, 216 insertions(+), 176 deletions(-) create mode 100644 Configs/IRC.Core.ini diff --git a/Bot/IRC_Bot.cpp b/Bot/IRC_Bot.cpp index b776b7a..d7a923e 100644 --- a/Bot/IRC_Bot.cpp +++ b/Bot/IRC_Bot.cpp @@ -30,8 +30,10 @@ using namespace Jupiter::literals; IRC_Bot::IRC_Bot(const Jupiter::INIFile::Section *in_primary_section, const Jupiter::INIFile::Section *in_secondary_section) : Client(in_primary_section, in_secondary_section) { IRC_Bot::commandPrefix = this->readConfigValue("Prefix"_jrs); + for (size_t i = 0; i != IRCMasterCommandList->size(); i++) - IRC_Bot::addCommand(IRCMasterCommandList->get(i)->copy()); + IRC_Bot::commands.add(IRCMasterCommandList->get(i)->copy()); + IRC_Bot::setCommandAccessLevels(); } @@ -45,9 +47,10 @@ IRC_Bot::~IRC_Bot() IRC_Bot::commands.emptyAndDelete(); } -void IRC_Bot::addCommand(IRCCommand *cmd) +void IRC_Bot::addCommand(IRCCommand *in_command) { - IRC_Bot::commands.add(cmd); + IRC_Bot::commands.add(in_command); + IRC_Bot::setCommandAccessLevels(in_command); } bool IRC_Bot::freeCommand(const Jupiter::ReadableString &trigger) @@ -101,11 +104,11 @@ Jupiter::StringL IRC_Bot::getTriggers(Jupiter::ArrayList &cmds) return r; } -void IRC_Bot::setCommandAccessLevels() +void IRC_Bot::setCommandAccessLevels(IRCCommand *in_command) { - auto set_command_access_levels = [this](const Jupiter::ReadableString §ion_name) + auto set_command_access_levels = [this, in_command](const Jupiter::ReadableString §ion_name) { - Jupiter::INIFile::Section *section = g_config->getSection(section_name); + Jupiter::INIFile::Section *section = serverManager->getConfig().getSection(section_name); if (section != nullptr) { @@ -134,7 +137,7 @@ void IRC_Bot::setCommandAccessLevels() tmp_sub_key.shiftRight(5); // shift beyond "Type." command = this->getCommand(tmp_key); - if (command != nullptr) + if (command != nullptr && (in_command == nullptr || in_command == command)) command->setAccessLevel(tmp_sub_key.asInt(), pair->getValue().asInt()); } else if (tmp_sub_key.findi("Channel."_jrs) == 0) @@ -143,7 +146,7 @@ void IRC_Bot::setCommandAccessLevels() // Assign access level to command (if command exists) command = this->getCommand(tmp_key); - if (command != nullptr) + if (command != nullptr && (in_command == nullptr || in_command == command)) command->setAccessLevel(tmp_sub_key, pair->getValue().asInt()); } } @@ -151,7 +154,7 @@ void IRC_Bot::setCommandAccessLevels() { // Assign access level to command (if command exists) command = this->getCommand(pair->getKey()); - if (command != nullptr) + if (command != nullptr && (in_command == nullptr || in_command == command)) command->setAccessLevel(pair->getValue().asInt()); } } diff --git a/Bot/IRC_Bot.h b/Bot/IRC_Bot.h index 1842a7f..04d8c31 100644 --- a/Bot/IRC_Bot.h +++ b/Bot/IRC_Bot.h @@ -49,12 +49,12 @@ public: /** * @brief Pulls the configured access levels from the config and applies them. */ - void setCommandAccessLevels(); + void setCommandAccessLevels(IRCCommand *in_command = nullptr); /** * @brief Adds a command to the command list. */ - void addCommand(IRCCommand *cmd); + void addCommand(IRCCommand *in_command); /** * @brief Removes a command from the command list. diff --git a/Bot/IRC_Command.cpp b/Bot/IRC_Command.cpp index bf76546..e84ec96 100644 --- a/Bot/IRC_Command.cpp +++ b/Bot/IRC_Command.cpp @@ -135,12 +135,8 @@ GenericCommandWrapperIRCCommand::GenericCommandWrapperIRCCommand(GenericCommandW GenericCommandWrapperIRCCommand::m_command = in_command.m_command; // Copy triggers - size_t index = 0; - while (index != GenericCommandWrapperIRCCommand::m_command->getTriggerCount()) - { + for (size_t index = 0; index != GenericCommandWrapperIRCCommand::m_command->getTriggerCount(); ++index) this->addTrigger(GenericCommandWrapperIRCCommand::m_command->getTrigger(index)); - ++index; - } } GenericCommandWrapperIRCCommand::GenericCommandWrapperIRCCommand(Jupiter::GenericCommand &in_command) : IRCCommand() @@ -148,12 +144,8 @@ GenericCommandWrapperIRCCommand::GenericCommandWrapperIRCCommand(Jupiter::Generi GenericCommandWrapperIRCCommand::m_command = &in_command; // Copy triggers - size_t index = 0; - while (index != GenericCommandWrapperIRCCommand::m_command->getTriggerCount()) - { + for (size_t index = 0; index != GenericCommandWrapperIRCCommand::m_command->getTriggerCount(); ++index) this->addTrigger(GenericCommandWrapperIRCCommand::m_command->getTrigger(index)); - ++index; - } if (serverManager != nullptr) serverManager->addCommand(this); diff --git a/Bot/Main.cpp b/Bot/Main.cpp index 725538a..ab38a92 100644 --- a/Bot/Main.cpp +++ b/Bot/Main.cpp @@ -29,7 +29,6 @@ #include "Jupiter/Plugin.h" #include "Jupiter/Timer.h" #include "IRC_Bot.h" -#include "ServerManager.h" #include "Console_Command.h" #include "IRC_Command.h" @@ -87,19 +86,13 @@ void inputLoop() } } -int rehash_config() -{ - o_config.reload(); - serverManager->OnConfigRehash(); - return 0; -} - int main(int argc, const char **args) { atexit(onExit); std::set_terminate(onTerminate); std::thread inputThread(inputLoop); Jupiter::ReferenceString command, plugins_directory, configs_directory; + size_t index; srand(static_cast(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count())); puts(Jupiter::copyright); @@ -131,8 +124,6 @@ int main(int argc, const char **args) exit(0); } - Jupiter::addOnRehash(rehash_config); - puts("Config loaded."); if (plugins_directory.isEmpty()) @@ -159,8 +150,10 @@ int main(int argc, const char **args) puts("No plugins to load!"); else { + // initialize plugins unsigned int nPlugins = pluginList.wordCount(WHITESPACE); printf("Attempting to load %u plugins..." ENDL, nPlugins); + for (unsigned int i = 0; i < nPlugins; i++) { Jupiter::ReferenceString plugin = Jupiter::ReferenceString::getWord(pluginList, i, WHITESPACE); @@ -168,6 +161,10 @@ int main(int argc, const char **args) fprintf(stderr, "WARNING: Failed to load plugin \"%.*s\"!" ENDL, plugin.size(), plugin.ptr()); else printf("\"%.*s\" loaded successfully." ENDL, plugin.size(), plugin.ptr()); } + + // OnPostInitialize + for (index = 0; index != Jupiter::plugins->size(); ++index) + Jupiter::plugins->get(index)->OnPostInitialize(); } if (consoleCommands->size() > 0) @@ -175,20 +172,6 @@ int main(int argc, const char **args) if (IRCMasterCommandList->size() > 0) printf("%u IRC Commands have been loaded into the master list." ENDL, IRCMasterCommandList->size()); - puts("Retreiving network list..."); - const Jupiter::ReadableString &serverList = o_config.get(Jupiter::ReferenceString::empty, "Servers"_jrs); - if (serverList == nullptr) - puts("Unable to find network list."); - else - { - unsigned int nServers = serverList.wordCount(WHITESPACE); - printf("Attempting to connect to %u servers..." ENDL, nServers); - for (unsigned int index = 0; index < nServers; ++index) - serverManager->addServer(Jupiter::ReferenceString::getWord(serverList, index, WHITESPACE)); - } - - puts("Sockets established."); - size_t index; while (1) { index = 0; @@ -198,7 +181,6 @@ int main(int argc, const char **args) else ++index; Jupiter_checkTimers(); - serverManager->think(); if (console_input.input_mutex.try_lock()) { diff --git a/Bot/ServerManager.cpp b/Bot/ServerManager.cpp index 1db4775..f62e042 100644 --- a/Bot/ServerManager.cpp +++ b/Bot/ServerManager.cpp @@ -75,8 +75,8 @@ void ServerManager::OnConfigRehash() { server = ServerManager::servers.get(index); - server->setPrimaryConfigSection(g_config->getSection(server->getConfigSection())); - server->setSecondaryConfigSection(g_config->getSection("Defualt"_jrs)); + server->setPrimaryConfigSection(m_config->getSection(server->getConfigSection())); + server->setSecondaryConfigSection(m_config->getSection("Defualt"_jrs)); server->setCommandAccessLevels(); } } @@ -109,7 +109,7 @@ IRC_Bot *ServerManager::getServer(size_t serverIndex) bool ServerManager::addServer(const Jupiter::ReadableString &serverConfig) { - IRC_Bot *server = new IRC_Bot(g_config->getSection(serverConfig), g_config->getSection("Default"_jrs)); + IRC_Bot *server = new IRC_Bot(m_config->getSection(serverConfig), m_config->getSection("Default"_jrs)); if (server->connect()) { ServerManager::servers.add(server); diff --git a/Bot/ServerManager.h b/Bot/ServerManager.h index feb63fc..414688e 100644 --- a/Bot/ServerManager.h +++ b/Bot/ServerManager.h @@ -77,7 +77,7 @@ public: size_t removeCommand(const Jupiter::ReadableString &command); /** - * @brief Called when g_config is rehashed + * @brief Called when m_config is rehashed */ void OnConfigRehash(); @@ -142,6 +142,20 @@ public: */ size_t size(); + /** + * @brief Fetches the configuration file being used + * + * @return Configuration file being used + */ + inline Jupiter::INIFile &getConfig() const { return *this->m_config; }; + + /** + * @brief Sets the configuration file to use + * + * @param Reference to the config file to use + */ + inline void setConfig(Jupiter::INIFile &in_config) { this->m_config = &in_config; }; + /** * Destructor for the ServerManager class. */ @@ -150,6 +164,9 @@ public: private: /** Underlying ArrayList of servers */ Jupiter::ArrayList servers; + + /** Config to read data from */ + Jupiter::INIFile *m_config = g_config; }; /** Pointer to an instance of the server manager. Note: DO NOT DELETE OR FREE THIS POINTER. */ diff --git a/Config.ini b/Config.ini index 4bc1471..3598451 100644 --- a/Config.ini +++ b/Config.ini @@ -15,132 +15,11 @@ ; DO NOT INCLUDE A PLUGIN'S FILE EXTENSION (.dll or .so). ; ; Settings: -; Plugins=String (Format: Plugin1 Plugin2) -; Servers=String (Format: Server1 Server2) -; PluginsDirectory=String (Default: Plugins\) -; ConfigsDirectory=String (Default: Configs\) +; Plugins=String (Format: Plugin1 Plugin2 ...); list of plugins to load +; PluginsDirectory=String (Default: Plugins\); directory where plugin binaries are +; ConfigsDirectory=String (Default: Configs\); directory where plugin configs are ; -Plugins=CoreCommands PluginManager ExtraCommands RenX.Core RenX.Commands RenX.Logging RenX.Medals -Servers=CnCIRC - -; [Default] -; -; This block is referenced only when a setting is not -; specified in a server's block. If a value is located -; neither in the server's block nor this one, then a -; crash or other unexpected behavior may occur. It is -; therefore recommended to have some sort of default -; value for all possible settings. -; -; Channel Configuring: -; Channels are given "types", which can determine various things such -; as auto-parting. A Channel of type -1 will automatically part itself. -; Channels can be configured in two ways: Active and Passive. -; Active channels are joined automatically. Passive channels are not, -; but can still be configured. -; -; Active Channels: -; Channel.Integer=String (Format: #Example [Password]) -; Channel.Integer.Type=Integer (-1 is auto-part) -; -; Passive Channels: -; Channel.String.Type=Integer -; -; Client address and port: -; The client address and port settings are entirely optional. These are -; only used under very special circumstances, and should otherwise -; not be specified. Unless you have a valid reason to need to bind the -; client to a specific address/port (such as connection limiations on a -; per-IP basis), do not set these. -; -; Settings: -; Hostname=String ("irc.cncirc.net" if unspecified) -; Port=Positive Integer (194 or 994 if unspecified) -; SSL=Bool -; STARTTLS=Bool (True if unspecified) -; Certificate=String (No SSL certificate used if unspecified) -; Key=String (Certificate file used if unspecified) -; SASL.Password=String (SASL disabled if unspecified) -; SASL.Account=String (Nickname if unspecified) -; Nick=String ("Jupiter" if unspecified) -; AltNick=String -; RealName=String ("Jupiter IRC Client" if unspecified) -; Channel.Type=Integer -; RawData.Integer=String (Example: OPER UserName Password) -; LogFile=String -; AutoJoinOnKick=Bool -; AutoPartMessage=String -; AutoReconnectDelay=Integer (Measured in seconds) -; MaxReconnectAttempts=Integer -; PrintOutput=Bool -; Prefix=String -; ClientAddress=String (Unused if unspecified) -; ClientPort=Integer (Unused if above is unspecified; defaults to 0) -; - -[Default] -Port=6667 -Nick=Jupiter -AltNick=Jupiter` -RealName=Jupiter IRC Framework by Agent -AutoPartMessage=Auto-Parting Enabled -AutoReconnect=1 -MaxReconnectAttempts=3 -AutoReconnectDelay=5 -PrintOutput=1 -Channel.Type=-1 -Prefix=! - -; [(ServerName)] -; -; Anything which can be set within the Default block can -; also be applied here. Values here supercede any value -; which is set within the Default block. -; - -[CnCIRC] -; CnCIRC includes the Renegade X IRC server. :) -Hostname=irc.cncirc.net -Nick=RenXBot -AltNick=RXBot` -Channel.1=#RenX-IRC -Channel.1.Type=1 -Channel.2=#RenX-IRC.Admin -Channel.2.Type=2 -SASL.Password=your_NickServ_Password -LogFile=CnCIRC.txt - -[CT] -Hostname=irc.ConstructiveTyranny.com -Nick=RenXBot -Channel.1=#RenX-IRC -Channel.1.Type=1 -Channel.2=#RenX-IRC.Admin -Channel.2.Type=2 -RawData.1=PRIVMSG NickServ :IDENTIFY your_NickServ_Password -LogFile=CT.txt - -; [DefaultCommands] -; You can modify the access requirements for any command here. -; Values set here will be set across all servers that do not have -; server-specific values set. -; -; To disable a command, set its access requirement to -1. -; -; Syntax: CommandTrigger=AccessLevel -; - -[DefaultCommands] -msg=1 - -; [(ServerName)Commands] -; You can modify the access requirements for any command here, on a -; per-server basis. Values specified here supercede those which are set -; in the DefaultCommands block. -; - -[CnCIRCCommands] -msg=0 +Plugins=IRC.Core CoreCommands PluginManager ExtraCommands RenX.Core RenX.Commands RenX.Logging RenX.Medals ;EOF \ No newline at end of file diff --git a/Configs/IRC.Core.ini b/Configs/IRC.Core.ini new file mode 100644 index 0000000..7edfdb1 --- /dev/null +++ b/Configs/IRC.Core.ini @@ -0,0 +1,121 @@ +; File: IRC.Core.ini +; + +; Servers=String (Format: Server1 Server2 ...) +; Lists server sections to instantiate IRC connections with +Servers=CnCIRC + +; [Default] +; +; This block is referenced only when a setting is not +; specified in a server's block. If a value is located +; neither in the server's block nor this one, then a +; crash or other unexpected behavior may occur. It is +; therefore recommended to have some sort of default +; value for all possible settings. +; +; Channel Configuring: +; Channels are given "types", which can determine various things such +; as auto-parting. A Channel of type -1 will automatically part itself. +; Channels can be configured in two ways: Active and Passive. +; Active channels are joined automatically. Passive channels are not, +; but can still be configured. +; +; Active Channels: +; Channel.Integer=String (Format: #Example [Password]) +; Channel.Integer.Type=Integer (-1 is auto-part) +; +; Passive Channels: +; Channel.String.Type=Integer +; +; Client address and port: +; The client address and port settings are entirely optional. These are +; only used under very special circumstances, and should otherwise +; not be specified. Unless you have a valid reason to need to bind the +; client to a specific address/port (such as connection limiations on a +; per-IP basis), do not set these. +; +; Settings: +; Hostname=String ("irc.cncirc.net" if unspecified) +; Port=Positive Integer (194 or 994 if unspecified) +; SSL=Bool (Default: false) +; STARTTLS=Bool (Default: true) +; Certificate=String (No SSL certificate used if unspecified) +; Key=String (Certificate file used if unspecified) +; SASL.Password=String (SASL disabled if unspecified) +; SASL.Account=String (Nickname if unspecified) +; Nick=String (Default: Jupiter) +; AltNick=String (Unused if unspecified) +; RealName=String ("Jupiter IRC Client" if unspecified) +; Channel.Type=Integer (Default: 0) +; RawData.Integer=String (Example: OPER UserName Password) +; LogFile=String (Default: ) +; AutoJoinOnKick=Bool (Default: false) +; AutoPartMessage=String (Default: ) +; AutoReconnectDelay=Integer (Default: 0; Measured in seconds) +; MaxReconnectAttempts=Integer (Default: 0; Set to -1 for unlimited) +; PrintOutput=Bool (Default: false) +; Prefix=String (Unused if unspecified) +; ClientAddress=String (Unused if unspecified) +; ClientPort=Integer (Unused if above is unspecified; defaults to 0) +; + +[Default] +Port=6667 +Nick=RenXBot +AltNick=RenXBot` +RealName=Jupiter IRC Framework by Agent +AutoPartMessage=Auto-Parting Enabled +AutoReconnect=1 +MaxReconnectAttempts=3 +AutoReconnectDelay=5 +PrintOutput=1 +Channel.Type=-1 +Prefix=! + +; [(ServerName)] +; +; Anything which can be set within the Default block can +; also be applied here. Values here supercede any value +; which is set within the Default block. +; + +[CnCIRC] +; CnCIRC includes the Renegade X IRC server. :) +Hostname=irc.cncirc.net +Channel.1=#RenX-IRC +Channel.1.Type=1 +Channel.2=#RenX-IRC.Admin +Channel.2.Type=2 +SASL.Password=your_NickServ_Password + +[CT] +Hostname=irc.ConstructiveTyranny.com +Channel.1=#RenX-IRC +Channel.1.Type=1 +Channel.2=#RenX-IRC.Admin +Channel.2.Type=2 +RawData.1=PRIVMSG NickServ :IDENTIFY your_NickServ_Password + +; [DefaultCommands] +; You can modify the access requirements for any command here. +; Values set here will be set across all servers that do not have +; server-specific values set. +; +; To disable a command, set its access requirement to -1. +; +; Syntax: CommandTrigger=AccessLevel +; + +[DefaultCommands] +msg=0 + +; [(ServerName)Commands] +; You can modify the access requirements for any command here, on a +; per-server basis. Values specified here supercede those which are set +; in the DefaultCommands block. +; + +[CnCIRCCommands] + +;EOF \ No newline at end of file diff --git a/IRC.Core/IRC_Core.cpp b/IRC.Core/IRC_Core.cpp index ed12728..9307e56 100644 --- a/IRC.Core/IRC_Core.cpp +++ b/IRC.Core/IRC_Core.cpp @@ -21,11 +21,40 @@ #include "IRC_Command.h" #include "IRC_Core.h" +using namespace Jupiter::literals; + IRCCorePlugin::~IRCCorePlugin() { IRCCorePlugin::m_wrapped_commands.emptyAndDelete(); } +bool IRCCorePlugin::initialize() +{ + const Jupiter::ReadableString &serverList = this->config.get(Jupiter::ReferenceString::empty, "Servers"_jrs); + if (serverList != nullptr) + { + serverManager->setConfig(this->config); + + unsigned int server_count = serverList.wordCount(WHITESPACE); + for (unsigned int index = 0; index != server_count; ++index) + serverManager->addServer(Jupiter::ReferenceString::getWord(serverList, index, WHITESPACE)); + } + + return true; +} + +int IRCCorePlugin::OnRehash() +{ + serverManager->OnConfigRehash(); + return 0; +} + +int IRCCorePlugin::think() +{ + serverManager->think(); + return 0; +} + void IRCCorePlugin::OnGenericCommandAdd(Jupiter::GenericCommand &in_command) { IRCCorePlugin::m_wrapped_commands.add(new GenericCommandWrapperIRCCommand(in_command)); diff --git a/IRC.Core/IRC_Core.h b/IRC.Core/IRC_Core.h index 7dede25..935c725 100644 --- a/IRC.Core/IRC_Core.h +++ b/IRC.Core/IRC_Core.h @@ -25,6 +25,23 @@ class IRCCorePlugin : public Jupiter::Plugin { public: + /** + * @brief Initializes the plugin + */ + virtual bool initialize() override; + + /** + * @brief Called when there is a rehash + * + * @return 0 always. + */ + virtual int OnRehash() override; + + /** + * @brief Cycles through IRC servers for new data + */ + virtual int think() override; + /** * @brief This is called when a GenericCommand is instantiated. */ diff --git a/Jupiter b/Jupiter index 5966674..c378c17 160000 --- a/Jupiter +++ b/Jupiter @@ -1 +1 @@ -Subproject commit 5966674d67b66f380b73ba3028d6176c0d3af2cb +Subproject commit c378c178809fc77ad922298c0e9bb33d1fa55cb0 diff --git a/Release/Bot.lib b/Release/Bot.lib index 1218a4e84a644b3c75b2a4f63c9c45305f82631a..4e7876bbd4cff96ad7305c8f4d9e887f28119f51 100644 GIT binary patch delta 3559 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