Compare commits

..

No commits in common. "53771396ca9dadb4ceb66430fd83fa1ec6485374" and "8c9678708b5a7c1867377cf931f287ebe2b0ccb4" have entirely different histories.

4 changed files with 52 additions and 181 deletions

View File

@ -63,7 +63,7 @@ auto configure_sasl(const Settings &settings) -> std::unique_ptr<SaslMechanism>
static auto start( static auto start(
boost::asio::io_context &io, boost::asio::io_context &io,
const Settings &settings, const Settings &settings,
std::shared_ptr<Webhooks> webhook std::shared_ptr<GithubWebhook> webhook
) -> void ) -> void
{ {
Ref<X509> tls_cert; Ref<X509> tls_cert;
@ -114,11 +114,12 @@ static auto start(
} }
); );
// Dispatch commands to the webhook logic // Simple example of a command handler
bot->sig_command.connect([webhook, connection](const Bot::Command &cmd) { bot->sig_command.connect([connection](const Bot::Command &cmd) {
auto cursor = webhook_commands.find(std::string{cmd.command}); if (cmd.oper == "glguy" && cmd.command == "ping") {
if (cursor != webhook_commands.end()) { if (auto bang = cmd.source.find('!'); bang != cmd.source.npos) {
cursor->second(webhook, cmd); connection->send_notice(cmd.source.substr(0, bang), cmd.arguments);
}
} }
}); });

View File

@ -64,7 +64,7 @@ static auto compute_signature(const std::string_view secret, const std::string_v
} }
static auto process_event( static auto process_event(
std::shared_ptr<Webhooks> self, std::shared_ptr<GithubWebhook> self,
const std::string_view notify_user, const std::string_view notify_user,
const std::string_view event, const std::string_view event,
const boost::json::value &json const boost::json::value &json
@ -102,12 +102,12 @@ static auto process_event(
} }
const auto message = std::string{event} + " on " + full_name; const auto message = std::string{event} + " on " + full_name;
self->send_notice({settings.channel, message}); self->add_event({settings.channel, message});
} }
template <class Body, class Allocator> template <class Body, class Allocator>
static auto handle_request( static auto handle_request(
std::shared_ptr<Webhooks> self, std::shared_ptr<GithubWebhook> self,
http::request<Body, http::basic_fields<Allocator>> &&req http::request<Body, http::basic_fields<Allocator>> &&req
) -> http::message_generator ) -> http::message_generator
{ {
@ -162,7 +162,7 @@ static auto handle_request(
return simple_response(http::status::ok, req.version(), req.keep_alive()); return simple_response(http::status::ok, req.version(), req.keep_alive());
} }
auto read_loop(tcp::socket socket, std::shared_ptr<Webhooks> self) -> boost::asio::awaitable<void> auto read_loop(tcp::socket socket, std::shared_ptr<GithubWebhook> self) -> boost::asio::awaitable<void>
{ {
beast::tcp_stream stream{std::move(socket)}; beast::tcp_stream stream{std::move(socket)};
beast::flat_buffer buffer; beast::flat_buffer buffer;
@ -200,7 +200,7 @@ auto read_loop(tcp::socket socket, std::shared_ptr<Webhooks> self) -> boost::asi
auto accept_loop( auto accept_loop(
tcp::acceptor acceptor, tcp::acceptor acceptor,
std::shared_ptr<Webhooks> self std::shared_ptr<GithubWebhook> self
) -> boost::asio::awaitable<void> ) -> boost::asio::awaitable<void>
{ {
for (;;) for (;;)
@ -216,7 +216,7 @@ auto accept_loop(
auto spawn_webhook( auto spawn_webhook(
boost::asio::io_context &io, boost::asio::io_context &io,
const std::shared_ptr<Webhooks> webhook const std::shared_ptr<GithubWebhook> webhook
) -> boost::asio::awaitable<void> ) -> boost::asio::awaitable<void>
{ {
tcp::resolver resolver{io}; tcp::resolver resolver{io};
@ -240,7 +240,7 @@ auto spawn_webhook(
auto start_webhook( auto start_webhook(
boost::asio::io_context &io, boost::asio::io_context &io,
const char *webhook_settings_filename const char *webhook_settings_filename
) -> std::shared_ptr<Webhooks> ) -> std::shared_ptr<GithubWebhook>
{ {
std::ifstream webhook_settings_file{webhook_settings_filename}; std::ifstream webhook_settings_file{webhook_settings_filename};
if (!webhook_settings_file) if (!webhook_settings_file)
@ -252,21 +252,14 @@ 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<Webhooks>(std::move(settings), webhook_settings_filename); auto webhook = std::make_shared<GithubWebhook>(std::move(settings));
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::save_settings() const -> void auto GithubWebhook::write_event(WebhookEvent event) -> void
{ {
std::ofstream webhook_settings_file{settings_file}; connection_->send_notice("glguy", event.channel + ": " + event.message);
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
@ -381,133 +374,3 @@ 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 Webhooks::send_notice(Notice notice) -> void
{
if (connection_)
{
connection_->send_notice(notice.target, notice.message);
}
else
{
events_.emplace_back(std::move(notice));
}
}
auto Webhooks::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 Webhooks::clear_connection() -> void
{
connection_.reset();
}
static auto reply_to(std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd, std::string message) -> void
{
if (cmd.target.starts_with("#"))
{
webhooks->send_notice({std::string{cmd.target}, std::move(message)});
}
else
{
webhooks->send_notice({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<Webhooks>, const myirc::Bot::Command &)> webhook_commands{
{"add-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, 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)
{
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> 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->send_notice({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,6 +1,5 @@
#pragma once #pragma once
#include <myirc/bot.hpp>
#include <myirc/connection.hpp> #include <myirc/connection.hpp>
#include <toml++/toml.hpp> #include <toml++/toml.hpp>
@ -47,38 +46,55 @@ struct WebhookSettings {
static auto from_toml(const toml::table &v) -> WebhookSettings; static auto from_toml(const toml::table &v) -> WebhookSettings;
}; };
class Webhooks { struct WebhookEvent {
public: std::string channel;
struct Notice {
std::string target;
std::string message; std::string message;
}; };
private:
class GithubWebhook {
// 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<Notice> events_; std::vector<WebhookEvent> 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_;
Webhooks(WebhookSettings settings, const char * settings_file) GithubWebhook(WebhookSettings settings)
: 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 send_notice(Notice event) -> void; auto add_event(WebhookEvent event) -> void
auto set_connection(std::shared_ptr<myirc::Connection> connection) -> void; {
auto clear_connection() -> void; if (connection_) {
auto save_settings() const -> void; write_event(std::move(event));
} 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<Webhooks>; auto start_webhook(boost::asio::io_context &io, const char *) -> std::shared_ptr<GithubWebhook>;
extern std::map<std::string, void(*)(std::shared_ptr<Webhooks>, const myirc::Bot::Command &)> webhook_commands;

View File

@ -18,15 +18,6 @@ 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_;