This commit is contained in:
2025-08-11 21:44:15 -05:00
commit d83ff41d41
9 changed files with 43645 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
config.toml
build/**

35
CMakeLists.txt Normal file
View File

@@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.16)
project(notification-pusher LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
message(FATAL_ERROR "Please configure with Clang: cmake -DCMAKE_CXX_COMPILER=clang++ ..")
endif()
find_package(fmt REQUIRED)
find_package(spdlog REQUIRED)
find_package(PahoMqttCpp REQUIRED)
find_package(cpr REQUIRED)
add_executable(${PROJECT_NAME} src/main.cpp
src/definitions.cpp
src/daemon.cpp
)
target_compile_options(${PROJECT_NAME} PRIVATE
-Wall -Wextra -Wpedantic
-Wshadow -Wconversion
-O2
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
fmt::fmt
spdlog::spdlog
PahoMqttCpp::paho-mqttpp3
cpr::cpr
)

75
src/daemon.cpp Normal file
View File

@@ -0,0 +1,75 @@
#include "daemon.hpp"
#include "definitions.hpp"
#include "json.hpp"
#include <cpr/api.h>
#include <cpr/body.h>
#include <cpr/cprtypes.h>
#include <cpr/response.h>
#include <fmt/format.h>
#include <memory>
#include <mqtt/client.h>
#include <mqtt/exception.h>
#include <mqtt/message.h>
#include <spdlog/spdlog.h>
#include <thread>
#include <cpr/cpr.h>
namespace daemon_notify {
void start(std::unique_ptr<mqtt::client> client, std::string topic) {
spdlog::info("Entered the notification daemon thread");
// Subscribe and start consuming
client->subscribe(topic, 1);
std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Wait for SUBACK
client->start_consuming();
// Consume messages with manual timeout to avoid indefinite blocking
while (true) {
try {
mqtt::const_message_ptr potential_notification_message;
bool has_message = false;
auto start = std::chrono::steady_clock::now();
auto timeout = std::chrono::seconds(2);
while ((std::chrono::steady_clock::now() - start) < timeout) {
has_message = client->try_consume_message(&potential_notification_message);
if (has_message) break;
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
if (!has_message) {
spdlog::trace("No message received in this interval.");
continue;
}
spdlog::debug("Received mqtt message: {} on topic {}",
potential_notification_message->to_string(),
potential_notification_message->get_topic());
nlohmann::json parsed_json = nlohmann::json::parse(potential_notification_message->to_string());
definitions::Notification notif = parsed_json.get<definitions::Notification>();
cpr::Response notification_post_request = cpr::Post(
cpr::Url{fmt::format("https://ntfy.sh/{}", notif.topic)},
cpr::Header{{"Title", notif.title}},
cpr::Body{notif.body}
);
spdlog::debug("Status code: {}", notification_post_request.status_code);
}
catch (const mqtt::exception& e) {
spdlog::warn("Encountered mqtt exception: {}", e.what());
}
catch (const nlohmann::json::parse_error& e) {
spdlog::warn("Encountered JSON parsing exception: {}", e.what());
}
catch (const std::exception& e) {
spdlog::warn("Encountered std exception: {}", e.what());
}
}
client->disconnect();
}
}

6
src/daemon.hpp Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
#include <memory>
#include <mqtt/client.h>
namespace daemon_notify{
void start(std::unique_ptr<mqtt::client> client, std::string topic);
}

5
src/definitions.cpp Normal file
View File

@@ -0,0 +1,5 @@
#include "definitions.hpp"
namespace definitions{
}

12
src/definitions.hpp Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include "json.hpp"
namespace definitions{
struct Notification{
std::string title;
std::string body;
std::string topic;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Notification, title, body, topic)
}

25526
src/json.hpp Normal file

File diff suppressed because it is too large Load Diff

104
src/main.cpp Normal file
View File

@@ -0,0 +1,104 @@
#include <algorithm>
#include <fmt/core.h>
#include <fmt/format.h>
#include <functional>
#include <memory>
#include <spdlog/common.h>
#include <spdlog/spdlog.h>
#include <mqtt/client.h>
#include <string>
#include <sys/types.h>
#include <thread>
#include <utility>
#include "daemon.hpp"
#include "toml.hpp"
int main() {
try {
spdlog::set_level(spdlog::level::debug);
// Parse config.toml
auto config = toml::parse_file("config.toml");
uint client_counter = {0};
// Retrieve broker settings
std::string server_address = config["broker"]["address"].value_or("");
std::string client_id = config["broker"]["client_id"].value_or("");
std::string topic = config["broker"]["topic"].value_or("");
// Retrieve auth settings
std::string username = config["auth"]["username"].value_or("");
std::string password = config["auth"]["password"].value_or("");
if (server_address.empty() || client_id.empty() || topic.empty()) {
spdlog::error("Missing broker configuration in config.toml");
return 1;
}
// Create MQTT client
mqtt::client client(server_address, fmt::format("{}-{}",client_id,client_counter++));
// Configure connection with login
mqtt::connect_options connOpts;
connOpts.set_clean_session(true);
if (!username.empty()) {
connOpts.set_user_name(username);
connOpts.set_password(password);
}
spdlog::info("Connecting to broker at {} with client ID '{}'", server_address, client_id);
client.connect(connOpts);
spdlog::info("Connected successfully.");
// Subscribe
client.subscribe(topic, 1);
spdlog::info("Subscribed to topic '{}'", topic);
// Publish a test message
auto message = fmt::format("Hello from {}", client_id);
client.publish(topic, message.data(), message.size(), 1, false);
spdlog::info("Published message: {}", message);
// Receive one message
spdlog::info("Waiting for incoming message...");
auto msg = client.consume_message();
if (msg) {
spdlog::info("Received message on topic '{}': {}",
msg->get_topic(), msg->to_string());
} else {
spdlog::warn("No message received.");
}
// Disconnect
client.disconnect();
spdlog::info("Disconnected from broker for the test attempt.");
// Start Notification Daemon (listen to that one topic, and then write to ntfy.sh)
spdlog::info("Starting notification daemon");
std::unique_ptr<mqtt::client> daemon_mqtt_client = std::make_unique<mqtt::client>(server_address, fmt::format("{}-{}", client_id, client_counter++));
daemon_mqtt_client->connect(connOpts);
std::thread daemon_thread(
[client = std::move(daemon_mqtt_client), topic]() mutable {
daemon_notify::start(std::move(client), topic);
}
);
daemon_thread.join();
}
catch (const toml::parse_error& err) {
spdlog::error("TOML parse error: {}", err.description());
return 1;
}
catch (const mqtt::exception& e) {
spdlog::error("MQTT Error: {}", e.what());
return 1;
}
catch (const std::exception& e) {
spdlog::error("Error: {}", e.what());
return 1;
}
return 0;
}

17880
src/toml.hpp Normal file

File diff suppressed because it is too large Load Diff