First way to publish

This commit is contained in:
Blue 2025-03-28 23:12:34 +02:00
parent 0be7fe9bff
commit 647b8f3072
Signed by: blue
GPG Key ID: 9B203B252A63EE38
17 changed files with 240 additions and 21 deletions

View File

@ -9,11 +9,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
cmake_policy(SET CMP0076 NEW)
find_package(PkgConfig)
find_package(PkgConfig REQUIRED)
pkg_search_module(GLOOX REQUIRED gloox)
find_package(yaml-cpp REQUIRED)
pkg_check_modules(UUID REQUIRED uuid)
set(EXEC_NAME "jay")
add_executable(${EXEC_NAME} main.cpp jay.cpp)
@ -25,9 +27,11 @@ add_subdirectory(shared)
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 ${UUID_INCLUDE_DIRS})
target_link_libraries(${EXEC_NAME} PRIVATE
${GLOOX_LIBRARIES}
yaml-cpp
${UUID_LIBRARIES}
)
install(TARGETS ${EXEC_NAME} RUNTIME DESTINATION bin)

View File

@ -51,6 +51,16 @@ void Core::setGroup(const std::string& jid, const std::string& group) {
config.setActors(actors);
}
void Core::publish(const std::string& service, const std::string& node, const std::string& title, const std::string& body) {
std::shared_ptr<Connection> cn = connection.lock();
if (!cn) {
logger.log(Shared::Logger::warning, "Couldn't publish to " + node + "@" + service + ", connection is not available", {"Core"});
return;
}
cn->publish(service, node, title, body);
}
void Core::initializeActors() {
for (const std::pair<const std::string, std::string>& pair : config.getActors()) {
logger.log(Shared::Logger::info, "registering actor " + pair.first + " as " + pair.second, {"Core"});

View File

@ -19,6 +19,7 @@ public:
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);
void publish(const std::string& service, const std::string& node, const std::string& title, const std::string& body);
public:
Config config;

View File

@ -41,7 +41,7 @@ void Router::routeMessage(const std::string& sender, const std::string& body) {
if (aItr == actors.end())
aItr = actors.emplace(sender, std::make_shared<Actor>(sender, defaultGroup)).first;
std::vector<std::string> args = Module::Module::split(body);
Shared::Strings args = Shared::split(body);
std::string moduleAlias = Module::Module::lower(args[0]);
Modules::iterator mItr = modules.find(moduleAlias);

View File

@ -11,6 +11,7 @@
#include "shared/result.h"
#include "shared/loggable.h"
#include "shared/utils.h"
#include "actor.h"
namespace Module {

View File

@ -7,7 +7,8 @@ Connection::Connection(const std::shared_ptr<Core>& core):
Shared::Loggable(core->logger, {"Connection"}),
state(initial),
core(core),
gloox()
gloox(),
pubsub()
{}
Connection::~Connection() noexcept {
@ -33,6 +34,8 @@ void Connection::initialize() {
gloox->setSASLMechanisms(gloox::SaslMechAll);
gloox->setStreamManagement(true, true);
pubsub = std::make_unique<gloox::PubSub::Manager>(gloox.get());
state = disconnected;
}
@ -65,6 +68,42 @@ void Connection::send(const std::string& jid, const std::string& body) {
gloox->send(gloox::Message(gloox::Message::Chat, jid, body));
}
void Connection::publish(const std::string& service, const std::string& node, const std::string& title, const std::string& body) {
debug("publishing an article \"" + title + "\" to " + node + "@" + service);
gloox::Tag* entry = new gloox::Tag("entry");
entry->setXmlns("http://www.w3.org/2005/Atom");
entry->addChild(new gloox::Tag("id", "urn:uuid:" + Shared::getUUID()));
entry->addChild(new gloox::Tag("title", title));
entry->addChild(new gloox::Tag("summary", body));
entry->addChild(new gloox::Tag("updated", Shared::getISOTimestamp()));
entry->addChild(new gloox::Tag("published", Shared::getISOTimestamp()));
gloox::PubSub::Item* item = new gloox::PubSub::Item();
item->setPayload(entry);
gloox::PubSub::ItemList list({item});
pubsub->publishItem(service, node, list, nullptr, this);
}
std::string Connection::errorTypeToString(gloox::StanzaErrorType err) {
switch (err) {
case gloox::StanzaErrorTypeAuth:
return "Authentication";
case gloox::StanzaErrorTypeCancel:
return "Cancel";
case gloox::StanzaErrorTypeContinue:
return "Continue";
case gloox::StanzaErrorTypeModify:
return "Modify";
case gloox::StanzaErrorTypeWait:
return "Wait";
case gloox::StanzaErrorTypeUndefined:
return "Undefined";
}
}
void Connection::handleMessage(const gloox::Message& message, gloox::MessageSession* session) {
if (message.subtype() != gloox::Message::Chat)
return;
@ -78,9 +117,21 @@ void Connection::handleMessage(const gloox::Message& message, gloox::MessageSess
core->router.routeMessage(jid, body);
}
void Connection::handleItemPublication(const std::string& id, const gloox::JID& service, const std::string& node, const gloox::PubSub::ItemList& itemList, const gloox::Error* err) {
std::string srv(node + "@" + service.full());
if (err) {
error("Publish failed to " + srv + ", Error: [" + errorTypeToString(err->type()) + "]");
return;
}
info("Publish successful to " + srv + ", ID: " + id);
}
void Connection::onConnect() {
info("connection established");
}
void Connection::onDisconnect(gloox::ConnectionError e) {
std::string error;
@ -148,6 +199,7 @@ void Connection::onDisconnect(gloox::ConnectionError e) {
else
Loggable::error("disconnected: " + error);
}
bool Connection::onTLSConnect(const gloox::CertInfo&) {
info("TLS established");
return true;

View File

@ -10,14 +10,19 @@
#include <gloox/disco.h>
#include <gloox/connectionlistener.h>
#include <gloox/messagehandler.h>
#include <gloox/pubsubmanager.h>
#include <gloox/pubsubitem.h>
#include <gloox/pubsubresulthandler.h>
#include "shared/loggable.h"
#include "shared/utils.h"
#include "component/core.h"
class Connection:
private Shared::Loggable,
public gloox::ConnectionListener,
public gloox::MessageHandler
public gloox::MessageHandler,
public gloox::PubSub::ResultHandler
{
public:
enum State {
@ -34,6 +39,9 @@ public:
void deinitialize();
void connect();
void send(const std::string& jid, const std::string& body);
void publish(const std::string& service, const std::string& node, const std::string& title, const std::string& body);
static std::string errorTypeToString(gloox::StanzaErrorType err);
public:
void onConnect() override;
@ -41,9 +49,33 @@ public:
bool onTLSConnect(const gloox::CertInfo&) override;
void handleMessage(const gloox::Message& message, gloox::MessageSession* session = 0) override;
void handleItemPublication(const std::string& id, const gloox::JID& service, const std::string& node, const gloox::PubSub::ItemList& itemList, const gloox::Error* error = 0) override;
// All other methods are not needed; make them no-op
void handleItem(const gloox::JID&, const std::string&, const gloox::Tag*) override {}
void handleItems(const std::string&, const gloox::JID&, const std::string&, const gloox::PubSub::ItemList&, const gloox::Error* = 0) override {}
void handleItemDeletion(const std::string&, const gloox::JID&, const std::string&, const gloox::PubSub::ItemList&, const gloox::Error* = 0) override {}
void handleSubscriptionResult(const std::string&, const gloox::JID&, const std::string&, const std::string&, const gloox::JID&, const gloox::PubSub::SubscriptionType, const gloox::Error* = 0) override {}
void handleUnsubscriptionResult(const std::string&, const gloox::JID&, const gloox::Error* = 0) override {}
void handleSubscriptionOptions(const std::string&, const gloox::JID&, const gloox::JID&, const std::string&, const gloox::DataForm*, const std::string& = gloox::EmptyString, const gloox::Error* = 0) override {}
void handleSubscriptionOptionsResult(const std::string&, const gloox::JID&, const gloox::JID&, const std::string&, const std::string& = gloox::EmptyString, const gloox::Error* = 0) override {}
void handleSubscribers(const std::string&, const gloox::JID&, const std::string&, const gloox::PubSub::SubscriptionList&, const gloox::Error* = 0) override {}
void handleSubscribersResult(const std::string&, const gloox::JID&, const std::string&, const gloox::PubSub::SubscriberList*, const gloox::Error* = 0) override {}
void handleAffiliates(const std::string&, const gloox::JID&, const std::string&, const gloox::PubSub::AffiliateList*, const gloox::Error* = 0) override {}
void handleAffiliatesResult(const std::string&, const gloox::JID&, const std::string&, const gloox::PubSub::AffiliateList*, const gloox::Error* = 0) override {}
void handleNodeConfig(const std::string&, const gloox::JID&, const std::string&, const gloox::DataForm*, const gloox::Error* = 0) override {}
void handleNodeConfigResult(const std::string&, const gloox::JID&, const std::string&, const gloox::Error* = 0) override {}
void handleNodeCreation(const std::string&, const gloox::JID&, const std::string&, const gloox::Error* = 0) override {}
void handleNodeDeletion(const std::string&, const gloox::JID&, const std::string&, const gloox::Error* = 0) override {}
void handleNodePurge(const std::string&, const gloox::JID&, const std::string&, const gloox::Error* = 0) override {}
void handleSubscriptions(const std::string&, const gloox::JID&, const gloox::PubSub::SubscriptionMap&, const gloox::Error* = 0) override {}
void handleAffiliations(const std::string&, const gloox::JID&, const gloox::PubSub::AffiliationMap&, const gloox::Error* = 0) override {}
void handleDefaultNodeConfig(const std::string&, const gloox::JID&, const gloox::DataForm*, const gloox::Error* = 0) override {}
private:
State state;
std::shared_ptr<Core> core;
std::unique_ptr<gloox::Client> gloox;
std::unique_ptr<gloox::PubSub::Manager> pubsub;
};

View File

@ -17,6 +17,12 @@ modules:
permissions:
read: [Owner, User]
write: [Owner]
publish:
alias: publish
enabled: true
permissions:
publish: [Owner]
replies:
success: []

View File

@ -4,6 +4,7 @@
#include "jay.h"
#include "module/actor.h"
#include "module/publish.h"
static const std::map<
std::string,
@ -17,7 +18,11 @@ static const std::map<
{"actor", [](
const std::shared_ptr<Core>& core,
const Shared::Permissions& permissions
) { return std::make_shared<Module::Actor>(core, permissions); }}
) { return std::make_shared<Module::Actor>(core, permissions); }},
{"publish", [](
const std::shared_ptr<Core>& core,
const Shared::Permissions& permissions
) { return std::make_shared<Module::Publish>(core, permissions); }}
};
Jay::Jay(const std::string& configPath):

View File

@ -1,11 +1,13 @@
set(SOURCES
module.cpp
actor.cpp
publish.cpp
)
set(HEADERS
module.h
actor.h
publish.h
)
target_sources(${EXEC_NAME} PRIVATE ${SOURCES})

View File

@ -23,21 +23,6 @@ bool Module::Module::hasPermission(const std::string& permission, const std::sha
Module::Module::~Module() noexcept {}
std::vector<std::string> Module::Module::split(const std::string& string, const std::string& delimiter) {
std::vector<std::string> result;
std::size_t last = 0;
std::size_t next = string.find(delimiter, last);
while (next != std::string::npos) {
result.emplace_back(string.substr(last, next - last));
last = next + 1;
next = string.find(delimiter, last);
}
result.emplace_back(string.substr(last));
return result;
}
std::string Module::Module::lower(const std::string& text) {
return std::ranges::to<std::string>(text | std::views::transform(::tolower));
}

View File

@ -26,7 +26,6 @@ protected:
public:
virtual ~Module() noexcept;
static Shared::Strings split(const std::string& string, const std::string& delimiter = " ");
static std::string lower(const std::string& text);
virtual Shared::Result message(const std::shared_ptr<::Actor>& actor, const Shared::Strings& args) = 0;

45
module/publish.cpp Normal file
View File

@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "publish.h"
#include <exception>
Module::Publish::Publish(const std::shared_ptr<Core>& core, const Shared::Permissions& permissions):
Module(core, permissions, "Actor")
{}
Module::Publish::~Publish() noexcept {}
Shared::Result Module::Publish::message(const std::shared_ptr<::Actor>& actor, const Shared::Strings& args) {
if (args.front() == "to") {
if (!hasPermission("publish", actor))
return Shared::forbidden;
if (args.size() < 3)
return Shared::error;
return to(args[1], args[2]);
}
return Shared::unhandled;
}
Shared::Result Module::Publish::to(const std::string& address, const std::string& body) {
Shared::Strings parts = Shared::split(address, "@");
if (parts.size() != 2) {
warn("Malformed address in \"to\" method");
return Shared::error;
}
try {
core->publish(parts[1], parts[0], "Completely testing stuff, early stages, ignore please", body);
return Shared::success;
} catch (const std::exception& e) {
error("Exception in \"to\" method: " + std::string(e.what()));
} catch (...) {
error("Unhandled exception in \"to\" method");
}
return Shared::error;
}

25
module/publish.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 "shared/definitions.h"
#include "shared/result.h"
#include "shared/utils.h"
#include "module.h"
namespace Module {
class Publish : public Module {
public:
Publish(const std::shared_ptr<Core>& core, const Shared::Permissions& permissions);
~Publish() noexcept;
virtual Shared::Result message(const std::shared_ptr<::Actor>& actor, const Shared::Strings& args) override;
private:
Shared::Result to(const std::string& address, const std::string& body);
};
}

View File

@ -1,6 +1,7 @@
set(SOURCES
logger.cpp
loggable.cpp
utils.cpp
)
set(HEADERS
@ -8,6 +9,7 @@ set(HEADERS
loggable.h
definitions.h
result.h
utils.h
)
target_sources(${EXEC_NAME} PRIVATE ${SOURCES})

32
shared/utils.cpp Normal file
View File

@ -0,0 +1,32 @@
#include "utils.h"
std::string Shared::getISOTimestamp() {
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds> time = std::chrono::floor<std::chrono::seconds>(now);
return std::format("{:%FT%TZ}", time);
}
std::string Shared::getUUID() {
uuid_t uuid;
uuid_generate_random(uuid);
char uuid_str[37];
uuid_unparse_lower(uuid, uuid_str);
return std::string(uuid_str);
}
Shared::Strings Shared::split(const std::string& string, const std::string& delimiter) {
Strings result;
std::size_t last = 0;
std::size_t next = string.find(delimiter, last);
while (next != std::string::npos) {
result.emplace_back(string.substr(last, next - last));
last = next + 1;
next = string.find(delimiter, last);
}
result.emplace_back(string.substr(last));
return result;
}

18
shared/utils.h Normal file
View File

@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <string>
#include <chrono>
#include <format>
#include <uuid/uuid.h>
#include "definitions.h"
namespace Shared {
std::string getISOTimestamp();
std::string getUUID();
Shared::Strings split(const std::string& string, const std::string& delimiter = " ");
}