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(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})

View File

@ -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<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;
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<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 "shared/definitions.h"
#include "shared/result.h"
#include "logger.h"
class Config {
public:
struct Module {
typedef std::vector<std::string> List;
typedef std::map<std::string, List> 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;

View File

@ -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<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
#include <string>
#include <memory>
#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>& 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> 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;
}
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)
return;

View File

@ -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<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 Level convert(gloox::LogLevel level);

View File

@ -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::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);
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::Module> 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<std::string, std::string> Router::getActors() const {
@ -54,4 +77,35 @@ std::map<std::string, std::string> Router::getActors() const {
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 <vector>
#include <memory>
#include <random>
#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::Module>& module);
void registerActor(const std::string& key, const std::string& group);
@ -24,11 +29,24 @@ public:
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:
typedef std::map<std::string, std::weak_ptr<Module::Module>> Modules;
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;
Actors actors;
std::string defaultGroup;
Responses responses;
std::mt19937 generator;
};

View File

@ -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() {

View File

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

View File

@ -17,3 +17,9 @@ modules:
permissions:
read: [Owner, User]
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::shared_ptr<Module::Module>(
const std::shared_ptr<Core>&,
const std::shared_ptr<Connection>&,
const Module::Module::Permissions& permissions
const Shared::Permissions& permissions
)
>
> moduleNames = {
{"actor", [](
const std::shared_ptr<Core>& core,
const std::shared_ptr<Connection>& connection,
const Module::Module::Permissions& permissions
) { return std::make_shared<Module::Actor>(core, connection, permissions); }}
const Shared::Permissions& permissions
) { return std::make_shared<Module::Actor>(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<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);
}
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());
}
}

2
jay.h
View File

@ -13,6 +13,7 @@
#include <gloox/disco.h>
#include <gloox/connectionlistener.h>
#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:

View File

@ -3,20 +3,36 @@
#include "actor.h"
Module::Actor::Actor(const std::shared_ptr<Core>& core, const std::shared_ptr<Connection>& connection, const Permissions& permissions):
Module(core, connection, permissions)
Module::Actor::Actor(const std::shared_ptr<Core>& 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;
if (!result.empty())
connection->send(actor->jid, result);
result = list();
} else if (args.front() == "set") {
if (!hasPermission("write", actor))
return Shared::forbidden;
if (args.size() < 3)
return Shared::error;
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);
}

View File

@ -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>& core, const std::shared_ptr<Connection>& connection, const Permissions& permissions);
Actor(const std::shared_ptr<Core>& 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);
};
}

View File

@ -7,14 +7,13 @@
#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),
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;

View File

@ -7,34 +7,29 @@
#include <memory>
#include <vector>
#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<std::string> Tokens;
typedef std::vector<std::string> List;
typedef std::map<std::string, List> Permissions;
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;
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> core;
std::shared_ptr<Connection> connection;
Permissions permissions;
Shared::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" },
}};
}