diff --git a/driver/main.cpp b/driver/main.cpp index 2a0d317..388f5e7 100644 --- a/driver/main.cpp +++ b/driver/main.cpp @@ -114,12 +114,11 @@ static auto start( } ); - // Simple example of a command handler - bot->sig_command.connect([connection](const Bot::Command &cmd) { - if (cmd.oper == "glguy" && cmd.command == "ping") { - if (auto bang = cmd.source.find('!'); bang != cmd.source.npos) { - connection->send_notice(cmd.source.substr(0, bang), cmd.arguments); - } + // Dispatch commands to the webhook logic + bot->sig_command.connect([webhook, connection](const Bot::Command &cmd) { + auto cursor = webhook_commands.find(std::string{cmd.command}); + if (cursor != webhook_commands.end()) { + cursor->second(webhook, cmd); } }); diff --git a/driver/web.cpp b/driver/web.cpp index 40851fc..9d10ad8 100644 --- a/driver/web.cpp +++ b/driver/web.cpp @@ -252,14 +252,21 @@ auto start_webhook( 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)); + auto webhook = std::make_shared(std::move(settings), webhook_settings_filename); boost::asio::co_spawn(io, spawn_webhook(io, webhook), report_error); return webhook; } -auto GithubWebhook::write_event(WebhookEvent event) -> void +auto GithubWebhook::save_settings() const -> void { - connection_->send_notice("glguy", event.channel + ": " + event.message); + std::ofstream webhook_settings_file{settings_file}; + if (!webhook_settings_file) + { + BOOST_LOG_TRIVIAL(error) << "Unable to open webhook settings file"; + return; + } + + webhook_settings_file << settings_.to_toml(); } auto ProjectSettings::from_toml(const toml::table &v) -> ProjectSettings @@ -374,3 +381,133 @@ auto WebhookSettings::to_toml() const -> toml::table {"projects", std::move(project_tables)} }; } + +// Either emit the event now or save it until a connection is set +auto GithubWebhook::add_event(Notice notice) -> void +{ + if (connection_) + { + connection_->send_notice(notice.target, notice.message); + } + else + { + events_.emplace_back(std::move(notice)); + } +} + +auto GithubWebhook::set_connection(std::shared_ptr connection) -> void +{ + connection_ = std::move(connection); + for (auto &&event : events_) + { + connection_->send_notice(event.target, event.message); + } + events_.clear(); +} + +auto GithubWebhook::clear_connection() -> void +{ + connection_.reset(); +} + +static auto reply_to(std::shared_ptr webhooks, const myirc::Bot::Command &cmd, std::string message) -> void +{ + if (cmd.target.starts_with("#")) + { + webhooks->add_event({std::string{cmd.target}, std::move(message)}); + } + else + { + webhooks->add_event({std::string{cmd.nick()}, std::move(message)}); + } +} + +static auto authorized_for_project(const ProjectSettings &project, const std::string_view nick) -> bool +{ + return project.authorized_accounts.find(std::string{nick}) != project.authorized_accounts.end(); +} + +std::map, const myirc::Bot::Command &)> webhook_commands{ + {"add-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, 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) + { + auto cursor = webhooks->settings_.projects.find(name); + if (cursor == webhooks->settings_.projects.end()) + { + reply_to(webhooks, cmd, "Unknown project " + name); + return; + } + + auto &project = cursor->second; + + if (not authorized_for_project(project, cmd.account)) + { + reply_to(webhooks, cmd, "Unauthorized to enable project " + name); + 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) { + //if (cmd.oper.empty()) + //{ + // return; + //} + std::istringstream iss{std::string{cmd.arguments}}; + std::string name; + if (iss >> name) + { + auto cursor = webhooks->settings_.projects.find(name); + if (cursor == webhooks->settings_.projects.end()) + { + webhooks->add_event({std::string{cmd.nick()}, "Unknown project " + name}); + return; + } + + auto &project = cursor->second; + + if (not authorized_for_project(project, cmd.account)) + { + reply_to(webhooks, cmd, "Unauthorized to disable project " + name); + return; + } + + project.enabled = false; + webhooks->save_settings(); + reply_to(webhooks, cmd, "Disabled project " + name); + } + }}, +}; diff --git a/driver/web.hpp b/driver/web.hpp index 656a31a..28186c0 100644 --- a/driver/web.hpp +++ b/driver/web.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -46,55 +47,38 @@ struct WebhookSettings { static auto from_toml(const toml::table &v) -> WebhookSettings; }; -struct WebhookEvent { - std::string channel; - std::string message; -}; - class GithubWebhook { +public: + struct Notice { + std::string target; + std::string message; + }; +private: + // IRC connection to announce on; could be empty std::shared_ptr connection_; // Buffered events in case connection was inactive when event was received - std::vector events_; + std::vector events_; - - // Actually write the event to the connection. - // Only call when there is a connection. - auto write_event(WebhookEvent event) -> void; + const char * settings_file; public: WebhookSettings settings_; - GithubWebhook(WebhookSettings settings) + GithubWebhook(WebhookSettings settings, const char * settings_file) : settings_(std::move(settings)) + , settings_file(settings_file) { } // Either emit the event now or save it until a connection is set - auto add_event(WebhookEvent event) -> void - { - if (connection_) { - write_event(std::move(event)); - } else { - events_.emplace_back(std::move(event)); - } - } - - auto set_connection(std::shared_ptr connection) -> void - { - connection_ = std::move(connection); - for (auto &&event : events_) - { - write_event(event); - } - events_.clear(); - } - - auto clear_connection() -> void - { - connection_.reset(); - } + auto add_event(Notice event) -> void; + auto set_connection(std::shared_ptr connection) -> void; + auto clear_connection() -> void; + auto save_settings() const -> void; }; auto start_webhook(boost::asio::io_context &io, const char *) -> std::shared_ptr; + +extern std::map, const myirc::Bot::Command &)> webhook_commands; diff --git a/myirc/include/myirc/bot.hpp b/myirc/include/myirc/bot.hpp index 74d33a8..b02cb81 100644 --- a/myirc/include/myirc/bot.hpp +++ b/myirc/include/myirc/bot.hpp @@ -18,6 +18,15 @@ struct Bot : std::enable_shared_from_this std::string_view account; std::string_view command; std::string_view arguments; + + auto nick() const -> std::string_view { + auto bang = source.find('!'); + if (bang == std::string::npos) { + return ""; + } else { + return source.substr(0, bang); + } + } }; std::shared_ptr self_;