Yaml config

This commit is contained in:
Blue 2025-02-22 21:03:21 +02:00
parent 698e706daa
commit 46695c8aab
Signed by: blue
GPG Key ID: 9B203B252A63EE38
12 changed files with 303 additions and 74 deletions

View File

@ -7,13 +7,29 @@ project(jay
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find gloox library
cmake_policy(SET CMP0076 NEW)
find_package(PkgConfig)
pkg_search_module(GLOOX REQUIRED gloox)
add_executable(jay main.cpp jay.cpp logger.cpp)
find_package(yaml-cpp REQUIRED)
target_include_directories(jay PRIVATE ${GLOOX_INCLUDE_DIRS})
target_link_libraries(jay ${GLOOX_LIBRARIES})
set(EXEC_NAME "jay")
install(TARGETS jay RUNTIME DESTINATION bin)
add_executable(${EXEC_NAME}
main.cpp
jay.cpp
logger.cpp
config.cpp
)
add_subdirectory(handlers)
target_include_directories(${EXEC_NAME} PRIVATE ${GLOOX_INCLUDE_DIRS})
target_include_directories(${EXEC_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${EXEC_NAME} PRIVATE
${GLOOX_LIBRARIES}
yaml-cpp
)
install(TARGETS ${EXEC_NAME} RUNTIME DESTINATION bin)

63
config.cpp Normal file
View File

@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "config.h"
Config::Config(const std::string& path):
root(YAML::LoadFile(path))
{}
std::string Config::getBareJID() const {
return root["jid"].as<std::string>();
}
std::string Config::getPassword() const {
return root["password"].as<std::string>();
}
std::string Config::getResource() const {
return root["resource"].as<std::string>("bot");
}
std::string Config::getFullJID() const {
return getBareJID() + "/" + getResource();
}
gloox::LogLevel Config::getLogLevel() const {
std::string level = root["logLevel"].as<std::string>("warning");
if (level == "debug")
return gloox::LogLevelDebug;
else if (level == "error")
return gloox::LogLevelError;
else
return gloox::LogLevelWarning;
}
gloox::TLSPolicy Config::getTLSPolicy() const {
std::string level = root["tls"].as<std::string>("optional");
if (level == "disabled")
return gloox::TLSDisabled;
else if (level == "required")
return gloox::TLSRequired;
else
return gloox::TLSOptional;
}
std::set<std::string> Config::getOwners() const {
std::set<std::string> result;
YAML::Node owners = root["resource"];
if (!owners.IsSequence())
return result;
for (const YAML::Node& node : owners)
result.insert(node.as<std::string>());
return result;
}
bool Config::isValid() const {
return !getBareJID().empty() && !getPassword().empty();
}

29
config.h Normal file
View File

@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <string>
#include <set>
#include "gloox/gloox.h"
#include "yaml-cpp/yaml.h"
class Config {
public:
Config(const std::string& path);
bool isValid() const;
std::string getBareJID() const;
std::string getFullJID() const;
std::string getPassword() const;
std::string getResource() const;
std::set<std::string> getOwners() const;
gloox::LogLevel getLogLevel() const;
gloox::TLSPolicy getTLSPolicy() const;
private:
YAML::Node root;
};

8
example.config.yml Normal file
View File

@ -0,0 +1,8 @@
jid: bot@xmpp.server
password: "supersecret"
logLevel: debug
tls: required
resource: bot
owners:
- user1@xmpp.server
- user2@xmpp.server

11
handlers/CMakeLists.txt Normal file
View File

@ -0,0 +1,11 @@
set(SOURCES
message.cpp
connection.cpp
)
set(HEADERS
message.h
connection.h
)
target_sources(${EXEC_NAME} PRIVATE ${SOURCES})

27
handlers/connection.cpp Normal file
View File

@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "connection.h"
Connection::Connection(const std::shared_ptr<Config>& config, const std::shared_ptr<gloox::Client>& client):
config(config),
client(client)
{
client->registerConnectionListener(this);
}
Connection::~Connection() {
if (std::shared_ptr<gloox::Client> cl = client.lock())
cl->removeConnectionListener(this);
}
void Connection::onConnect() {}
void Connection::onDisconnect(gloox::ConnectionError e) {}
bool Connection::onTLSConnect(const gloox::CertInfo&) {
return true;
}

25
handlers/connection.h Normal file
View File

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <memory>
#include "gloox/client.h"
#include "gloox/connectionlistener.h"
#include "config.h"
class Connection : public gloox::ConnectionListener {
public:
Connection(const std::shared_ptr<Config>& config, const std::shared_ptr<gloox::Client>& client);
~Connection();
void onConnect() override;
void onDisconnect(gloox::ConnectionError e) override;
bool onTLSConnect(const gloox::CertInfo&) override;
private:
std::weak_ptr<Config> config;
std::weak_ptr<gloox::Client> client;
};

30
handlers/message.cpp Normal file
View File

@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "message.h"
#include <iostream>
Message::Message(const std::shared_ptr<Config>& config, const std::shared_ptr<gloox::Client>& client):
config(config),
client(client)
{
client->registerMessageHandler(this);
}
Message::~Message() {
if (std::shared_ptr<gloox::Client> cl = client.lock())
cl->removeMessageHandler(this);
}
void Message::handleMessage(const gloox::Message& message, gloox::MessageSession* session) {
std::shared_ptr<Config> cfg = config.lock();
if (!cfg)
return;
if (cfg->getOwners().count(message.from().bare())) {
std::cout << "Received message from owner: " << message.body() << std::endl;
} else {
std::cout << "Received message: " << message.body() << std::endl;
}
}

24
handlers/message.h Normal file
View File

@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <memory>
#include <gloox/client.h>
#include <gloox/messagehandler.h>
#include <gloox/message.h>
#include "config.h"
class Message : public gloox::MessageHandler {
public:
Message(const std::shared_ptr<Config>& config, const std::shared_ptr<gloox::Client>& client);
~Message();
void handleMessage(const gloox::Message& message, gloox::MessageSession* session = 0) override;
private:
std::weak_ptr<Config> config;
std::weak_ptr<gloox::Client> client;
};

80
jay.cpp
View File

@ -3,59 +3,57 @@
#include "jay.h"
Jay::Jay(const std::string& jid, const std::string& password) :
client(jid, password),
loggers(),
owners()
{
client.registerMessageHandler(this);
client.registerConnectionListener(this);
client.setTls(gloox::TLSPolicy::TLSOptional);
client.setSASLMechanisms(gloox::SaslMechScramSha1);
}
Jay::Jay(const std::string& configPath):
runMutex(),
config(std::make_shared<Config>(configPath)),
client(),
messageHandler(),
connectionHandler(),
loggers()
{}
Jay::~Jay() {}
bool Jay::isConfigValid() const {
return config->isValid();
}
void Jay::run() {
if (client.connect(false)) {
// Run the event loop
gloox::ConnectionError ce = gloox::ConnNoError;
while (ce == gloox::ConnNoError)
ce = client.recv();
std::lock_guard lock(runMutex);
std::cout << "Connection terminated with error: " << ce << std::endl;
}
std::cout << "Out of loop" << std::endl;
initialize();
client->connect(true);
}
void Jay::handleMessage(const gloox::Message& message, gloox::MessageSession* session) {
if (owners.count(message.from().bare())) {
std::cout << "Received message from owner: " << message.body() << std::endl;
} else {
std::cout << "Received message: " << message.body() << std::endl;
}
void Jay::initialize() {
createClient();
if (!messageHandler)
messageHandler = std::make_unique<Message>(config, client);
if (!connectionHandler)
connectionHandler = std::make_unique<Connection>(config, client);
}
void Jay::onConnect() {
for (const std::string& owner : owners)
client.send(gloox::Message(gloox::Message::Chat, owner, "I'm online!"));
}
void Jay::createClient() {
if (client)
return;
void Jay::onDisconnect(gloox::ConnectionError e) {
std::cout << "Disconnected: " << e << std::endl;
}
client = std::make_shared<gloox::Client>(config->getFullJID(), config->getPassword());
addLogger(config->getLogLevel());
bool Jay::onTLSConnect(const gloox::CertInfo&) {
return true;
gloox::Disco* disco = client->disco();
disco->setVersion("Jay", "0.0.1");
disco->setIdentity("client", "bot");
client->setTls(config->getTLSPolicy());
client->setSASLMechanisms(gloox::SaslMechAll);
client->setStreamManagement(true, true);
}
Logger* Jay::addLogger(gloox::LogLevel level) {
loggers.emplace_back(std::make_unique<Logger>(client.logInstance(), level));
return loggers.back().get();
}
void Jay::addOwner(const std::string& jid) {
owners.insert(jid);
void Jay::addLogger(gloox::LogLevel level) {
loggers.emplace_back(std::make_unique<Logger>(client->logInstance(), level));
}

30
jay.h
View File

@ -4,35 +4,39 @@
#pragma once
#include <string>
#include <set>
#include <iostream>
#include <vector>
#include <memory>
#include <mutex>
#include <gloox/client.h>
#include <gloox/messagehandler.h>
#include <gloox/disco.h>
#include <gloox/connectionlistener.h>
#include <gloox/message.h>
#include "logger.h"
#include "config.h"
#include "handlers/message.h"
#include "handlers/connection.h"
class Jay : public gloox::MessageHandler, public gloox::ConnectionListener {
class Jay {
public:
Jay(const std::string& jid, const std::string& password);
Jay(const std::string& configPath);
~Jay();
void handleMessage(const gloox::Message& message, gloox::MessageSession* session = 0) override;
void onConnect() override;
void onDisconnect(gloox::ConnectionError e) override;
bool onTLSConnect(const gloox::CertInfo&) override;
bool isConfigValid() const;
void run();
Logger* addLogger(gloox::LogLevel level);
void addOwner(const std::string& jid);
private:
void addLogger(gloox::LogLevel level);
void initialize();
void createClient();
private:
gloox::Client client;
std::mutex runMutex;
std::shared_ptr<Config> config;
std::shared_ptr<gloox::Client> client;
std::unique_ptr<Message> messageHandler;
std::unique_ptr<Connection> connectionHandler;
std::vector<std::unique_ptr<Logger>> loggers;
std::set<std::string> owners;
};

View File

@ -16,23 +16,17 @@ std::string readEnv(const std::string& key, const std::string& defaultValue = ""
return defaultValue;
}
int main(int argc, char** argv) {
std::string jid = readEnv("JID");
std::string password = readEnv("PASSWORD");
if (jid.empty() || password.empty()) {
std::cout << "You need to provide JID and PASSWORD environment variables" << std::endl;
return - 1;
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <config.yaml>" << std::endl;
return EXIT_FAILURE;
}
Jay bot(jid, password);
bot.addLogger(gloox::LogLevelDebug);
std::string owners = readEnv("OWNERS");
std::stringstream ss(owners);
std::string owner;
while (std::getline(ss, owner, ','))
bot.addOwner(owner);
Jay bot(argv[1]);
if (!bot.isConfigValid()) {
std::cerr << "Invalid config, can not proceed, quitting" << std::endl;
return EXIT_FAILURE;
}
bot.run();