implement a few commands

This commit is contained in:
Eric Mertens 2025-02-02 14:56:34 -08:00
parent 8c9678708b
commit 178d7dfcfe
4 changed files with 172 additions and 43 deletions

View File

@ -114,12 +114,11 @@ static auto start(
} }
); );
// Simple example of a command handler // Dispatch commands to the webhook logic
bot->sig_command.connect([connection](const Bot::Command &cmd) { bot->sig_command.connect([webhook, connection](const Bot::Command &cmd) {
if (cmd.oper == "glguy" && cmd.command == "ping") { auto cursor = webhook_commands.find(std::string{cmd.command});
if (auto bang = cmd.source.find('!'); bang != cmd.source.npos) { if (cursor != webhook_commands.end()) {
connection->send_notice(cmd.source.substr(0, bang), cmd.arguments); cursor->second(webhook, cmd);
}
} }
}); });

View File

@ -252,14 +252,21 @@ auto start_webhook(
auto webhook_settings = toml::parse(webhook_settings_file); auto webhook_settings = toml::parse(webhook_settings_file);
WebhookSettings settings = WebhookSettings::from_toml(webhook_settings); WebhookSettings settings = WebhookSettings::from_toml(webhook_settings);
BOOST_LOG_TRIVIAL(info) << "Webhook settings: " << settings.to_toml(); BOOST_LOG_TRIVIAL(info) << "Webhook settings: " << settings.to_toml();
auto webhook = std::make_shared<GithubWebhook>(std::move(settings)); auto webhook = std::make_shared<GithubWebhook>(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 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 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)} {"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<myirc::Connection> 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<GithubWebhook> 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<std::string, void (*)(std::shared_ptr<GithubWebhook>, const myirc::Bot::Command &)> webhook_commands{
{"add-credential", [](std::shared_ptr<GithubWebhook> 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<GithubWebhook> 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<GithubWebhook> 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<GithubWebhook> 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);
}
}},
};

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <myirc/bot.hpp>
#include <myirc/connection.hpp> #include <myirc/connection.hpp>
#include <toml++/toml.hpp> #include <toml++/toml.hpp>
@ -46,55 +47,38 @@ struct WebhookSettings {
static auto from_toml(const toml::table &v) -> WebhookSettings; static auto from_toml(const toml::table &v) -> WebhookSettings;
}; };
struct WebhookEvent {
std::string channel;
std::string message;
};
class GithubWebhook { class GithubWebhook {
public:
struct Notice {
std::string target;
std::string message;
};
private:
// 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::Connection> connection_;
// Buffered events in case connection was inactive when event was received // Buffered events in case connection was inactive when event was received
std::vector<WebhookEvent> events_; std::vector<Notice> events_;
const char * settings_file;
// Actually write the event to the connection.
// Only call when there is a connection.
auto write_event(WebhookEvent event) -> void;
public: public:
WebhookSettings settings_; WebhookSettings settings_;
GithubWebhook(WebhookSettings settings) GithubWebhook(WebhookSettings settings, const char * settings_file)
: settings_(std::move(settings)) : settings_(std::move(settings))
, 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 add_event(WebhookEvent event) -> void auto add_event(Notice event) -> void;
{ auto set_connection(std::shared_ptr<myirc::Connection> connection) -> void;
if (connection_) { auto clear_connection() -> void;
write_event(std::move(event)); auto save_settings() const -> void;
} else {
events_.emplace_back(std::move(event));
}
}
auto set_connection(std::shared_ptr<myirc::Connection> connection) -> void
{
connection_ = std::move(connection);
for (auto &&event : events_)
{
write_event(event);
}
events_.clear();
}
auto clear_connection() -> void
{
connection_.reset();
}
}; };
auto start_webhook(boost::asio::io_context &io, const char *) -> std::shared_ptr<GithubWebhook>; auto start_webhook(boost::asio::io_context &io, const char *) -> std::shared_ptr<GithubWebhook>;
extern std::map<std::string, void(*)(std::shared_ptr<GithubWebhook>, const myirc::Bot::Command &)> webhook_commands;

View File

@ -18,6 +18,15 @@ struct Bot : std::enable_shared_from_this<Bot>
std::string_view account; std::string_view account;
std::string_view command; std::string_view command;
std::string_view arguments; 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<Client> self_; std::shared_ptr<Client> self_;