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]() { client->sig_registered.connect([client, webhook]() {
webhook->set_connection(connection); webhook->set_client(client);
}); });
// On disconnect reconnect in 5 seconds // On disconnect reconnect in 5 seconds
// connection is captured in the disconnect handler so it can keep itself alive // connection is captured in the disconnect handler so it can keep itself alive
connection->sig_disconnect.connect( connection->sig_disconnect.connect(
[&io, &settings, connection, webhook]() { [&io, &settings, connection, webhook]() {
webhook->clear_connection(); webhook->clear_client();
auto timer = std::make_shared<boost::asio::steady_timer>(io); auto timer = std::make_shared<boost::asio::steady_timer>(io);
timer->expires_after(5s); timer->expires_after(5s);
timer->async_wait([&io, &settings, timer, webhook](auto) { start_irc(io, settings, webhook); }); 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 const char *webhook_settings_filename
) -> std::shared_ptr<Webhooks> ) -> std::shared_ptr<Webhooks>
{ {
std::ifstream webhook_settings_file{webhook_settings_filename}; auto webhook = std::make_shared<Webhooks>(webhook_settings_filename);
if (!webhook_settings_file) webhook->load_settings();
{
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);
boost::asio::co_spawn(io, spawn_webhook(io, webhook), report_error); boost::asio::co_spawn(io, spawn_webhook(io, webhook), report_error);
return webhook; 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 auto Webhooks::save_settings() const -> void
{ {
std::ofstream webhook_settings_file{settings_file}; std::ofstream webhook_settings_file{settings_filename_};
if (!webhook_settings_file) if (!webhook_settings_file)
{ {
BOOST_LOG_TRIVIAL(error) << "Unable to open webhook settings file"; BOOST_LOG_TRIVIAL(error) << "Unable to open webhook settings file";
return; return;
} }
webhook_settings_file << settings_.to_toml() << "\n";
webhook_settings_file << settings_.to_toml();
} }
auto ProjectSettings::from_toml(const toml::table &v) -> ProjectSettings 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 // 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 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 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_)) for (auto &&[target, message] : std::move(events_))
{ {
connection_->send_notice(target, message); client_->get_connection().send_notice(target, message);
} }
events_.clear(); 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 static auto reply_to(std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd, std::string message) -> void
@ -460,171 +461,124 @@ static auto authorized_for_project(
} }
std::map<std::string, void (*)(std::shared_ptr<Webhooks>, const myirc::Bot::Command &)> webhook_commands{ 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) { {"announce", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
if (cmd.oper.empty())
{
return;
}
std::istringstream iss{std::string{cmd.arguments}}; std::istringstream iss{std::string{cmd.arguments}};
std::string name, key; std::string name, mode;
if (iss >> name >> key) if (iss >> name >> mode)
{
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)
{ {
auto &project = webhooks->settings_.projects.at(name); auto &project = webhooks->settings_.projects.at(name);
if (not authorized_for_project(cmd, project, cmd.account)) if (not authorized_for_project(cmd, project, cmd.account))
{ {
return; return;
} }
project.enabled = true; if (mode == "on") {
webhooks->save_settings(); project.enabled = true;
reply_to(webhooks, cmd, "Enabled project " + name); reply_to(webhooks, cmd, "Enabled project " + name);
} } else if (mode == "off") {
}}, project.enabled = false;
{"disable-project", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) { reply_to(webhooks, cmd, "Disabled project " + name);
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> 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> 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> 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> 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 { } else {
reply_to(webhooks, cmd, "Access not found"); return;
} }
webhooks->save_settings();
} }
}}, }},
{"set-channel", [](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, 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> 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> webhooks, const myirc::Bot::Command &cmd) {
if (cmd.oper.empty()) if (cmd.oper.empty())
{ {
return; return;
@ -639,4 +593,12 @@ std::map<std::string, void (*)(std::shared_ptr<Webhooks>, const myirc::Bot::Comm
reply_to(webhooks, cmd, "Channel assigned"); 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 #pragma once
#include <myirc/bot.hpp> #include <myirc/bot.hpp>
#include <myirc/connection.hpp> #include <myirc/client.hpp>
#include <toml++/toml.hpp> #include <toml++/toml.hpp>
@ -49,27 +49,27 @@ struct WebhookSettings {
class Webhooks { class Webhooks {
// IRC connection to announce on; could be empty // 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 // Buffered events in case connection was inactive when event was received
std::vector<std::pair<std::string, std::string>> events_; std::vector<std::pair<std::string, std::string>> events_;
const char * settings_file; const char * settings_filename_;
public: public:
WebhookSettings settings_; WebhookSettings settings_;
Webhooks(WebhookSettings settings, const char * settings_file) Webhooks(const char * settings_filename)
: settings_(std::move(settings)) : settings_filename_{settings_filename}
, settings_file(settings_file)
{ {
} }
// Either emit the event now or save it until a connection is set // Either emit the event now or save it until a connection is set
auto send_notice(std::string_view, std::string) -> void; auto send_notice(std::string_view, std::string) -> void;
auto set_connection(std::shared_ptr<myirc::Connection> connection) -> void; auto set_client(std::shared_ptr<myirc::Client> client) -> void;
auto clear_connection() -> void; auto clear_client() -> void;
auto save_settings() const -> void; auto save_settings() const -> void;
auto load_settings() -> void;
}; };
auto start_webhook(boost::asio::io_context &io, const char *) -> std::shared_ptr<Webhooks>; auto start_webhook(boost::asio::io_context &io, const char *) -> std::shared_ptr<Webhooks>;