From a9d6eb3811a8d551b7aab1ef64ccbf3b088fb860 Mon Sep 17 00:00:00 2001 From: Eric Mertens Date: Mon, 27 Jan 2025 11:08:16 -0800 Subject: [PATCH] push cap logic into client --- client.cpp | 104 +++++++++++++++++++++++++++++++++++++++++- client.hpp | 7 +++ registration.cpp | 115 +++++++++++------------------------------------ registration.hpp | 7 +-- 4 files changed, 137 insertions(+), 96 deletions(-) diff --git a/client.cpp b/client.cpp index e09bfb1..f4f3ee0 100644 --- a/client.cpp +++ b/client.cpp @@ -152,6 +152,9 @@ auto Client::start(Connection &connection) -> std::shared_ptr case IrcCommand::RPL_ENDOFMOTD: thread->on_registered(); break; + case IrcCommand::CAP: + thread->on_cap(msg); + break; default: break; } @@ -285,4 +288,103 @@ auto Client::casemap_compare(std::string_view lhs, std::string_view rhs) const - auto Client::shutdown() -> void { sig_registered.disconnect_all_slots(); -} \ No newline at end of file + sig_cap_ls.disconnect_all_slots(); +} + +auto Client::list_caps() -> void +{ + caps_available_.clear(); + connection_.send_cap_ls(); +} + +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{in}, + std::istream_iterator{}, + [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{in}, + std::istream_iterator{}, + [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{in}, + std::istream_iterator{}, + [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{in}, + std::istream_iterator{}, + [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{in}, + std::istream_iterator{}, + [this](std::string x) { + caps_available_.erase(x); + caps_.erase(x); + } + ); + } +} diff --git a/client.hpp b/client.hpp index b5a5b23..7c0b0b5 100644 --- a/client.hpp +++ b/client.hpp @@ -35,6 +35,9 @@ class Client Casemap casemap_; std::string channel_prefix_; + std::unordered_map caps_available_; + std::unordered_set caps_; + auto on_welcome(const IrcMsg &irc) -> void; auto on_isupport(const IrcMsg &irc) -> void; auto on_nick(const IrcMsg &irc) -> void; @@ -43,11 +46,13 @@ class Client auto on_kick(const IrcMsg &irc) -> void; auto on_part(const IrcMsg &irc) -> void; auto on_mode(const IrcMsg &irc) -> void; + auto on_cap(const IrcMsg &irc) -> void; auto on_authenticate(std::string_view) -> void; auto on_registered() -> void; public: boost::signals2::signal sig_registered; + boost::signals2::signal &)> sig_cap_ls; Client(Connection &connection) : connection_{connection} @@ -71,6 +76,8 @@ public: auto get_my_mode() const -> const std::string &; auto get_my_channels() const -> const std::unordered_set &; + auto list_caps() -> void; + auto is_my_nick(std::string_view nick) const -> bool; auto is_my_mask(std::string_view mask) const -> bool; auto is_channel(std::string_view name) const -> bool; diff --git a/registration.cpp b/registration.cpp index 0ffb4da..83b1f83 100644 --- a/registration.cpp +++ b/registration.cpp @@ -7,7 +7,6 @@ #include #include #include -#include Registration::Registration( const Settings &settings, @@ -20,7 +19,11 @@ Registration::Registration( auto Registration::on_connect() -> void { - client_->get_connection().send_cap_ls(); + client_->list_caps(); + caps_slot_ = client_->sig_cap_ls.connect([self = shared_from_this()](auto &caps) { + self->on_cap_list(caps); + }); + slot_ = client_->get_connection().sig_ircmsg.connect( [self = shared_from_this()](const auto cmd, auto &msg) { @@ -36,10 +39,10 @@ auto Registration::on_connect() -> void client_->get_connection().send_nick(settings_.nickname); } -auto Registration::send_req() -> void +auto Registration::on_cap_list(const std::unordered_map &caps) -> void { std::string request; - std::vector want{ + static const char * const want [] { "account-notify", "account-tag", "batch", @@ -55,100 +58,37 @@ auto Registration::send_req() -> void "solanum.chat/realhost", }; - if (not settings_.sasl_mechanism.empty()) - { - want.push_back("sasl"); - } - for (const auto cap : want) { if (caps.contains(cap)) { request.append(cap); request.push_back(' '); - outstanding.insert(cap); } } - if (not outstanding.empty()) + bool do_sasl = not settings_.sasl_mechanism.empty() && caps.contains("sasl"); + if (do_sasl) { + request.append("sasl "); + } + + if (not request.empty()) { - request.pop_back(); + request.pop_back(); // trailing space client_->get_connection().send_cap_req(request); } - else - { + + if (do_sasl) { + client_->start_sasl( + std::make_unique( + settings_.sasl_authcid, + settings_.sasl_authzid, + settings_.sasl_password)); + } else { client_->get_connection().send_cap_end(); } } -auto Registration::on_msg_cap_ack(const IrcMsg &msg) -> void -{ - auto in = std::istringstream{std::string{msg.args[2]}}; - std::for_each( - std::istream_iterator{in}, - std::istream_iterator{}, - [this](std::string x) { - outstanding.erase(x); - } - ); - if (outstanding.empty()) - { - - if (settings_.sasl_mechanism.empty()) - { - client_->get_connection().send_cap_end(); - } - else - { - client_->start_sasl(std::make_unique(settings_.sasl_authcid, settings_.sasl_authzid, settings_.sasl_password)); - } - } -} - -auto Registration::on_msg_cap_ls(const IrcMsg &msg) -> void -{ - 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 - { - return; - } - - auto in = std::istringstream{std::string{*kvs}}; - - std::for_each( - std::istream_iterator{in}, - std::istream_iterator{}, - [this](std::string x) { - const auto eq = x.find('='); - if (eq == x.npos) - { - caps.emplace(x, std::string{}); - } - else - { - caps.emplace(std::string{x, 0, eq}, std::string{x, eq + 1, x.npos}); - } - } - ); - - if (last) - { - send_req(); - } -} - auto Registration::start( const Settings &settings, std::shared_ptr client @@ -187,20 +127,15 @@ auto Registration::on_ircmsg(const IrcCommand cmd, const IrcMsg &msg) -> void { default: break; - case IrcCommand::CAP: - if (msg.args.size() >= 2 && "LS" == msg.args[1]) { - on_msg_cap_ls(msg); - } else if (msg.args.size() >= 2 && "ACK" == msg.args[1]) { - on_msg_cap_ack(msg); - } - break; - case IrcCommand::ERR_NICKNAMEINUSE: + case IrcCommand::ERR_ERRONEUSNICKNAME: + case IrcCommand::ERR_UNAVAILRESOURCE: randomize_nick(); break; case IrcCommand::RPL_WELCOME: slot_.disconnect(); + caps_slot_.disconnect(); break; case IrcCommand::RPL_SASLSUCCESS: diff --git a/registration.hpp b/registration.hpp index 8318bdf..b87d6be 100644 --- a/registration.hpp +++ b/registration.hpp @@ -14,14 +14,11 @@ class Registration : public std::enable_shared_from_this const Settings &settings_; std::shared_ptr client_; - std::unordered_map caps; - std::unordered_set outstanding; - boost::signals2::scoped_connection slot_; + boost::signals2::scoped_connection caps_slot_; auto on_connect() -> void; - auto on_msg_cap_ls(const IrcMsg &msg) -> void; - auto on_msg_cap_ack(const IrcMsg &msg) -> void; + auto on_cap_list(const std::unordered_map &) -> void; auto on_ircmsg(IrcCommand, const IrcMsg &msg) -> void;