refactor
This commit is contained in:
parent
53771396ca
commit
5cfb47ce92
@ -11,7 +11,6 @@
|
|||||||
#include "myirc/ref.hpp"
|
#include "myirc/ref.hpp"
|
||||||
#include "myirc/irc_coroutine.hpp"
|
#include "myirc/irc_coroutine.hpp"
|
||||||
|
|
||||||
|
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
#include <boost/log/expressions.hpp>
|
#include <boost/log/expressions.hpp>
|
||||||
@ -22,10 +21,6 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
using myirc::SaslMechanism;
|
|
||||||
using myirc::SaslPlain;
|
|
||||||
using myirc::SaslExternal;
|
|
||||||
using myirc::SaslEcdsa;
|
|
||||||
using myirc::Bot;
|
using myirc::Bot;
|
||||||
using myirc::Client;
|
using myirc::Client;
|
||||||
using myirc::Connection;
|
using myirc::Connection;
|
||||||
@ -33,34 +28,7 @@ using myirc::Registration;
|
|||||||
using myirc::Challenge;
|
using myirc::Challenge;
|
||||||
using myirc::Ref;
|
using myirc::Ref;
|
||||||
|
|
||||||
auto configure_sasl(const Settings &settings) -> std::unique_ptr<SaslMechanism>
|
static auto start_irc(
|
||||||
{
|
|
||||||
if (settings.sasl_mechanism == "PLAIN" &&
|
|
||||||
not settings.sasl_authcid.empty()
|
|
||||||
) {
|
|
||||||
return std::make_unique<SaslPlain>(
|
|
||||||
settings.sasl_authcid,
|
|
||||||
settings.sasl_authzid,
|
|
||||||
settings.sasl_password);
|
|
||||||
|
|
||||||
} else if (settings.sasl_mechanism == "EXTERNAL") {
|
|
||||||
return std::make_unique<SaslExternal>(settings.sasl_authzid);
|
|
||||||
|
|
||||||
} else if (
|
|
||||||
settings.sasl_mechanism == "ECDSA" &&
|
|
||||||
not settings.sasl_authcid.empty() &&
|
|
||||||
not settings.sasl_key_file.empty()
|
|
||||||
) {
|
|
||||||
if (auto sasl_key = myirc::key_from_file(settings.sasl_key_file, settings.sasl_key_password))
|
|
||||||
return std::make_unique<SaslEcdsa>(
|
|
||||||
settings.sasl_authcid,
|
|
||||||
settings.sasl_authzid,
|
|
||||||
std::move(sasl_key));
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Webhooks> webhook
|
||||||
@ -110,7 +78,7 @@ static auto start(
|
|||||||
webhook->clear_connection();
|
webhook->clear_connection();
|
||||||
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(io, settings, webhook); });
|
timer->async_wait([&io, &settings, timer, webhook](auto) { start_irc(io, settings, webhook); });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -155,9 +123,7 @@ auto main(int argc, char *argv[]) -> int
|
|||||||
}
|
}
|
||||||
const auto settings = get_settings(argv[1]);
|
const auto settings = get_settings(argv[1]);
|
||||||
auto io = boost::asio::io_context{};
|
auto io = boost::asio::io_context{};
|
||||||
|
|
||||||
auto webhooks = start_webhook(io, argv[2]);
|
auto webhooks = start_webhook(io, argv[2]);
|
||||||
|
start_irc(io, settings, webhooks);
|
||||||
start(io, settings, webhooks);
|
|
||||||
io.run();
|
io.run();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
|
|
||||||
|
#include <myirc/openssl_utils.hpp>
|
||||||
|
|
||||||
#define TOML_ENABLE_FORMATTERS 0
|
#define TOML_ENABLE_FORMATTERS 0
|
||||||
#include <toml++/toml.hpp>
|
#include <toml++/toml.hpp>
|
||||||
|
|
||||||
@ -29,3 +31,31 @@ auto Settings::from_stream(std::istream &in) -> Settings
|
|||||||
.use_tls = config["use_tls"].value_or(false),
|
.use_tls = config["use_tls"].value_or(false),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto configure_sasl(const Settings &settings) -> std::unique_ptr<myirc::SaslMechanism>
|
||||||
|
{
|
||||||
|
if (settings.sasl_mechanism == "PLAIN" &&
|
||||||
|
not settings.sasl_authcid.empty()
|
||||||
|
) {
|
||||||
|
return std::make_unique<myirc::SaslPlain>(
|
||||||
|
settings.sasl_authcid,
|
||||||
|
settings.sasl_authzid,
|
||||||
|
settings.sasl_password);
|
||||||
|
|
||||||
|
} else if (settings.sasl_mechanism == "EXTERNAL") {
|
||||||
|
return std::make_unique<myirc::SaslExternal>(settings.sasl_authzid);
|
||||||
|
|
||||||
|
} else if (
|
||||||
|
settings.sasl_mechanism == "ECDSA" &&
|
||||||
|
not settings.sasl_authcid.empty() &&
|
||||||
|
not settings.sasl_key_file.empty()
|
||||||
|
) {
|
||||||
|
if (auto sasl_key = myirc::key_from_file(settings.sasl_key_file, settings.sasl_key_password))
|
||||||
|
return std::make_unique<myirc::SaslEcdsa>(
|
||||||
|
settings.sasl_authcid,
|
||||||
|
settings.sasl_authzid,
|
||||||
|
std::move(sasl_key));
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <myirc/sasl_mechanism.hpp>
|
||||||
|
|
||||||
#include <istream>
|
#include <istream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@ -32,3 +34,5 @@ struct Settings
|
|||||||
|
|
||||||
static auto from_stream(std::istream &in) -> Settings;
|
static auto from_stream(std::istream &in) -> Settings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto configure_sasl(const Settings &settings) -> std::unique_ptr<myirc::SaslMechanism>;
|
||||||
|
119
driver/web.cpp
119
driver/web.cpp
@ -15,13 +15,14 @@
|
|||||||
namespace beast = boost::beast; // from <boost/beast.hpp>
|
namespace beast = boost::beast; // from <boost/beast.hpp>
|
||||||
namespace http = beast::http; // from <boost/beast/http.hpp>
|
namespace http = beast::http; // from <boost/beast/http.hpp>
|
||||||
namespace net = boost::asio; // from <boost/asio.hpp>
|
namespace net = boost::asio; // from <boost/asio.hpp>
|
||||||
namespace websocket = beast::websocket;
|
|
||||||
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
// Used as the completion handler for coroutines in this module to print
|
||||||
|
// failure reasons to the log.
|
||||||
auto report_error(std::exception_ptr eptr) -> void
|
auto report_error(std::exception_ptr eptr) -> void
|
||||||
{
|
{
|
||||||
if (eptr)
|
if (eptr)
|
||||||
@ -32,12 +33,17 @@ auto report_error(std::exception_ptr eptr) -> void
|
|||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
BOOST_LOG_TRIVIAL(error) << "An error occurred: " << e.what();
|
BOOST_LOG_TRIVIAL(error) << "HTTP coroutine failed: " << e.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto simple_response(http::status status, unsigned version, bool keep_alive) -> http::message_generator
|
// Construct a simple, empty reply using the given status code.
|
||||||
|
auto simple_response(
|
||||||
|
http::status status,
|
||||||
|
unsigned version,
|
||||||
|
bool keep_alive
|
||||||
|
) -> http::message_generator
|
||||||
{
|
{
|
||||||
http::response<http::string_body> res{status, version};
|
http::response<http::string_body> res{status, version};
|
||||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||||
@ -46,7 +52,8 @@ static auto simple_response(http::status status, unsigned version, bool keep_ali
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto compute_signature(const std::string_view secret, const std::string_view body) -> std::string
|
// Compute the expected signature string for the POST body.
|
||||||
|
auto compute_signature(const std::string_view secret, const std::string_view body) -> std::string
|
||||||
{
|
{
|
||||||
unsigned int digest_length = EVP_MAX_MD_SIZE;
|
unsigned int digest_length = EVP_MAX_MD_SIZE;
|
||||||
unsigned char digest[EVP_MAX_MD_SIZE];
|
unsigned char digest[EVP_MAX_MD_SIZE];
|
||||||
@ -63,23 +70,37 @@ static auto compute_signature(const std::string_view secret, const std::string_v
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto process_event(
|
// This event is ready to actually announce
|
||||||
|
auto announce_event(
|
||||||
|
std::shared_ptr<Webhooks> self,
|
||||||
|
const ProjectSettings &project,
|
||||||
|
const std::string full_name,
|
||||||
|
const std::string_view event_name,
|
||||||
|
const boost::json::value event
|
||||||
|
) -> void {
|
||||||
|
const auto message = std::string{event_name} + " on " + full_name;
|
||||||
|
self->send_notice(project.channel, std::move(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if this event should be announced
|
||||||
|
auto process_event(
|
||||||
std::shared_ptr<Webhooks> self,
|
std::shared_ptr<Webhooks> self,
|
||||||
const std::string_view notify_user,
|
const std::string_view notify_user,
|
||||||
const std::string_view event,
|
const std::string_view event_name,
|
||||||
const boost::json::value &json
|
const boost::json::value &json
|
||||||
) -> void
|
) -> void
|
||||||
{
|
{
|
||||||
auto &project = json.as_object();
|
auto &event = json.as_object();
|
||||||
|
|
||||||
|
// Determine the project name. Repositories use: user/project. Organization events use: organization
|
||||||
std::string full_name;
|
std::string full_name;
|
||||||
if (project.contains("repository"))
|
if (event.contains("repository"))
|
||||||
{
|
{
|
||||||
full_name = std::string{project.at("repository").as_object().at("full_name").as_string()};
|
full_name = std::string{event.at("repository").as_object().at("full_name").as_string()};
|
||||||
}
|
}
|
||||||
else if (project.contains("organization"))
|
else if (event.contains("organization"))
|
||||||
{
|
{
|
||||||
full_name = std::string{project.at("organization").as_object().at("login").as_string()};
|
full_name = std::string{event.at("organization").as_object().at("login").as_string()};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -96,17 +117,18 @@ static auto process_event(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (not settings.enabled)
|
if (not settings.enabled || not settings.events.contains(std::string{event_name}))
|
||||||
{
|
{
|
||||||
|
// quietly ignore events we don't care about
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto message = std::string{event} + " on " + full_name;
|
announce_event(self, settings, std::move(full_name), event_name, std::move(event));
|
||||||
self->send_notice({settings.channel, message});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process the HTTP request validating its structure and signature.
|
||||||
template <class Body, class Allocator>
|
template <class Body, class Allocator>
|
||||||
static auto handle_request(
|
auto handle_request(
|
||||||
std::shared_ptr<Webhooks> self,
|
std::shared_ptr<Webhooks> self,
|
||||||
http::request<Body, http::basic_fields<Allocator>> &&req
|
http::request<Body, http::basic_fields<Allocator>> &&req
|
||||||
) -> http::message_generator
|
) -> http::message_generator
|
||||||
@ -162,42 +184,37 @@ 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Repeatedly read HTTP requests off a socket and reply to them
|
||||||
auto read_loop(tcp::socket socket, std::shared_ptr<Webhooks> self) -> boost::asio::awaitable<void>
|
auto read_loop(tcp::socket socket, std::shared_ptr<Webhooks> 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;
|
||||||
http::request<http::string_body> req;
|
http::request<http::string_body> req;
|
||||||
|
bool keep_alive = true;
|
||||||
|
|
||||||
for (;;)
|
while (keep_alive)
|
||||||
{
|
{
|
||||||
req.clear();
|
req.clear();
|
||||||
stream.expires_after(30s);
|
stream.expires_after(30s);
|
||||||
|
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
co_await http::async_read(stream, buffer, req, net::redirect_error(net::use_awaitable, ec));
|
co_await http::async_read(stream, buffer, req, net::redirect_error(net::use_awaitable, ec));
|
||||||
if (ec == http::error::end_of_stream)
|
if (ec == http::error::end_of_stream)
|
||||||
{
|
{
|
||||||
stream.socket().shutdown(tcp::socket::shutdown_send, ec);
|
break;
|
||||||
co_return;
|
|
||||||
}
|
}
|
||||||
else if (ec)
|
else if (ec)
|
||||||
{
|
{
|
||||||
throw boost::system::system_error{ec};
|
throw boost::system::system_error{ec};
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto keep_alive = req.keep_alive();
|
keep_alive = req.keep_alive();
|
||||||
auto msg = handle_request(self, std::move(req));
|
auto msg = handle_request(self, std::move(req));
|
||||||
|
|
||||||
co_await beast::async_write(stream, std::move(msg), net::use_awaitable);
|
co_await beast::async_write(stream, std::move(msg), net::use_awaitable);
|
||||||
|
|
||||||
if (!keep_alive)
|
|
||||||
{
|
|
||||||
stream.socket().shutdown(tcp::socket::shutdown_send, ec);
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
stream.socket().shutdown(tcp::socket::shutdown_both);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Repeatedly accept new connections on a listening socket
|
||||||
auto accept_loop(
|
auto accept_loop(
|
||||||
tcp::acceptor acceptor,
|
tcp::acceptor acceptor,
|
||||||
std::shared_ptr<Webhooks> self
|
std::shared_ptr<Webhooks> self
|
||||||
@ -214,6 +231,7 @@ auto accept_loop(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Launch the listening sockets
|
||||||
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<Webhooks> webhook
|
||||||
@ -383,24 +401,24 @@ 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(Notice notice) -> void
|
auto Webhooks::send_notice(std::string_view target, std::string message) -> void
|
||||||
{
|
{
|
||||||
if (connection_)
|
if (connection_)
|
||||||
{
|
{
|
||||||
connection_->send_notice(notice.target, notice.message);
|
connection_->send_notice(target, message);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
events_.emplace_back(std::move(notice));
|
events_.emplace_back(std::string(target), std::move(message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Webhooks::set_connection(std::shared_ptr<myirc::Connection> connection) -> void
|
auto Webhooks::set_connection(std::shared_ptr<myirc::Connection> connection) -> void
|
||||||
{
|
{
|
||||||
connection_ = std::move(connection);
|
connection_ = std::move(connection);
|
||||||
for (auto &&event : events_)
|
for (auto &&[target, message] : std::move(events_))
|
||||||
{
|
{
|
||||||
connection_->send_notice(event.target, event.message);
|
connection_->send_notice(target, message);
|
||||||
}
|
}
|
||||||
events_.clear();
|
events_.clear();
|
||||||
}
|
}
|
||||||
@ -414,25 +432,30 @@ static auto reply_to(std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Comma
|
|||||||
{
|
{
|
||||||
if (cmd.target.starts_with("#"))
|
if (cmd.target.starts_with("#"))
|
||||||
{
|
{
|
||||||
webhooks->send_notice({std::string{cmd.target}, std::move(message)});
|
webhooks->send_notice(cmd.target, std::move(message));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
webhooks->send_notice({std::string{cmd.nick()}, std::move(message)});
|
webhooks->send_notice(cmd.nick(), std::move(message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto authorized_for_project(const ProjectSettings &project, const std::string_view nick) -> bool
|
// Operators are authorized for all projects otherwise nickserv account names can be added to individual projects.
|
||||||
|
static auto authorized_for_project(
|
||||||
|
const myirc::Bot::Command &cmd,
|
||||||
|
const ProjectSettings &project,
|
||||||
|
const std::string_view nick
|
||||||
|
) -> bool
|
||||||
{
|
{
|
||||||
return project.authorized_accounts.find(std::string{nick}) != project.authorized_accounts.end();
|
return !cmd.oper.empty() || project.authorized_accounts.contains(std::string{nick});
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
{"add-credential", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
|
||||||
//if (cmd.oper.empty())
|
if (cmd.oper.empty())
|
||||||
//{
|
{
|
||||||
// return;
|
return;
|
||||||
//}
|
}
|
||||||
std::istringstream iss{std::string{cmd.arguments}};
|
std::istringstream iss{std::string{cmd.arguments}};
|
||||||
std::string name, key;
|
std::string name, key;
|
||||||
if (iss >> name >> key)
|
if (iss >> name >> key)
|
||||||
@ -443,10 +466,10 @@ std::map<std::string, void (*)(std::shared_ptr<Webhooks>, const myirc::Bot::Comm
|
|||||||
}
|
}
|
||||||
}},
|
}},
|
||||||
{"drop-credential", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
|
{"drop-credential", [](std::shared_ptr<Webhooks> webhooks, const myirc::Bot::Command &cmd) {
|
||||||
//if (cmd.oper.empty())
|
if (cmd.oper.empty())
|
||||||
//{
|
{
|
||||||
// return;
|
return;
|
||||||
//}
|
}
|
||||||
std::istringstream iss{std::string{cmd.arguments}};
|
std::istringstream iss{std::string{cmd.arguments}};
|
||||||
std::string name;
|
std::string name;
|
||||||
if (iss >> name)
|
if (iss >> name)
|
||||||
@ -464,15 +487,13 @@ std::map<std::string, void (*)(std::shared_ptr<Webhooks>, const myirc::Bot::Comm
|
|||||||
auto cursor = webhooks->settings_.projects.find(name);
|
auto cursor = webhooks->settings_.projects.find(name);
|
||||||
if (cursor == webhooks->settings_.projects.end())
|
if (cursor == webhooks->settings_.projects.end())
|
||||||
{
|
{
|
||||||
reply_to(webhooks, cmd, "Unknown project " + name);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &project = cursor->second;
|
auto &project = cursor->second;
|
||||||
|
|
||||||
if (not authorized_for_project(project, cmd.account))
|
if (not authorized_for_project(cmd, project, cmd.account))
|
||||||
{
|
{
|
||||||
reply_to(webhooks, cmd, "Unauthorized to enable project " + name);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -493,15 +514,13 @@ std::map<std::string, void (*)(std::shared_ptr<Webhooks>, const myirc::Bot::Comm
|
|||||||
auto cursor = webhooks->settings_.projects.find(name);
|
auto cursor = webhooks->settings_.projects.find(name);
|
||||||
if (cursor == webhooks->settings_.projects.end())
|
if (cursor == webhooks->settings_.projects.end())
|
||||||
{
|
{
|
||||||
webhooks->send_notice({std::string{cmd.nick()}, "Unknown project " + name});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &project = cursor->second;
|
auto &project = cursor->second;
|
||||||
|
|
||||||
if (not authorized_for_project(project, cmd.account))
|
if (not authorized_for_project(cmd, project, cmd.account))
|
||||||
{
|
{
|
||||||
reply_to(webhooks, cmd, "Unauthorized to disable project " + name);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,18 +48,11 @@ struct WebhookSettings {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class Webhooks {
|
class Webhooks {
|
||||||
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<Notice> events_;
|
std::vector<std::pair<std::string, std::string>> events_;
|
||||||
|
|
||||||
const char * settings_file;
|
const char * settings_file;
|
||||||
|
|
||||||
@ -73,7 +66,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 send_notice(std::string_view, std::string) -> void;
|
||||||
auto set_connection(std::shared_ptr<myirc::Connection> connection) -> void;
|
auto set_connection(std::shared_ptr<myirc::Connection> connection) -> void;
|
||||||
auto clear_connection() -> void;
|
auto clear_connection() -> void;
|
||||||
auto save_settings() const -> void;
|
auto save_settings() const -> void;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user