xbot/myirc/client.cpp

428 lines
11 KiB
C++
Raw Normal View History

2025-01-26 19:35:56 -08:00
#include "client.hpp"
2023-11-26 16:48:21 -08:00
#include "connection.hpp"
2025-01-25 12:25:38 -08:00
#include <mybase64.hpp>
2025-01-24 14:48:15 -08:00
#include <boost/container/flat_map.hpp>
2025-01-25 12:25:38 -08:00
#include <boost/log/trivial.hpp>
using namespace std::literals;
2025-01-24 14:48:15 -08:00
2025-01-26 19:35:56 -08:00
auto Client::on_welcome(const IrcMsg &irc) -> void
2024-03-03 12:53:59 -08:00
{
nickname_ = irc.args[0];
}
2025-01-26 19:35:56 -08:00
auto Client::on_registered() -> void
2025-01-25 21:24:33 -08:00
{
2025-01-26 19:35:56 -08:00
sig_registered();
2025-01-26 19:44:35 -08:00
sig_registered.disconnect_all_slots();
2025-01-25 21:24:33 -08:00
}
2025-01-26 19:35:56 -08:00
auto Client::on_nick(const IrcMsg &irc) -> void
2024-03-03 12:53:59 -08:00
{
if (is_my_mask(irc.source))
{
nickname_ = irc.args[0];
}
}
2025-01-26 19:35:56 -08:00
auto Client::on_umodeis(const IrcMsg &irc) -> void
2024-03-03 12:53:59 -08:00
{
mode_ = irc.args[1];
}
2025-01-26 19:35:56 -08:00
auto Client::on_join(const IrcMsg &irc) -> void
2024-03-03 12:53:59 -08:00
{
if (is_my_mask(irc.source))
{
2025-01-27 17:46:07 -08:00
channels_.insert(casemap(irc.args[0]));
2024-03-03 12:53:59 -08:00
}
}
2025-01-26 19:35:56 -08:00
auto Client::on_kick(const IrcMsg &irc) -> void
2024-03-03 12:53:59 -08:00
{
if (is_my_nick(irc.args[1]))
{
2025-01-27 17:46:07 -08:00
channels_.erase(casemap(irc.args[0]));
2024-03-03 12:53:59 -08:00
}
}
2025-01-26 19:35:56 -08:00
auto Client::on_part(const IrcMsg &irc) -> void
2024-03-03 12:53:59 -08:00
{
if (is_my_mask(irc.source))
{
2025-01-27 17:46:07 -08:00
channels_.erase(casemap(irc.args[0]));
2024-03-03 12:53:59 -08:00
}
}
2025-01-26 19:35:56 -08:00
auto Client::on_mode(const IrcMsg &irc) -> void
2024-03-03 12:53:59 -08:00
{
if (is_my_nick(irc.args[0]))
{
auto polarity = true;
2025-01-25 15:45:31 -08:00
for (const char c : irc.args[1])
2024-03-03 12:53:59 -08:00
{
switch (c)
{
case '+':
polarity = true;
break;
case '-':
polarity = false;
break;
default:
if (polarity)
{
mode_ += c;
}
else
{
2025-01-25 15:45:31 -08:00
const auto ix = mode_.find(c);
2024-03-03 12:53:59 -08:00
if (ix != std::string::npos)
{
mode_.erase(ix, 1);
}
}
break;
}
}
}
}
2025-01-26 19:35:56 -08:00
auto Client::on_isupport(const IrcMsg &msg) -> void
2025-01-24 14:48:15 -08:00
{
2025-01-25 15:45:31 -08:00
const auto hi = msg.args.size() - 1;
2025-01-24 14:48:15 -08:00
for (int i = 1; i < hi; ++i)
{
auto &entry = msg.args[i];
2025-01-25 12:25:38 -08:00
// Leading minus means to stop support
2025-01-25 15:45:31 -08:00
if (entry.starts_with("-"))
{
const auto key = std::string{entry.substr(1)};
if (auto cursor = isupport_.find(key); cursor != isupport_.end())
{
2025-01-24 14:48:15 -08:00
isupport_.erase(cursor);
}
2025-01-25 15:45:31 -08:00
}
else if (const auto cursor = entry.find('='); cursor != entry.npos)
{
isupport_.emplace(entry.substr(0, cursor), entry.substr(cursor + 1));
}
else
{
2025-01-24 14:48:15 -08:00
isupport_.emplace(entry, std::string{});
}
}
}
2025-01-27 17:46:07 -08:00
auto Client::on_chat(bool notice, const IrcMsg &irc) -> void
{
char status_msg = '\0';
std::string_view target = irc.args[0];
if (not target.empty() && status_msg_.find(target[0]) != std::string::npos)
{
status_msg = target[0];
target = target.substr(1);
}
sig_chat({
.tags = irc.tags,
.is_notice = notice,
.status_msg = '\0',
.source = irc.source,
.target = irc.args[0],
.message = irc.args[1],
});
}
2025-01-31 08:38:14 -08:00
auto Client::start(std::shared_ptr<Connection> connection) -> std::shared_ptr<Client>
2023-11-26 16:48:21 -08:00
{
2025-01-26 19:35:56 -08:00
auto thread = std::make_shared<Client>(connection);
2023-11-26 16:48:21 -08:00
2025-01-31 08:38:14 -08:00
connection->sig_ircmsg.connect([thread](auto cmd, auto &msg) {
2025-01-22 23:49:48 -08:00
switch (cmd)
2023-11-26 16:48:21 -08:00
{
2025-01-27 17:46:07 -08:00
case IrcCommand::PRIVMSG:
thread->on_chat(false, msg);
break;
case IrcCommand::NOTICE:
thread->on_chat(true, msg);
break;
2025-01-25 15:45:31 -08:00
case IrcCommand::JOIN:
thread->on_join(msg);
break;
case IrcCommand::KICK:
thread->on_kick(msg);
break;
case IrcCommand::MODE:
thread->on_mode(msg);
break;
case IrcCommand::NICK:
thread->on_nick(msg);
break;
case IrcCommand::PART:
thread->on_part(msg);
break;
case IrcCommand::RPL_ISUPPORT:
thread->on_isupport(msg);
break;
case IrcCommand::RPL_UMODEIS:
thread->on_umodeis(msg);
break;
case IrcCommand::RPL_WELCOME:
thread->on_welcome(msg);
break;
2025-01-25 21:24:33 -08:00
case IrcCommand::RPL_ENDOFMOTD:
case IrcCommand::ERR_NOMOTD:
2025-01-25 21:24:33 -08:00
thread->on_registered();
break;
2025-01-27 11:08:16 -08:00
case IrcCommand::CAP:
thread->on_cap(msg);
break;
2025-01-25 15:45:31 -08:00
default:
break;
2023-11-26 16:48:21 -08:00
}
});
2025-01-31 08:38:14 -08:00
connection->sig_authenticate.connect([thread](auto msg) {
2025-01-25 12:25:38 -08:00
thread->on_authenticate(msg);
});
2023-11-26 16:48:21 -08:00
return thread;
}
2024-03-03 12:27:36 -08:00
2025-01-27 20:02:31 -08:00
auto Client::get_my_nick() const -> const std::string &
2024-03-03 12:27:36 -08:00
{
return nickname_;
}
2025-01-26 19:35:56 -08:00
auto Client::get_my_mode() const -> const std::string &
2024-03-03 12:27:36 -08:00
{
return mode_;
}
2025-01-26 19:35:56 -08:00
auto Client::get_my_channels() const -> const std::unordered_set<std::string> &
2024-03-03 12:27:36 -08:00
{
return channels_;
}
2025-01-26 19:35:56 -08:00
auto Client::is_my_nick(std::string_view nick) const -> bool
2024-03-03 12:27:36 -08:00
{
2025-01-25 21:24:33 -08:00
return casemap_compare(nick, nickname_) == 0;
2024-03-03 12:27:36 -08:00
}
2025-01-26 19:35:56 -08:00
auto Client::is_my_mask(std::string_view mask) const -> bool
2024-03-03 12:27:36 -08:00
{
2025-01-25 15:45:31 -08:00
const auto bang = mask.find('!');
2025-01-27 17:46:07 -08:00
return bang != std::string_view::npos && is_my_nick(mask.substr(0, bang));
2024-03-03 12:27:36 -08:00
}
2025-01-25 12:25:38 -08:00
2025-01-26 19:35:56 -08:00
auto Client::is_channel(std::string_view name) const -> bool
2025-01-25 21:24:33 -08:00
{
return not name.empty() && channel_prefix_.find(name[0]) != channel_prefix_.npos;
}
2025-01-30 09:28:28 -08:00
namespace {
template <class... Ts>
struct overloaded : Ts...
{
using Ts::operator()...;
};
template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
}
2025-01-26 19:35:56 -08:00
auto Client::on_authenticate(const std::string_view body) -> void
2025-01-25 12:25:38 -08:00
{
if (not sasl_mechanism_)
{
BOOST_LOG_TRIVIAL(warning) << "Unexpected AUTHENTICATE from server"sv;
2025-01-31 08:38:14 -08:00
connection_->send_authenticate_abort();
2025-01-25 12:25:38 -08:00
return;
}
2025-01-30 09:28:28 -08:00
std::visit(
overloaded{
[this](const std::string &reply) {
2025-01-31 08:38:14 -08:00
connection_->send_authenticate_encoded(reply);
2025-01-30 09:28:28 -08:00
},
[this](SaslMechanism::NoReply) {
2025-01-31 08:38:14 -08:00
connection_->send_authenticate("*"sv);
2025-01-30 09:28:28 -08:00
},
[this](SaslMechanism::Failure) {
2025-01-31 08:38:14 -08:00
connection_->send_authenticate_abort();
2025-01-30 09:28:28 -08:00
},
},
sasl_mechanism_->step(body));
if (sasl_mechanism_->is_complete())
2025-01-25 15:45:31 -08:00
{
2025-01-30 09:28:28 -08:00
sasl_mechanism_.reset();
2025-01-25 12:25:38 -08:00
}
}
2025-01-26 19:35:56 -08:00
auto Client::start_sasl(std::unique_ptr<SaslMechanism> mechanism) -> void
2025-01-25 12:25:38 -08:00
{
2025-01-25 15:45:31 -08:00
if (sasl_mechanism_)
{
2025-01-31 08:38:14 -08:00
connection_->send_authenticate("*"sv); // abort SASL
2025-01-25 12:25:38 -08:00
}
sasl_mechanism_ = std::move(mechanism);
2025-01-31 08:38:14 -08:00
connection_->send_authenticate(sasl_mechanism_->mechanism_name());
2025-01-25 12:25:38 -08:00
}
2025-01-25 21:24:33 -08:00
static auto tolower_rfc1459(int c) -> int
{
return 97 <= c && c <= 126 ? c - 32 : c;
}
static auto tolower_rfc1459_strict(int c) -> int
{
return 97 <= c && c <= 125 ? c - 32 : c;
}
template <int lower(int)>
static auto casemap_impl(std::string_view str) -> std::string
{
std::string result;
result.reserve(str.size());
for (auto c : str) {
result.push_back(lower(c));
}
return result;
}
2025-01-26 19:35:56 -08:00
auto Client::casemap(std::string_view str) const -> std::string
2025-01-25 21:24:33 -08:00
{
switch (casemap_) {
case Casemap::Ascii: return casemap_impl<tolower>(str);
case Casemap::Rfc1459: return casemap_impl<tolower_rfc1459>(str);
case Casemap::Rfc1459_Strict: return casemap_impl<tolower_rfc1459_strict>(str);
}
}
template <int lower(int)>
static auto casemap_compare_impl(std::string_view lhs, std::string_view rhs) -> int
{
size_t n1 = lhs.size();
size_t n2 = rhs.size();
size_t n = std::min(n1, n2);
for (size_t i = 0; i < n; ++i) {
if (lower(lhs[i]) < lower(rhs[i])) return -1;
if (lower(lhs[i]) > lower(rhs[i])) return 1;
}
if (n1 < n2) return -1;
if (n1 > n2) return 1;
return 0;
}
2025-01-26 19:35:56 -08:00
auto Client::casemap_compare(std::string_view lhs, std::string_view rhs) const -> int
2025-01-25 21:24:33 -08:00
{
switch (casemap_) {
case Casemap::Ascii: return casemap_compare_impl<tolower>(lhs, rhs);
case Casemap::Rfc1459: return casemap_compare_impl<tolower_rfc1459>(lhs, rhs);
case Casemap::Rfc1459_Strict: return casemap_compare_impl<tolower_rfc1459_strict>(lhs, rhs);
}
2025-01-26 19:35:56 -08:00
}
2025-01-27 11:08:16 -08:00
auto Client::list_caps() -> void
{
caps_available_.clear();
2025-01-31 08:38:14 -08:00
connection_->send_cap_ls();
2025-01-27 11:08:16 -08:00
}
auto Client::on_cap(const IrcMsg &msg) -> void
{
if ("ACK" == msg.args[1] && msg.args.size() == 3) {
auto in = std::istringstream{std::string{msg.args[2]}};
std::for_each(
std::istream_iterator<std::string>{in},
std::istream_iterator<std::string>{},
[this](std::string x) {
caps_.insert(std::move(x));
}
);
} else if ("NAK" == msg.args[1] && msg.args.size() == 3) {
auto in = std::istringstream{std::string{msg.args[2]}};
std::for_each(
std::istream_iterator<std::string>{in},
std::istream_iterator<std::string>{},
[this](std::string x) {
caps_.erase(x);
}
);
} else if ("LS" == msg.args[1]) {
const std::string_view *kvs;
bool last;
if (3 == msg.args.size())
{
kvs = &msg.args[2];
last = true;
}
else if (4 == msg.args.size() && "*" == msg.args[2])
{
kvs = &msg.args[3];
last = false;
}
else
{
BOOST_LOG_TRIVIAL(warning) << "Malformed CAP LS";
return;
}
auto in = std::istringstream{std::string{*kvs}};
std::for_each(
std::istream_iterator<std::string>{in},
std::istream_iterator<std::string>{},
[this](std::string x) {
const auto eq = x.find('=');
if (eq == x.npos)
{
caps_available_.emplace(x, std::string{});
}
else
{
caps_available_.emplace(std::string{x, 0, eq}, std::string{x, eq + 1, x.npos});
}
}
);
if (last)
{
sig_cap_ls(caps_available_);
}
} else if ("NEW" == msg.args[1] && msg.args.size() == 3) {
auto in = std::istringstream{std::string{msg.args[2]}};
std::for_each(
std::istream_iterator<std::string>{in},
std::istream_iterator<std::string>{},
[this](std::string x) {
const auto eq = x.find('=');
if (eq == x.npos)
{
caps_available_.emplace(x, std::string{});
}
else
{
caps_available_.emplace(std::string{x, 0, eq}, std::string{x, eq + 1, x.npos});
}
}
);
} else if ("DEL" == msg.args[1] && msg.args.size() == 3) {
auto in = std::istringstream{std::string{msg.args[2]}};
std::for_each(
std::istream_iterator<std::string>{in},
std::istream_iterator<std::string>{},
[this](std::string x) {
caps_available_.erase(x);
caps_.erase(x);
}
);
}
}