diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..e0b7162 --- /dev/null +++ b/.clang-format @@ -0,0 +1,52 @@ +BasedOnStyle: Google + +# === Indentation === +IndentWidth: 4 +TabWidth: 4 +UseTab: Never + +# === Pointer/Reference Alignment === +DerivePointerAlignment: false +PointerAlignment: Left + +# === Braces === +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + +# === Spaces === +SpaceBeforeParens: ControlStatements +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpacesInAngles: false + +# === Line Breaking === +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +BreakBeforeBraces: Attach +ColumnLimit: 120 + +# === Includes === +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 +SortIncludes: false + +# === Misc === +AlignAfterOpenBracket: Align +AlwaysBreakTemplateDeclarations: Yes +Cpp11BracedListStyle: true + +# === Constructor === +ConstructorInitializerIndentWidth: 4 +BreakConstructorInitializers: AfterColon \ No newline at end of file diff --git a/README.md b/README.md index c24ed54..79eeee0 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,14 @@ cmake --build . ## Modules ### Actor -This module is designed to control access to Bot. +This module is designed to manage access to Bot. #### Commands: -- list: lists all known actors -- set `actor` `group`: assigns a group to an actor. `default` is a special actor name, group from it is assigned to all new actors, not listed in `actors` section of config \ No newline at end of file +- **list**: lists all known actors +- **set** `actor` `group`: assigns a group to an actor. `default` is a special actor name, group from it is assigned to all new actors, not listed in `actors` section of config + +### Publish +This module enables bot to publish to PubSub nodes + +#### Commands: +- **to** `node@service` `title`\n`article`: Publishes `article` to the `node` of the `service` \ No newline at end of file diff --git a/component/router.cpp b/component/router.cpp index 4154fcb..a7459fb 100644 --- a/component/router.cpp +++ b/component/router.cpp @@ -3,23 +3,21 @@ #include "router.h" -#include - #include "module/module.h" #include "connection/connection.h" -Router::Router(const Shared::Logger& logger): +Router::Router(const Shared::Logger& logger) : Shared::Loggable(logger, {"Router"}), modules(), actors(), defaultGroup("Stranger"), responses({ - {Shared::forbidden, { "Forbidden" }}, - {Shared::error, { "Command error" }}, - {Shared::unhandled, { "No such command" }}, + {Shared::forbidden, {"Forbidden"}}, + {Shared::error, {"Command error"}}, + {Shared::unhandled, {"No such command"}}, }), - generator(std::random_device{}()) -{} + generator(std::random_device{}()), + dialogs() {} void Router::registerModule(const std::shared_ptr& module) { info("Registering module " + module->name + " as " + module->alias); @@ -40,49 +38,129 @@ void Router::registerActor(const std::string& key, const std::string& group) { } void Router::routeMessage(const std::string& sender, const std::string& body) { - Actors::iterator aItr = actors.find(sender); - if (aItr == actors.end()) - aItr = actors.emplace(sender, std::make_shared(sender, defaultGroup)).first; + Shared::VC::const_iterator dItr = dialogs.find(sender); + if (dItr != dialogs.end()) { + if (body != "cancel") + return routeToModule(dItr->second, sender, body); + + onMessageResult(cancelDialog(sender), sender); + return; + } std::string_view view(body); std::string_view aliasView = Module::Module::pop(view); - - if (aliasView.size() > 1000) { - warn("Received a message with the first word larger than 1000 symbols, " + sender + "is clearly looking for a vulnerability"); - return onMessageResult(Shared::unhandled, sender); + if (aliasView.size() > 100) { + warn("Received a message with the first word larger than 100 symbols from " + sender); + onMessageResult(Shared::unhandled, sender); + return; } - if (view.empty()) { + routeToModule(std::string(aliasView), sender, view); +} + +void Router::routeToModule(const std::string& alias, const std::string& sender, const std::string_view& message) { + if (message.empty()) { debug("Incoming message from " + sender + " consists of only one word, modules have nothing to process"); - return onMessageResult(Shared::unhandled, sender); + onMessageResult(Shared::unhandled, sender); + return; } - std::string alias(aliasView); Modules::iterator mItr = modules.find(alias); if (mItr == modules.end()) { debug("could not find module \"" + alias + "\" to handle message from " + sender); - return onMessageResult(Shared::unhandled, sender); + onMessageResult(Shared::unhandled, sender); + return; } std::shared_ptr module = mItr->second.lock(); if (!module) { error("could not lock module \"" + mItr->first + "\" to handle message from " + sender); - return onMessageResult(Shared::error, sender); + onMessageResult(Shared::error, sender); + return; } - Shared::Result result; + Shared::Result result = Shared::error; try { - result = module->message(aItr->second, view); + result = module->message(getActor(sender), message); if (result == Shared::success) - debug("module \"" + mItr->first + "\" successfully handled message from " + sender); + debug("module \"" + mItr->first + "\" successfully handled message from " + sender); + } catch (const std::exception& e) { + error("module \"" + mItr->first + "\" thew an exception handling message from " + sender + ": " + e.what()); } catch (...) { error("module \"" + mItr->first + "\" thew an unhandled exception handling message from " + sender); - result = Shared::error; + } + + switch (result) { + case Shared::grab: + startDialog(sender, alias); + break; + default: + finishDialog(sender); + break; } onMessageResult(result, sender); } +bool Router::startDialog(const std::string& actor, const std::string& alias) { + std::pair result = dialogs.emplace(actor, alias); + if (alias == result.first->second) { + if (result.second) + debug("Starting dialog of " + actor + " and " + alias); + else + debug("Continuing dialog of " + actor + " and " + alias); + + result.second = true; + } + + if (!result.second) + warn("There was a request from the module " + alias + " to start dialog with " + actor + " but the module " + + result.first->second + " is already in a dialog with this user right now"); + + return result.second; +} + +void Router::finishDialog(const std::string& actor) { + Shared::VC::const_iterator itr = dialogs.find(actor); + if (itr != dialogs.end()) + return debug("Dialogue of " + actor + " and " + itr->second + " is complete"); + + dialogs.erase(itr); +} + +Shared::Result Router::cancelDialog(const std::string& actor) { + Shared::VC::const_iterator dItr = dialogs.find(actor); + if (dItr == dialogs.end()) { + warn("There was a request to cancel dialog with " + actor + " but there is no dialog with him open right now"); + return Shared::unhandled; + } + + Modules::iterator mItr = modules.find(dItr->second); + if (mItr == modules.end()) { + error("could not find module \"" + dItr->second + "\" to cancel dialog with " + actor); + return Shared::unhandled; + } + + std::shared_ptr module = mItr->second.lock(); + if (!module) { + error("could not lock module \"" + mItr->first + "\" to cancel dialog with " + actor); + return Shared::error; + } + + debug("canceling dialog with " + actor); + try { + module->cancelDialog(getActor(actor)); + dialogs.erase(dItr); + return Shared::success; + } catch (const std::exception& e) { + error("Caught exception canceling dialog in module " + dItr->second + ": " + e.what()); + } catch (...) { + error("Caught unhandled exception canceling dialog in module " + dItr->second); + } + + return Shared::error; +} + std::map Router::getActors() const { std::map result; @@ -116,7 +194,7 @@ 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) { warn("Couldn't send a message to " + sender + ", connection is not available"); @@ -128,3 +206,10 @@ void Router::onMessageResult(Shared::Result result, const std::string& sender) { cn->send(sender, options[dist(generator)]); } +std::shared_ptr<::Actor> Router::getActor(const std::string& jid) { + Actors::iterator itr = actors.find(jid); + if (itr == actors.end()) + itr = actors.emplace(jid, std::make_shared(jid, defaultGroup)).first; + + return itr->second; +} diff --git a/component/router.h b/component/router.h index 0e2aa3a..9719753 100644 --- a/component/router.h +++ b/component/router.h @@ -4,10 +4,12 @@ #pragma once #include +#include #include #include #include #include +#include #include "shared/result.h" #include "shared/loggable.h" @@ -27,6 +29,7 @@ public: void registerModule(const std::shared_ptr& module); void registerActor(const std::string& key, const std::string& group); void routeMessage(const std::string& sender, const std::string& body); + void routeToModule(const std::string& alias, const std::string& sender, const std::string_view& message); std::map getActors() const; std::string getDefaultGroup() const; @@ -37,6 +40,11 @@ public: private: void onMessageResult(Shared::Result result, const std::string& sender); + std::shared_ptr<::Actor> getActor(const std::string& jid); + + bool startDialog(const std::string& actor, const std::string& alias); + void finishDialog(const std::string& alias); + Shared::Result cancelDialog(const std::string& actor); private: typedef std::map> Modules; @@ -50,4 +58,5 @@ private: std::string defaultGroup; Responses responses; std::mt19937 generator; + Shared::VC dialogs; }; diff --git a/example.config.yml b/example.config.yml index ba46eeb..3c5fe4b 100644 --- a/example.config.yml +++ b/example.config.yml @@ -26,6 +26,7 @@ modules: replies: success: [] + grab: [] forbidden: [Forbidden] error: [Command error] unhandled: [No such command] diff --git a/module/module.cpp b/module/module.cpp index 784ae14..4118436 100644 --- a/module/module.cpp +++ b/module/module.cpp @@ -47,3 +47,7 @@ std::string_view Module::Module::pop(std::string_view& input, char delimiter) { return token; } + +void Module::Module::cancelDialog(const std::shared_ptr<::Actor>& actor) { + warn("Dialog with " + actor->jid + " was requested to be canceled, but this module doesn't handle canceling dialogs"); +} diff --git a/module/module.h b/module/module.h index 5640f92..aed7de4 100644 --- a/module/module.h +++ b/module/module.h @@ -32,6 +32,7 @@ public: static std::string_view pop(std::string_view& input, char delimiter = ' '); virtual Shared::Result message(const std::shared_ptr<::Actor>& actor, std::string_view view) = 0; + virtual void cancelDialog(const std::shared_ptr<::Actor>& actor); public: const std::string name; diff --git a/shared/result.h b/shared/result.h index 60e9c36..52d9ec1 100644 --- a/shared/result.h +++ b/shared/result.h @@ -12,6 +12,7 @@ namespace Shared { enum Result { success, + grab, forbidden, unhandled, error @@ -19,11 +20,12 @@ namespace Shared { typedef std::map Responses; - constexpr std::array, 4> results = {{ + constexpr std::array, 5> results = {{ { success, "success" }, + { grab, "grab" }, { forbidden, "forbidden" }, { unhandled, "unhandled" }, - { error, "error" }, + { error, "error" } }};