From 5f2439e5af03760bdebe4f2db62d9ab2c721e485 Mon Sep 17 00:00:00 2001 From: Eric Mertens Date: Mon, 3 Feb 2025 09:35:50 -0800 Subject: [PATCH] consolidate command interface --- driver/main.cpp | 6 +- driver/web.cpp | 312 +++++++++++++++++++++--------------------------- driver/web.hpp | 16 +-- 3 files changed, 148 insertions(+), 186 deletions(-) diff --git a/driver/main.cpp b/driver/main.cpp index 91f6394..c48c0a5 100644 --- a/driver/main.cpp +++ b/driver/main.cpp @@ -67,15 +67,15 @@ static auto start_irc( } } - client->sig_registered.connect([connection, webhook]() { - webhook->set_connection(connection); + client->sig_registered.connect([client, webhook]() { + webhook->set_client(client); }); // On disconnect reconnect in 5 seconds // connection is captured in the disconnect handler so it can keep itself alive connection->sig_disconnect.connect( [&io, &settings, connection, webhook]() { - webhook->clear_connection(); + webhook->clear_client(); auto timer = std::make_shared(io); timer->expires_after(5s); timer->async_wait([&io, &settings, timer, webhook](auto) { start_irc(io, settings, webhook); }); diff --git a/driver/web.cpp b/driver/web.cpp index 0450a5e..5df8a14 100644 --- a/driver/web.cpp +++ b/driver/web.cpp @@ -269,31 +269,32 @@ auto start_webhook( const char *webhook_settings_filename ) -> std::shared_ptr { - std::ifstream webhook_settings_file{webhook_settings_filename}; - if (!webhook_settings_file) - { - BOOST_LOG_TRIVIAL(error) << "Unable to open webhook settings file"; - std::exit(1); - } - - auto webhook_settings = toml::parse(webhook_settings_file); - WebhookSettings settings = WebhookSettings::from_toml(webhook_settings); - BOOST_LOG_TRIVIAL(info) << "Webhook settings: " << settings.to_toml(); - auto webhook = std::make_shared(std::move(settings), webhook_settings_filename); + auto webhook = std::make_shared(webhook_settings_filename); + webhook->load_settings(); boost::asio::co_spawn(io, spawn_webhook(io, webhook), report_error); return webhook; } +auto Webhooks::load_settings() -> void +{ + std::ifstream webhook_settings_file{settings_filename_}; + if (!webhook_settings_file) + { + BOOST_LOG_TRIVIAL(error) << "Unable to open webhook settings file"; + } + auto webhook_settings = toml::parse(webhook_settings_file); + settings_ = WebhookSettings::from_toml(webhook_settings); +} + auto Webhooks::save_settings() const -> void { - std::ofstream webhook_settings_file{settings_file}; + std::ofstream webhook_settings_file{settings_filename_}; if (!webhook_settings_file) { BOOST_LOG_TRIVIAL(error) << "Unable to open webhook settings file"; return; } - - webhook_settings_file << settings_.to_toml(); + webhook_settings_file << settings_.to_toml() << "\n"; } auto ProjectSettings::from_toml(const toml::table &v) -> ProjectSettings @@ -412,9 +413,9 @@ auto WebhookSettings::to_toml() const -> toml::table // Either emit the event now or save it until a connection is set auto Webhooks::send_notice(std::string_view target, std::string message) -> void { - if (connection_) + if (client_) { - connection_->send_notice(target, message); + client_->get_connection().send_notice(target, message); } else { @@ -422,19 +423,19 @@ auto Webhooks::send_notice(std::string_view target, std::string message) -> void } } -auto Webhooks::set_connection(std::shared_ptr connection) -> void +auto Webhooks::set_client(std::shared_ptr client) -> void { - connection_ = std::move(connection); + client_ = std::move(client); for (auto &&[target, message] : std::move(events_)) { - connection_->send_notice(target, message); + client_->get_connection().send_notice(target, message); } events_.clear(); } -auto Webhooks::clear_connection() -> void +auto Webhooks::clear_client() -> void { - connection_.reset(); + client_.reset(); } static auto reply_to(std::shared_ptr webhooks, const myirc::Bot::Command &cmd, std::string message) -> void @@ -460,171 +461,124 @@ static auto authorized_for_project( } std::map, const myirc::Bot::Command &)> webhook_commands{ - {"add-credential", [](std::shared_ptr webhooks, const myirc::Bot::Command &cmd) { - if (cmd.oper.empty()) - { - return; - } + {"announce", [](std::shared_ptr webhooks, const myirc::Bot::Command &cmd) { std::istringstream iss{std::string{cmd.arguments}}; - std::string name, key; - if (iss >> name >> key) - { - webhooks->settings_.credentials.insert_or_assign(name, key); - webhooks->save_settings(); - reply_to(webhooks, cmd, "Added credential " + name); - } - }}, - {"drop-credential", [](std::shared_ptr webhooks, const myirc::Bot::Command &cmd) { - if (cmd.oper.empty()) - { - return; - } - std::istringstream iss{std::string{cmd.arguments}}; - std::string name; - if (iss >> name) - { - webhooks->settings_.credentials.erase(name); - webhooks->save_settings(); - reply_to(webhooks, cmd, "Dropped credential " + name); - } - }}, - {"enable-project", [](std::shared_ptr webhooks, const myirc::Bot::Command &cmd) { - std::istringstream iss{std::string{cmd.arguments}}; - std::string name; - if (iss >> name) + std::string name, mode; + if (iss >> name >> mode) { auto &project = webhooks->settings_.projects.at(name); if (not authorized_for_project(cmd, project, cmd.account)) { return; } - project.enabled = true; - webhooks->save_settings(); - reply_to(webhooks, cmd, "Enabled project " + name); - } - }}, - {"disable-project", [](std::shared_ptr webhooks, const myirc::Bot::Command &cmd) { - std::istringstream iss{std::string{cmd.arguments}}; - std::string name; - if (iss >> name) - { - auto &project = webhooks->settings_.projects.at(name); - if (not authorized_for_project(cmd, project, cmd.account)) - { - return; - } - project.enabled = false; - webhooks->save_settings(); - reply_to(webhooks, cmd, "Disabled project " + name); - } - }}, - {"add-events", [](std::shared_ptr webhooks, const myirc::Bot::Command &cmd) { - std::istringstream iss{std::string{cmd.arguments}}; - std::string name; - if (iss >> name) - { - auto &project = webhooks->settings_.projects.at(name); - if (not authorized_for_project(cmd, project, cmd.account)) - { - return; - } - - unsigned n_added = 0, n_skipped = 0, n_unknown = 0; - while (iss >> name) { - if (formatters.contains(name)) { - const auto [_, added] = project.events.insert(name); - if (added) { n_added++; } else { n_skipped++; } - } else { - n_unknown++; - } - } - - webhooks->save_settings(); - - std::stringstream ss; - ss << "Events updated."; - if (n_added) { ss << " added: " << n_added; } - if (n_skipped) { ss << " skipped: " << n_skipped; } - if (n_unknown) { ss << " unknown: " << n_unknown; } - reply_to(webhooks, cmd, ss.str()); - } - }}, - {"drop-events", [](std::shared_ptr webhooks, const myirc::Bot::Command &cmd) { - std::istringstream iss{std::string{cmd.arguments}}; - std::string name; - if (iss >> name) - { - auto &project = webhooks->settings_.projects.at(name); - if (not authorized_for_project(cmd, project, cmd.account)) - { - return; - } - - unsigned n_removed = 0, n_skipped = 0, n_unknown = 0; - while (iss >> name) { - if (formatters.contains(name)) { - const auto removed = project.events.erase(name); - if (removed) { n_removed++; } else { n_skipped++; } - } else { - n_unknown++; - } - } - - webhooks->save_settings(); - - std::stringstream ss; - ss << "Events updated."; - if (n_removed) { ss << " removed: " << n_removed; } - if (n_skipped) { ss << " skipped: " << n_skipped; } - if (n_unknown) { ss << " unknown: " << n_unknown; } - reply_to(webhooks, cmd, ss.str()); - } - }}, - {"add-access", [](std::shared_ptr webhooks, const myirc::Bot::Command &cmd) { - if (cmd.oper.empty()) - { - return; - } - std::istringstream iss{std::string{cmd.arguments}}; - std::string name, account; - if (iss >> name >> account) - { - auto &project = webhooks->settings_.projects.at(name); - if (not authorized_for_project(cmd, project, cmd.account)) - { - return; - } - auto [_, inserted] = project.authorized_accounts.insert(account); - if (inserted) { - webhooks->save_settings(); - reply_to(webhooks, cmd, "Access added"); - } - else - { - reply_to(webhooks, cmd, "Access already set"); - } - } - }}, - {"drop-access", [](std::shared_ptr webhooks, const myirc::Bot::Command &cmd) { - if (cmd.oper.empty()) - { - return; - } - std::istringstream iss{std::string{cmd.arguments}}; - std::string name, account; - if (iss >> name >> account) - { - auto &project = webhooks->settings_.projects.at(name); - auto removed = project.authorized_accounts.erase(account); - if (removed) { - webhooks->save_settings(); - reply_to(webhooks, cmd, "Access dropped"); + if (mode == "on") { + project.enabled = true; + reply_to(webhooks, cmd, "Enabled project " + name); + } else if (mode == "off") { + project.enabled = false; + reply_to(webhooks, cmd, "Disabled project " + name); } else { - reply_to(webhooks, cmd, "Access not found"); + return; } + webhooks->save_settings(); } }}, - {"set-channel", [](std::shared_ptr webhooks, const myirc::Bot::Command &cmd) { + {"event", [](std::shared_ptr webhooks, const myirc::Bot::Command &cmd) { + std::istringstream iss{std::string{cmd.arguments}}; + std::string name, mode; + if (iss >> name >> mode) + { + auto &project = webhooks->settings_.projects.at(name); + if (not authorized_for_project(cmd, project, cmd.account)) + { + return; + } + + if (mode == "list") { + std::stringstream ss; + ss << "Events for " << name << ":"; + for (auto &&event : project.events) { + ss << " " << event; + } + reply_to(webhooks, cmd, ss.str()); + return; + } + + unsigned n_added = 0, n_removed = 0, n_skipped = 0, n_unknown = 0; + if (mode == "add") { + while (iss >> name) { + if (formatters.contains(name)) { + const auto [_, added] = project.events.insert(name); + if (added) { n_added++; } else { n_skipped++; } + } else { + n_unknown++; + } + } + } else if (mode == "del") { + while (iss >> name) { + if (formatters.contains(name)) { + const auto removed = project.events.erase(name); + if (removed) { n_removed++; } else { n_skipped++; } + } else { + n_unknown++; + } + } + } + webhooks->save_settings(); + + std::stringstream ss; + ss << "Events updated:"; + if (n_added) { ss << " added " << n_added; } + if (n_removed) { ss << " removed " << n_removed; } + if (n_skipped) { ss << " skipped " << n_skipped; } + if (n_unknown) { ss << " unknown " << n_unknown; } + reply_to(webhooks, cmd, ss.str()); + } + }}, + {"auth", [](std::shared_ptr webhooks, const myirc::Bot::Command &cmd) { + if (cmd.oper.empty()) + { + return; + } + std::istringstream iss{std::string{cmd.arguments}}; + std::string name, mode; + if (iss >> name >> mode) + { + auto &project = webhooks->settings_.projects.at(name); + + if (mode == "list") { + std::stringstream ss; + ss << "Authorized accounts:"; + for (auto &&event : project.authorized_accounts) { + ss << " " << event; + } + reply_to(webhooks, cmd, ss.str()); + return; + } + + unsigned n_added = 0, n_removed = 0, n_skipped = 0; + if (mode == "add") { + while (iss >> name) { + const auto [_, added] = project.authorized_accounts.insert(name); + if (added) { n_added++; } else { n_skipped++; } + } + } else if (mode == "del") { + while (iss >> name) { + const auto removed = project.authorized_accounts.erase(name); + if (removed) { n_removed++; } else { n_skipped++; } + } + } + webhooks->save_settings(); + + std::stringstream ss; + ss << "Authorized accounts updated:"; + if (n_added) { ss << " added " << n_added; } + if (n_removed) { ss << " removed " << n_removed; } + if (n_skipped) { ss << " skipped " << n_skipped; } + reply_to(webhooks, cmd, ss.str()); + } + }}, + {"setchannel", [](std::shared_ptr webhooks, const myirc::Bot::Command &cmd) { if (cmd.oper.empty()) { return; @@ -639,4 +593,12 @@ std::map, const myirc::Bot::Comm reply_to(webhooks, cmd, "Channel assigned"); } }}, + {"rehash", [](std::shared_ptr webhooks, const myirc::Bot::Command &cmd) { + if (cmd.oper.empty()) + { + return; + } + webhooks->load_settings(); + reply_to(webhooks, cmd, "Rehashed"); + }}, }; diff --git a/driver/web.hpp b/driver/web.hpp index 020240f..e1a4792 100644 --- a/driver/web.hpp +++ b/driver/web.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include @@ -49,27 +49,27 @@ struct WebhookSettings { class Webhooks { // IRC connection to announce on; could be empty - std::shared_ptr connection_; + std::shared_ptr client_; // Buffered events in case connection was inactive when event was received std::vector> events_; - const char * settings_file; + const char * settings_filename_; public: WebhookSettings settings_; - Webhooks(WebhookSettings settings, const char * settings_file) - : settings_(std::move(settings)) - , settings_file(settings_file) + Webhooks(const char * settings_filename) + : settings_filename_{settings_filename} { } // Either emit the event now or save it until a connection is set auto send_notice(std::string_view, std::string) -> void; - auto set_connection(std::shared_ptr connection) -> void; - auto clear_connection() -> void; + auto set_client(std::shared_ptr client) -> void; + auto clear_client() -> void; auto save_settings() const -> void; + auto load_settings() -> void; }; auto start_webhook(boost::asio::io_context &io, const char *) -> std::shared_ptr;