consolidate command interface

This commit is contained in:
Eric Mertens 2025-02-03 09:35:50 -08:00
parent 5aec8397bb
commit 5f2439e5af
3 changed files with 148 additions and 186 deletions

View File

@ -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<boost::asio::steady_timer>(io);
timer->expires_after(5s);
timer->async_wait([&io, &settings, timer, webhook](auto) { start_irc(io, settings, webhook); });

View File

@ -269,31 +269,32 @@ auto start_webhook(
const char *webhook_settings_filename
) -> std::shared_ptr<Webhooks>
{
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<Webhooks>(std::move(settings), webhook_settings_filename);
auto webhook = std::make_shared<Webhooks>(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<myirc::Connection> connection) -> void
auto Webhooks::set_client(std::shared_ptr<myirc::Client> 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> webhooks, const myirc::Bot::Command &cmd, std::string message) -> void
@ -460,68 +461,32 @@ static auto authorized_for_project(
}
std::map<std::string, void (*)(std::shared_ptr<Webhooks>, const myirc::Bot::Command &)> webhook_commands{
{"add-credential", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
if (cmd.oper.empty())
{
return;
}
{"announce", [](std::shared_ptr<Webhooks> 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> 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> 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;
}
if (mode == "on") {
project.enabled = true;
webhooks->save_settings();
reply_to(webhooks, cmd, "Enabled project " + name);
}
}},
{"disable-project", [](std::shared_ptr<Webhooks> 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))
{
} else if (mode == "off") {
project.enabled = false;
reply_to(webhooks, cmd, "Disabled project " + name);
} else {
return;
}
project.enabled = false;
webhooks->save_settings();
reply_to(webhooks, cmd, "Disabled project " + name);
}
}},
{"add-events", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
{"event", [](std::shared_ptr<Webhooks> 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))
@ -529,7 +494,18 @@ std::map<std::string, void (*)(std::shared_ptr<Webhooks>, const myirc::Bot::Comm
return;
}
unsigned n_added = 0, n_skipped = 0, n_unknown = 0;
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);
@ -538,29 +514,7 @@ std::map<std::string, void (*)(std::shared_ptr<Webhooks>, const myirc::Bot::Comm
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> 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;
} else if (mode == "del") {
while (iss >> name) {
if (formatters.contains(name)) {
const auto removed = project.events.erase(name);
@ -569,62 +523,62 @@ std::map<std::string, void (*)(std::shared_ptr<Webhooks>, const myirc::Bot::Comm
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; }
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());
}
}},
{"add-access", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
{"auth", [](std::shared_ptr<Webhooks> 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)
std::string name, mode;
if (iss >> name >> mode)
{
auto &project = webhooks->settings_.projects.at(name);
if (not authorized_for_project(cmd, project, cmd.account))
{
if (mode == "list") {
std::stringstream ss;
ss << "Authorized accounts:";
for (auto &&event : project.authorized_accounts) {
ss << " " << event;
}
reply_to(webhooks, cmd, ss.str());
return;
}
auto [_, inserted] = project.authorized_accounts.insert(account);
if (inserted) {
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();
reply_to(webhooks, cmd, "Access added");
}
else
{
reply_to(webhooks, cmd, "Access already set");
}
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());
}
}},
{"drop-access", [](std::shared_ptr<Webhooks> 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");
} else {
reply_to(webhooks, cmd, "Access not found");
}
}
}},
{"set-channel", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
{"setchannel", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
if (cmd.oper.empty())
{
return;
@ -639,4 +593,12 @@ std::map<std::string, void (*)(std::shared_ptr<Webhooks>, const myirc::Bot::Comm
reply_to(webhooks, cmd, "Channel assigned");
}
}},
{"rehash", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
if (cmd.oper.empty())
{
return;
}
webhooks->load_settings();
reply_to(webhooks, cmd, "Rehashed");
}},
};

View File

@ -1,7 +1,7 @@
#pragma once
#include <myirc/bot.hpp>
#include <myirc/connection.hpp>
#include <myirc/client.hpp>
#include <toml++/toml.hpp>
@ -49,27 +49,27 @@ struct WebhookSettings {
class Webhooks {
// IRC connection to announce on; could be empty
std::shared_ptr<myirc::Connection> connection_;
std::shared_ptr<myirc::Client> client_;
// Buffered events in case connection was inactive when event was received
std::vector<std::pair<std::string, std::string>> 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<myirc::Connection> connection) -> void;
auto clear_connection() -> void;
auto set_client(std::shared_ptr<myirc::Client> 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<Webhooks>;