xbot/priv_thread.cpp
2023-11-29 13:13:48 -08:00

225 lines
6.6 KiB
C++

#include "priv_thread.hpp"
#include "command_thread.hpp"
#include "connection.hpp"
#include "write_irc.hpp"
#include <toml++/toml.hpp>
#include <boost/log/trivial.hpp>
#include <fstream>
#include <sstream>
#include <filesystem>
PrivThread::PrivThread(Connection& connection, std::string config_path)
: connection_{connection}
, config_path_{std::move(config_path)}
{
}
auto PrivThread::check_command(CommandEvent& event, std::string priv) -> bool
{
auto check_priv = [&priv](auto& map, std::string const& name) {
auto const cursor = map.find(name);
return cursor != map.end() && cursor->second.contains(priv);
};
auto check = [&check_priv](auto& map, auto& name) {
return
(not name.empty() &&
(check_priv(map, wildcard) ||
check_priv(map, std::string{name})));
};
return check(oper_privs_, event.oper) || check(account_privs_, event.account);
}
auto PrivThread::list_privs(std::string_view nick) -> void
{
for (auto const& [oper, privset] : oper_privs_)
{
std::string message = "Oper ";
message += oper;
message += ":";
for (auto const& priv : privset)
{
message += " ";
message += priv;
}
send_notice(connection_, nick, message);
}
for (auto const& [account, privset] : account_privs_)
{
std::string message = "Account ";
message += account;
message += ":";
for (auto const& priv : privset)
{
message += " ";
message += priv;
}
send_notice(connection_, nick, message);
}
}
auto PrivThread::on_command(CommandEvent& event) -> void
{
if ("list_privs" == event.command)
{
if (check_command(event, PrivThread::owner_priv))
{
list_privs(event.nick);
}
}
else if ("add_priv" == event.command)
{
if (check_command(event, PrivThread::owner_priv))
{
auto in = std::istringstream{std::string{event.arg}};
std::string kind, name, priv;
if (in >> kind >> name >> priv)
{
if ("oper" == kind)
{
oper_privs_[name].insert(priv);
save_config();
send_notice(connection_, event.nick, "ack");
BOOST_LOG_TRIVIAL(info) << "Priv added. by:" << event.nick << " oper:" << name << " priv:" << priv;
return;
}
else if ("account" == kind)
{
account_privs_[name].insert(priv);
save_config();
send_notice(connection_, event.nick, "ack");
BOOST_LOG_TRIVIAL(info) << "Priv added. by:" << event.nick << " account:" << name << " priv:" << priv;
return;
}
}
send_notice(connection_, event.nick, "nak");
}
}
else if ("remove_priv" == event.command)
{
if (check_command(event, PrivThread::owner_priv))
{
auto in = std::istringstream{std::string{event.arg}};
std::string kind, name, priv;
if (in >> kind >> name >> priv)
{
if ("oper" == kind)
{
if (wildcard == priv)
{
oper_privs_.erase(name);
}
else
{
oper_privs_[name].erase(priv);
if (oper_privs_[name].empty()) oper_privs_.erase(name);
}
save_config();
send_notice(connection_, event.nick, "ack");
BOOST_LOG_TRIVIAL(info) << "Priv removed. by:" << event.nick << " oper:" << name << " priv:" << priv;
return;
}
else if ("account" == kind)
{
if (wildcard == priv)
{
account_privs_.erase(name);
}
else
{
account_privs_[name].erase(priv);
if (account_privs_[name].empty()) account_privs_.erase(name);
}
save_config();
send_notice(connection_, event.nick, "ack");
BOOST_LOG_TRIVIAL(info) << "Priv removed. by:" << event.nick << " account:" << name << " priv:" << priv;
return;
}
}
send_notice(connection_, event.nick, "nak");
}
}
}
auto PrivThread::save_config() -> void
{
auto config = toml::table{};
auto serialize_table = [&config](auto name, auto& map) {
if (not map.empty()) {
auto tab = toml::table{};
for (auto const& [oper, privs] : map)
{
auto privset = toml::array{};
for (auto const& priv : privs)
{
privset.push_back(priv);
}
tab.insert(oper, privset);
}
config.insert(name, tab);
}
};
serialize_table("oper", oper_privs_);
serialize_table("account", account_privs_);
std::string tmp = config_path_ + ".tmp";
std::ofstream{tmp} << config;
std::filesystem::rename(tmp, config_path_);
}
auto PrivThread::load_config() -> void
{
if (auto in = std::ifstream{config_path_})
{
auto const config = toml::parse(in);
if (config.contains("oper"))
{
for (auto const [oper, privs] : *config["oper"].as_table())
{
auto& privset = oper_privs_[std::string{oper.str()}];
for (auto const& priv : *privs.as_array())
{
privset.insert(priv.as_string()->get());
}
}
}
if (config.contains("account"))
{
for (auto const [account, privs] : *config["account"].as_table())
{
auto& privset = account_privs_[std::string{account.str()}];
for (auto const& priv : *privs.as_array())
{
privset.insert(priv.as_string()->get());
}
}
}
}
}
auto PrivThread::start(Connection& connection, std::string config_path) -> std::shared_ptr<PrivThread>
{
auto thread = std::make_shared<PrivThread>(connection, config_path);
thread->load_config();
connection.add_listener<CommandEvent>([thread](CommandEvent& event)
{
thread->on_command(event);
});
return thread;
}