diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c1678a..44e59d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ add_executable(${EXEC_NAME} main.cpp jay.cpp) add_subdirectory(component) add_subdirectory(connection) add_subdirectory(module) +add_subdirectory(shared) target_include_directories(${EXEC_NAME} PRIVATE ${GLOOX_INCLUDE_DIRS}) target_include_directories(${EXEC_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/component/config.cpp b/component/config.cpp index a729912..d3838e5 100644 --- a/component/config.cpp +++ b/component/config.cpp @@ -85,7 +85,7 @@ Config::Module Config::getModuleConfig(const std::string& name) const { YAML::Node prm = conf["permissions"]; if (prm.IsMap()) { for (const auto& node : prm) { - Module::List& list = result.permissions.emplace(node.first.as(), Module::List()).first->second; + Shared::Strings& list = result.permissions.emplace(node.first.as(), Shared::Strings()).first->second; YAML::Node lst = node.second; if (lst.IsSequence()) for (const YAML::Node& member : lst) @@ -93,6 +93,25 @@ Config::Module Config::getModuleConfig(const std::string& name) const { } } - return result; } + +Shared::Responses Config::getResponses() const { + Shared::Responses responses; + + YAML::Node replies = root["replies"]; + if (!replies || !replies.IsMap()) + return responses; + + for (const std::pair& entry : Shared::results) { + YAML::Node options = replies[entry.second]; + if (!options.IsSequence()) + continue; + + Shared::Strings& out = responses.emplace(entry.first, Shared::Strings()).first->second; + for (const YAML::Node& option : options) + out.emplace_back(option.as()); + } + + return responses; +} diff --git a/component/config.h b/component/config.h index 0aae50d..9169159 100644 --- a/component/config.h +++ b/component/config.h @@ -10,17 +10,16 @@ #include "yaml-cpp/yaml.h" +#include "shared/definitions.h" +#include "shared/result.h" #include "logger.h" class Config { public: struct Module { - typedef std::vector List; - typedef std::map Permissions; - bool enabled; std::string alias; - Permissions permissions; + Shared::Permissions permissions; }; public: @@ -36,6 +35,7 @@ public: Logger::Level getLogLevel() const; gloox::TLSPolicy getTLSPolicy() const; Module getModuleConfig(const std::string& name) const; + Shared::Responses getResponses() const; private: YAML::Node root; diff --git a/component/core.cpp b/component/core.cpp index 0b40d79..ad89f2d 100644 --- a/component/core.cpp +++ b/component/core.cpp @@ -3,8 +3,51 @@ #include "core.h" +#include "connection/connection.h" + Core::Core(const std::string& configPath): config(configPath), - router(), - logger(config.getLogLevel()) + logger(config.getLogLevel()), + router(logger), + initialized(false) {} + +void Core::send(const std::string& jid, const std::string& body) { + std::shared_ptr cn = connection.lock(); + if (!cn) { + logger.log(Logger::warning, "Couldn't send a message to " + jid + ", connection is not available", {"Core"}); + return; + } + + cn->send(jid, body); +} + +void Core::initialize(const std::shared_ptr& cn) { + connection = cn; + router.setConnection(cn); + + if (initialized) + return; + + initializeActors(); + initializeResponses(); + + initialized = true; +} + +void Core::setGroup(const std::string& jid, const std::string& group) { + router.registerActor(jid, group); + // todo save config +} + +void Core::initializeActors() { + for (const std::pair& pair : config.getActors()) { + logger.log(Logger::info, "registering actor " + pair.first + " as " + pair.second, {"Core"}); + router.registerActor(pair.first, pair.second); + } +} + +void Core::initializeResponses() { + for (const std::pair& pair : config.getResponses()) + router.setResponses(pair.first, pair.second); +} diff --git a/component/core.h b/component/core.h index f8c08f5..6a63a66 100644 --- a/component/core.h +++ b/component/core.h @@ -4,17 +4,33 @@ #pragma once #include +#include #include "config.h" #include "router.h" #include "logger.h" +class Connection; + class Core { public: Core(const std::string& configPath); + void send(const std::string& jid, const std::string& body); + void initialize(const std::shared_ptr& connection); + void setGroup(const std::string& jid, const std::string& group); + +public: Config config; - Router router; Logger logger; + Router router; + +private: + void initializeActors(); + void initializeResponses(); + +private: + bool initialized; + std::weak_ptr connection; }; diff --git a/component/logger.cpp b/component/logger.cpp index 8ab3548..0e7f10b 100644 --- a/component/logger.cpp +++ b/component/logger.cpp @@ -129,7 +129,7 @@ void Logger::handleLog(gloox::LogLevel level, gloox::LogArea area, const std::st std::cout << clear << '\t' << message << clear << std::endl; } -void Logger::log(Level lvl, const std::string& message, const std::vector& domain) { +void Logger::log(Level lvl, const std::string& message, const std::vector& domain) const { if (lvl < level) return; diff --git a/component/logger.h b/component/logger.h index d14b473..6059745 100644 --- a/component/logger.h +++ b/component/logger.h @@ -25,7 +25,7 @@ public: void handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message) override; - void log(Level level, const std::string& message, const std::vector& domain = {}); + void log(Level level, const std::string& message, const std::vector& domain = {}) const; static gloox::LogLevel convert(Level level); static Level convert(gloox::LogLevel level); diff --git a/component/router.cpp b/component/router.cpp index 8789e84..b6314a9 100644 --- a/component/router.cpp +++ b/component/router.cpp @@ -4,10 +4,19 @@ #include "router.h" #include "module/module.h" +#include "connection/connection.h" -Router::Router(): +Router::Router(const Logger& logger): + logger(logger), + modules(), actors(), - defaultGroup("Stranger") + defaultGroup("Stranger"), + responses({ + {Shared::forbidden, { "Forbidden" }}, + {Shared::error, { "Command error" }}, + {Shared::unhandled, { "No such command" }}, + }), + generator(std::random_device{}()) {} void Router::registerModule(const std::string& key, const std::shared_ptr& module) { @@ -34,15 +43,29 @@ void Router::routeMessage(const std::string& sender, const std::string& body) { std::vector args = Module::Module::split(body); Modules::iterator mItr = modules.find(args[0]); - if (mItr == modules.end()) - return; + if (mItr == modules.end()) { + logger.log(Logger::debug, "could not find module \"" + args[0] + "\" to handle message from " + sender, {"Router"}); + return onMessageResult(Shared::unhandled, sender); + } std::shared_ptr module = mItr->second.lock(); - if (!module) - return; + if (!module) { + logger.log(Logger::error, "could not lock module \"" + mItr->first + "\" to handle message from " + sender, {"Router"}); + return onMessageResult(Shared::error, sender); + } args.erase(args.begin()); - module->message(aItr->second, args); + Shared::Result result; + try { + result = module->message(aItr->second, args); + if (result == Shared::success) + logger.log(Logger::debug, "module \"" + mItr->first + "\" successfully handled message from " + sender, {"Router"}); + } catch (...) { + logger.log(Logger::error, "module \"" + mItr->first + "\" thew an unhandled exception handling message from " + sender, {"Router"}); + result = Shared::error; + } + + onMessageResult(result, sender); } std::map Router::getActors() const { @@ -54,4 +77,35 @@ std::map Router::getActors() const { return result; } +void Router::setConnection(const std::shared_ptr& cn) { + connection = cn; +} + +void Router::setResponses(Shared::Result result, const Shared::Strings& options) { + responses[result] = options; +} + +std::string Router::getGroup(const std::string& key) const { + Actors::const_iterator itr = actors.find(key); + if (itr == actors.end()) + return defaultGroup; + + return itr->second->getGroup(); +} + +void Router::onMessageResult(Shared::Result result, const std::string& sender) { + Responses::const_iterator rItr = responses.find(result); + if (rItr == responses.end() || rItr->second.empty()) + return; + + std::shared_ptr cn = connection.lock(); + if (!cn) { + logger.log(Logger::warning, "Couldn't send a message to " + sender + ", connection is not available", {"Router"}); + return; + } + + const List& options = rItr->second; + std::uniform_int_distribution dist(0, options.size() - 1); + cn->send(sender, options[dist(generator)]); +} diff --git a/component/router.h b/component/router.h index 3655551..4d9caeb 100644 --- a/component/router.h +++ b/component/router.h @@ -7,16 +7,21 @@ #include #include #include +#include +#include "shared/result.h" #include "actor.h" +#include "logger.h" namespace Module { class Module; }; +class Connection; + class Router { public: - Router(); + Router(const Logger& logger); void registerModule(const std::string& key, const std::shared_ptr& module); void registerActor(const std::string& key, const std::string& group); @@ -24,11 +29,24 @@ public: std::map getActors() const; + void setConnection(const std::shared_ptr& connection); + void setResponses(Shared::Result result, const Shared::Strings& options); + std::string getGroup(const std::string& key) const; + +private: + void onMessageResult(Shared::Result result, const std::string& sender); + private: typedef std::map> Modules; typedef std::map> Actors; + typedef std::vector List; + typedef std::map Responses; + std::weak_ptr connection; + const Logger& logger; Modules modules; Actors actors; std::string defaultGroup; + Responses responses; + std::mt19937 generator; }; diff --git a/connection/connection.cpp b/connection/connection.cpp index f704b22..ce78083 100644 --- a/connection/connection.cpp +++ b/connection/connection.cpp @@ -13,7 +13,7 @@ Connection::~Connection() noexcept { deinitialize(); } -void Connection::initiialize() { +void Connection::initialize() { if (state != initial) return; @@ -65,9 +65,16 @@ void Connection::send(const std::string& jid, const std::string& body) { } void Connection::handleMessage(const gloox::Message& message, gloox::MessageSession* session) { + if (message.subtype() != gloox::Message::Chat) + return; + + std::string body = message.body(); + if (body.empty()) + return; + std::string jid = message.from().bare(); - core->logger.log(Logger::debug, "received message \"" + message.body() + "\" from " + jid, {"Connection"}); - core->router.routeMessage(jid, message.body()); + core->logger.log(Logger::debug, "received message \"" + body + "\" from " + jid, {"Connection"}); + core->router.routeMessage(jid, body); } void Connection::onConnect() { diff --git a/connection/connection.h b/connection/connection.h index e933387..910635c 100644 --- a/connection/connection.h +++ b/connection/connection.h @@ -28,7 +28,7 @@ public: Connection(const std::shared_ptr& core); ~Connection() noexcept; - void initiialize(); + void initialize(); void deinitialize(); void connect(); void send(const std::string& jid, const std::string& body); diff --git a/example.config.yml b/example.config.yml index ffe7d50..f63d888 100644 --- a/example.config.yml +++ b/example.config.yml @@ -17,3 +17,9 @@ modules: permissions: read: [Owner, User] write: [Owner] + +replies: + success: [] + forbidden: [Forbidden] + error: [Command error] + unhandled: [No such command] diff --git a/jay.cpp b/jay.cpp index b3edb99..ade530d 100644 --- a/jay.cpp +++ b/jay.cpp @@ -10,16 +10,14 @@ static const std::map< std::function< std::shared_ptr( const std::shared_ptr&, - const std::shared_ptr&, - const Module::Module::Permissions& permissions + const Shared::Permissions& permissions ) > > moduleNames = { {"actor", []( const std::shared_ptr& core, - const std::shared_ptr& connection, - const Module::Module::Permissions& permissions - ) { return std::make_shared(core, connection, permissions); }} + const Shared::Permissions& permissions + ) { return std::make_shared(core, permissions); }} }; Jay::Jay(const std::string& configPath): @@ -42,16 +40,9 @@ void Jay::run() { } void Jay::initialize() { - connection->initiialize(); createModules(); - createActors(); -} - -void Jay::createActors() { - for (const std::pair& pair : core->config.getActors()) { - core->logger.log(Logger::info, "registering actor " + pair.first + " as " + pair.second, {"Jay"}); - core->router.registerActor(pair.first, pair.second); - } + core->initialize(connection); + connection->initialize(); } void Jay::createModules() { @@ -61,7 +52,7 @@ void Jay::createModules() { continue; core->logger.log(Logger::info, "enabling module " + pair.first, {"Jay"}); - modules.emplace_back(pair.second(core, connection, conf.permissions)); + modules.emplace_back(pair.second(core, conf.permissions)); core->router.registerModule(pair.first, modules.back()); } } diff --git a/jay.h b/jay.h index 603f0d4..0f3c694 100644 --- a/jay.h +++ b/jay.h @@ -13,6 +13,7 @@ #include #include +#include "shared/definitions.h" #include "component/core.h" #include "connection/connection.h" #include "module/module.h" @@ -28,7 +29,6 @@ public: private: void initialize(); - void createActors(); void createModules(); private: diff --git a/module/actor.cpp b/module/actor.cpp index 44d5848..ee68076 100644 --- a/module/actor.cpp +++ b/module/actor.cpp @@ -3,20 +3,36 @@ #include "actor.h" -Module::Actor::Actor(const std::shared_ptr& core, const std::shared_ptr& connection, const Permissions& permissions): - Module(core, connection, permissions) +Module::Actor::Actor(const std::shared_ptr& core, const Shared::Permissions& permissions): + Module(core, permissions) {} Module::Actor::~Actor() noexcept {} -void Module::Actor::message(const std::shared_ptr<::Actor>& actor, const Module::Module::Tokens& args) { +Shared::Result Module::Actor::message(const std::shared_ptr<::Actor>& actor, const Shared::Strings& args) { std::string result; - if (args.front() == "list") - result = hasPermission("read", actor) ? list() : "Can not tell you that"; + if (args.front() == "list") { + if (!hasPermission("read", actor)) + return Shared::forbidden; + + result = list(); + } else if (args.front() == "set") { + if (!hasPermission("write", actor)) + return Shared::forbidden; + + if (args.size() < 3) + return Shared::error; - if (!result.empty()) - connection->send(actor->jid, result); + result = set(args[1], args[2]); + } + + if (!result.empty()) { + core->send(actor->jid, result); + return Shared::success; + } + + return Shared::unhandled; } std::string Module::Actor::list() { @@ -33,3 +49,9 @@ std::string Module::Actor::list() { return result; } + +std::string Module::Actor::set(const std::string& jid, const std::string& group) { + core->setGroup(jid, group); + + return jid + " is now " + core->router.getGroup(jid); +} diff --git a/module/actor.h b/module/actor.h index ef89150..fc6448b 100644 --- a/module/actor.h +++ b/module/actor.h @@ -3,19 +3,22 @@ #pragma once +#include "shared/definitions.h" +#include "shared/result.h" #include "module.h" namespace Module { class Actor : public Module { public: - Actor(const std::shared_ptr& core, const std::shared_ptr& connection, const Permissions& permissions); + Actor(const std::shared_ptr& core, const Shared::Permissions& permissions); ~Actor() noexcept; - virtual void message(const std::shared_ptr<::Actor>& actor, const Tokens& args) override; + virtual Shared::Result message(const std::shared_ptr<::Actor>& actor, const Shared::Strings& args) override; private: std::string list(); + std::string set(const std::string& jid, const std::string& group); }; } diff --git a/module/module.cpp b/module/module.cpp index 56815ff..3865188 100644 --- a/module/module.cpp +++ b/module/module.cpp @@ -7,14 +7,13 @@ #include "gloox/message.h" -Module::Module::Module(const std::shared_ptr& core, const std::shared_ptr& connection, const Permissions& permissions): +Module::Module::Module(const std::shared_ptr& core, const Shared::Permissions& permissions): core(core), - connection(connection), permissions(permissions) {} bool Module::Module::hasPermission(const std::string& permission, const std::shared_ptr<::Actor>& actor) const { - Permissions::const_iterator itr = permissions.find(permission); + Shared::Permissions::const_iterator itr = permissions.find(permission); if (itr == permissions.end()) return false; diff --git a/module/module.h b/module/module.h index 61a7911..b8f2513 100644 --- a/module/module.h +++ b/module/module.h @@ -7,34 +7,29 @@ #include #include +#include "shared/definitions.h" +#include "shared/result.h" #include "component/core.h" #include "component/actor.h" -#include "connection/connection.h" namespace Module { class Module { -public: - typedef std::vector Tokens; - typedef std::vector List; - typedef std::map Permissions; - protected: - Module(const std::shared_ptr& core, const std::shared_ptr& connection, const Permissions& permissions); + Module(const std::shared_ptr& core, const Shared::Permissions& permissions); bool hasPermission(const std::string& permission, const std::shared_ptr<::Actor>& actor) const; public: virtual ~Module() noexcept; - static Tokens split(const std::string& string, const std::string& delimiter = " "); + static Shared::Strings split(const std::string& string, const std::string& delimiter = " "); - virtual void message(const std::shared_ptr<::Actor>& actor, const Tokens& args) = 0; + virtual Shared::Result message(const std::shared_ptr<::Actor>& actor, const Shared::Strings& args) = 0; protected: std::shared_ptr core; - std::shared_ptr connection; - Permissions permissions; + Shared::Permissions permissions; }; diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt new file mode 100644 index 0000000..d157eb4 --- /dev/null +++ b/shared/CMakeLists.txt @@ -0,0 +1,9 @@ +set(SOURCES +) + +set(HEADERS + definitions.h + result.h +) + +target_sources(${EXEC_NAME} PRIVATE ${SOURCES}) \ No newline at end of file diff --git a/shared/definitions.h b/shared/definitions.h new file mode 100644 index 0000000..c03236e --- /dev/null +++ b/shared/definitions.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2024 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +namespace Shared { + typedef std::vector Strings; + typedef std::map Permissions; +} \ No newline at end of file diff --git a/shared/result.h b/shared/result.h new file mode 100644 index 0000000..60e9c36 --- /dev/null +++ b/shared/result.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2024 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +#include "definitions.h" + +namespace Shared { + enum Result { + success, + forbidden, + unhandled, + error + }; + + typedef std::map Responses; + + constexpr std::array, 4> results = {{ + { success, "success" }, + { forbidden, "forbidden" }, + { unhandled, "unhandled" }, + { error, "error" }, + }}; + + +} \ No newline at end of file