Logging of the messages, customizable replies, assigning groups

This commit is contained in:
Blue 2025-03-15 22:34:50 +02:00
parent f03f392cee
commit 7f57cd3bf6
Signed by: blue
GPG Key ID: 9B203B252A63EE38
21 changed files with 288 additions and 62 deletions

View File

@ -21,6 +21,7 @@ add_executable(${EXEC_NAME} main.cpp jay.cpp)
add_subdirectory(component) add_subdirectory(component)
add_subdirectory(connection) add_subdirectory(connection)
add_subdirectory(module) add_subdirectory(module)
add_subdirectory(shared)
target_include_directories(${EXEC_NAME} PRIVATE ${GLOOX_INCLUDE_DIRS}) target_include_directories(${EXEC_NAME} PRIVATE ${GLOOX_INCLUDE_DIRS})
target_include_directories(${EXEC_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(${EXEC_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -85,7 +85,7 @@ Config::Module Config::getModuleConfig(const std::string& name) const {
YAML::Node prm = conf["permissions"]; YAML::Node prm = conf["permissions"];
if (prm.IsMap()) { if (prm.IsMap()) {
for (const auto& node : prm) { for (const auto& node : prm) {
Module::List& list = result.permissions.emplace(node.first.as<std::string>(), Module::List()).first->second; Shared::Strings& list = result.permissions.emplace(node.first.as<std::string>(), Shared::Strings()).first->second;
YAML::Node lst = node.second; YAML::Node lst = node.second;
if (lst.IsSequence()) if (lst.IsSequence())
for (const YAML::Node& member : lst) for (const YAML::Node& member : lst)
@ -93,6 +93,25 @@ Config::Module Config::getModuleConfig(const std::string& name) const {
} }
} }
return result; 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<Shared::Result, std::string_view>& 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<std::string>());
}
return responses;
}

View File

@ -10,17 +10,16 @@
#include "yaml-cpp/yaml.h" #include "yaml-cpp/yaml.h"
#include "shared/definitions.h"
#include "shared/result.h"
#include "logger.h" #include "logger.h"
class Config { class Config {
public: public:
struct Module { struct Module {
typedef std::vector<std::string> List;
typedef std::map<std::string, List> Permissions;
bool enabled; bool enabled;
std::string alias; std::string alias;
Permissions permissions; Shared::Permissions permissions;
}; };
public: public:
@ -36,6 +35,7 @@ public:
Logger::Level getLogLevel() const; Logger::Level getLogLevel() const;
gloox::TLSPolicy getTLSPolicy() const; gloox::TLSPolicy getTLSPolicy() const;
Module getModuleConfig(const std::string& name) const; Module getModuleConfig(const std::string& name) const;
Shared::Responses getResponses() const;
private: private:
YAML::Node root; YAML::Node root;

View File

@ -3,8 +3,51 @@
#include "core.h" #include "core.h"
#include "connection/connection.h"
Core::Core(const std::string& configPath): Core::Core(const std::string& configPath):
config(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<Connection> 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<Connection>& 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<const std::string, std::string>& 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<Shared::Result, Shared::Strings>& pair : config.getResponses())
router.setResponses(pair.first, pair.second);
}

View File

@ -4,17 +4,33 @@
#pragma once #pragma once
#include <string> #include <string>
#include <memory>
#include "config.h" #include "config.h"
#include "router.h" #include "router.h"
#include "logger.h" #include "logger.h"
class Connection;
class Core { class Core {
public: public:
Core(const std::string& configPath); Core(const std::string& configPath);
void send(const std::string& jid, const std::string& body);
void initialize(const std::shared_ptr<Connection>& connection);
void setGroup(const std::string& jid, const std::string& group);
public:
Config config; Config config;
Router router;
Logger logger; Logger logger;
Router router;
private:
void initializeActors();
void initializeResponses();
private:
bool initialized;
std::weak_ptr<Connection> connection;
}; };

View File

@ -129,7 +129,7 @@ void Logger::handleLog(gloox::LogLevel level, gloox::LogArea area, const std::st
std::cout << clear << '\t' << message << clear << std::endl; std::cout << clear << '\t' << message << clear << std::endl;
} }
void Logger::log(Level lvl, const std::string& message, const std::vector<std::string>& domain) { void Logger::log(Level lvl, const std::string& message, const std::vector<std::string>& domain) const {
if (lvl < level) if (lvl < level)
return; return;

View File

@ -25,7 +25,7 @@ public:
void handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message) override; void handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message) override;
void log(Level level, const std::string& message, const std::vector<std::string>& domain = {}); void log(Level level, const std::string& message, const std::vector<std::string>& domain = {}) const;
static gloox::LogLevel convert(Level level); static gloox::LogLevel convert(Level level);
static Level convert(gloox::LogLevel level); static Level convert(gloox::LogLevel level);

View File

@ -4,10 +4,19 @@
#include "router.h" #include "router.h"
#include "module/module.h" #include "module/module.h"
#include "connection/connection.h"
Router::Router(): Router::Router(const Logger& logger):
logger(logger),
modules(),
actors(), 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::Module>& module) { void Router::registerModule(const std::string& key, const std::shared_ptr<Module::Module>& module) {
@ -34,15 +43,29 @@ void Router::routeMessage(const std::string& sender, const std::string& body) {
std::vector<std::string> args = Module::Module::split(body); std::vector<std::string> args = Module::Module::split(body);
Modules::iterator mItr = modules.find(args[0]); Modules::iterator mItr = modules.find(args[0]);
if (mItr == modules.end()) if (mItr == modules.end()) {
return; 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::Module> module = mItr->second.lock(); std::shared_ptr<Module::Module> module = mItr->second.lock();
if (!module) if (!module) {
return; 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()); 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<std::string, std::string> Router::getActors() const { std::map<std::string, std::string> Router::getActors() const {
@ -54,4 +77,35 @@ std::map<std::string, std::string> Router::getActors() const {
return result; return result;
} }
void Router::setConnection(const std::shared_ptr<Connection>& 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<Connection> 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<std::size_t> dist(0, options.size() - 1);
cn->send(sender, options[dist(generator)]);
}

View File

@ -7,16 +7,21 @@
#include <map> #include <map>
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <random>
#include "shared/result.h"
#include "actor.h" #include "actor.h"
#include "logger.h"
namespace Module { namespace Module {
class Module; class Module;
}; };
class Connection;
class Router { class Router {
public: public:
Router(); Router(const Logger& logger);
void registerModule(const std::string& key, const std::shared_ptr<Module::Module>& module); void registerModule(const std::string& key, const std::shared_ptr<Module::Module>& module);
void registerActor(const std::string& key, const std::string& group); void registerActor(const std::string& key, const std::string& group);
@ -24,11 +29,24 @@ public:
std::map<std::string, std::string> getActors() const; std::map<std::string, std::string> getActors() const;
void setConnection(const std::shared_ptr<Connection>& 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: private:
typedef std::map<std::string, std::weak_ptr<Module::Module>> Modules; typedef std::map<std::string, std::weak_ptr<Module::Module>> Modules;
typedef std::map<std::string, std::shared_ptr<Actor>> Actors; typedef std::map<std::string, std::shared_ptr<Actor>> Actors;
typedef std::vector<std::string> List;
typedef std::map<Shared::Result, List> Responses;
std::weak_ptr<Connection> connection;
const Logger& logger;
Modules modules; Modules modules;
Actors actors; Actors actors;
std::string defaultGroup; std::string defaultGroup;
Responses responses;
std::mt19937 generator;
}; };

View File

@ -13,7 +13,7 @@ Connection::~Connection() noexcept {
deinitialize(); deinitialize();
} }
void Connection::initiialize() { void Connection::initialize() {
if (state != initial) if (state != initial)
return; 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) { 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(); std::string jid = message.from().bare();
core->logger.log(Logger::debug, "received message \"" + message.body() + "\" from " + jid, {"Connection"}); core->logger.log(Logger::debug, "received message \"" + body + "\" from " + jid, {"Connection"});
core->router.routeMessage(jid, message.body()); core->router.routeMessage(jid, body);
} }
void Connection::onConnect() { void Connection::onConnect() {

View File

@ -28,7 +28,7 @@ public:
Connection(const std::shared_ptr<Core>& core); Connection(const std::shared_ptr<Core>& core);
~Connection() noexcept; ~Connection() noexcept;
void initiialize(); void initialize();
void deinitialize(); void deinitialize();
void connect(); void connect();
void send(const std::string& jid, const std::string& body); void send(const std::string& jid, const std::string& body);

View File

@ -17,3 +17,9 @@ modules:
permissions: permissions:
read: [Owner, User] read: [Owner, User]
write: [Owner] write: [Owner]
replies:
success: []
forbidden: [Forbidden]
error: [Command error]
unhandled: [No such command]

21
jay.cpp
View File

@ -10,16 +10,14 @@ static const std::map<
std::function< std::function<
std::shared_ptr<Module::Module>( std::shared_ptr<Module::Module>(
const std::shared_ptr<Core>&, const std::shared_ptr<Core>&,
const std::shared_ptr<Connection>&, const Shared::Permissions& permissions
const Module::Module::Permissions& permissions
) )
> >
> moduleNames = { > moduleNames = {
{"actor", []( {"actor", [](
const std::shared_ptr<Core>& core, const std::shared_ptr<Core>& core,
const std::shared_ptr<Connection>& connection, const Shared::Permissions& permissions
const Module::Module::Permissions& permissions ) { return std::make_shared<Module::Actor>(core, permissions); }}
) { return std::make_shared<Module::Actor>(core, connection, permissions); }}
}; };
Jay::Jay(const std::string& configPath): Jay::Jay(const std::string& configPath):
@ -42,16 +40,9 @@ void Jay::run() {
} }
void Jay::initialize() { void Jay::initialize() {
connection->initiialize();
createModules(); createModules();
createActors(); core->initialize(connection);
} connection->initialize();
void Jay::createActors() {
for (const std::pair<const std::string, std::string>& pair : core->config.getActors()) {
core->logger.log(Logger::info, "registering actor " + pair.first + " as " + pair.second, {"Jay"});
core->router.registerActor(pair.first, pair.second);
}
} }
void Jay::createModules() { void Jay::createModules() {
@ -61,7 +52,7 @@ void Jay::createModules() {
continue; continue;
core->logger.log(Logger::info, "enabling module " + pair.first, {"Jay"}); 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()); core->router.registerModule(pair.first, modules.back());
} }
} }

2
jay.h
View File

@ -13,6 +13,7 @@
#include <gloox/disco.h> #include <gloox/disco.h>
#include <gloox/connectionlistener.h> #include <gloox/connectionlistener.h>
#include "shared/definitions.h"
#include "component/core.h" #include "component/core.h"
#include "connection/connection.h" #include "connection/connection.h"
#include "module/module.h" #include "module/module.h"
@ -28,7 +29,6 @@ public:
private: private:
void initialize(); void initialize();
void createActors();
void createModules(); void createModules();
private: private:

View File

@ -3,20 +3,36 @@
#include "actor.h" #include "actor.h"
Module::Actor::Actor(const std::shared_ptr<Core>& core, const std::shared_ptr<Connection>& connection, const Permissions& permissions): Module::Actor::Actor(const std::shared_ptr<Core>& core, const Shared::Permissions& permissions):
Module(core, connection, permissions) Module(core, permissions)
{} {}
Module::Actor::~Actor() noexcept {} 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; std::string result;
if (args.front() == "list") if (args.front() == "list") {
result = hasPermission("read", actor) ? list() : "Can not tell you that"; 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()) result = set(args[1], args[2]);
connection->send(actor->jid, result); }
if (!result.empty()) {
core->send(actor->jid, result);
return Shared::success;
}
return Shared::unhandled;
} }
std::string Module::Actor::list() { std::string Module::Actor::list() {
@ -33,3 +49,9 @@ std::string Module::Actor::list() {
return result; 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);
}

View File

@ -3,19 +3,22 @@
#pragma once #pragma once
#include "shared/definitions.h"
#include "shared/result.h"
#include "module.h" #include "module.h"
namespace Module { namespace Module {
class Actor : public Module { class Actor : public Module {
public: public:
Actor(const std::shared_ptr<Core>& core, const std::shared_ptr<Connection>& connection, const Permissions& permissions); Actor(const std::shared_ptr<Core>& core, const Shared::Permissions& permissions);
~Actor() noexcept; ~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: private:
std::string list(); std::string list();
std::string set(const std::string& jid, const std::string& group);
}; };
} }

View File

@ -7,14 +7,13 @@
#include "gloox/message.h" #include "gloox/message.h"
Module::Module::Module(const std::shared_ptr<Core>& core, const std::shared_ptr<Connection>& connection, const Permissions& permissions): Module::Module::Module(const std::shared_ptr<Core>& core, const Shared::Permissions& permissions):
core(core), core(core),
connection(connection),
permissions(permissions) permissions(permissions)
{} {}
bool Module::Module::hasPermission(const std::string& permission, const std::shared_ptr<::Actor>& actor) const { 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()) if (itr == permissions.end())
return false; return false;

View File

@ -7,34 +7,29 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "shared/definitions.h"
#include "shared/result.h"
#include "component/core.h" #include "component/core.h"
#include "component/actor.h" #include "component/actor.h"
#include "connection/connection.h"
namespace Module { namespace Module {
class Module { class Module {
public:
typedef std::vector<std::string> Tokens;
typedef std::vector<std::string> List;
typedef std::map<std::string, List> Permissions;
protected: protected:
Module(const std::shared_ptr<Core>& core, const std::shared_ptr<Connection>& connection, const Permissions& permissions); Module(const std::shared_ptr<Core>& core, const Shared::Permissions& permissions);
bool hasPermission(const std::string& permission, const std::shared_ptr<::Actor>& actor) const; bool hasPermission(const std::string& permission, const std::shared_ptr<::Actor>& actor) const;
public: public:
virtual ~Module() noexcept; 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: protected:
std::shared_ptr<Core> core; std::shared_ptr<Core> core;
std::shared_ptr<Connection> connection; Shared::Permissions permissions;
Permissions permissions;
}; };

9
shared/CMakeLists.txt Normal file
View File

@ -0,0 +1,9 @@
set(SOURCES
)
set(HEADERS
definitions.h
result.h
)
target_sources(${EXEC_NAME} PRIVATE ${SOURCES})

13
shared/definitions.h Normal file
View File

@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <string>
#include <vector>
#include <map>
namespace Shared {
typedef std::vector<std::string> Strings;
typedef std::map<std::string, Strings> Permissions;
}

30
shared/result.h Normal file
View File

@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <map>
#include <array>
#include <string_view>
#include "definitions.h"
namespace Shared {
enum Result {
success,
forbidden,
unhandled,
error
};
typedef std::map<Result, Strings> Responses;
constexpr std::array<std::pair<Result, std::string_view>, 4> results = {{
{ success, "success" },
{ forbidden, "forbidden" },
{ unhandled, "unhandled" },
{ error, "error" },
}};
}