#include "registration.hpp" #include "connection.hpp" #include "ircmsg.hpp" #include "sasl_mechanism.hpp" #include #include #include Registration::Registration( Connection &connection, const Settings &settings, std::shared_ptr self ) : connection_{connection} , settings_{settings} , self_{std::move(self)} { } auto Registration::on_connect() -> void { connection_.send_cap_ls(); listen_for_cap_ls(); if (not settings_.password.empty()) { connection_.send_pass(settings_.password); } connection_.send_user(settings_.username, settings_.realname); connection_.send_nick(settings_.nickname); } auto Registration::send_req() -> void { std::string request; std::vector want{ "account-notify", "account-tag", "batch", "chghost", "draft/chathistory", "extended-join", "invite-notify", "server-time", "setname", "soju.im/no-implicit-names", "solanum.chat/identify-msg", "solanum.chat/oper", "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()) { request.pop_back(); connection_.send_cap_req(request); listen_for_cap_ack(); } else { 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()) { slot_.disconnect(); connection_.send_cap_end(); } else { self_->start_sasl(std::make_unique(settings_.sasl_authcid, settings_.sasl_authzid, settings_.sasl_password)); slot_ = connection_.sig_ircmsg.connect([thread = shared_from_this()](auto cmd, auto &msg) { switch (cmd) { default: break; case IrcCommand::RPL_SASLSUCCESS: case IrcCommand::ERR_SASLFAIL: thread->connection_.send_cap_end(); thread->slot_.disconnect(); } }); } } } 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) { slot_.disconnect(); send_req(); } } auto Registration::start( Connection &connection, const Settings &settings, std::shared_ptr self ) -> std::shared_ptr { const auto thread = std::make_shared(connection, std::move(settings), std::move(self)); thread->slot_ = connection.sig_connect.connect([thread]() { thread->slot_.disconnect(); thread->on_connect(); }); return thread; } auto Registration::listen_for_cap_ack() -> void { slot_ = connection_.sig_ircmsg.connect([thread = shared_from_this()](IrcCommand cmd, const IrcMsg &msg) { if (IrcCommand::CAP == cmd && msg.args.size() >= 2 && "ACK" == msg.args[1]) { thread->on_msg_cap_ack(msg); } }); } auto Registration::listen_for_cap_ls() -> void { slot_ = connection_.sig_ircmsg.connect([thread = shared_from_this()](IrcCommand cmd, const IrcMsg &msg) { if (IrcCommand::CAP == cmd && msg.args.size() >= 2 && "LS" == msg.args[1]) { thread->on_msg_cap_ls(msg); } else if (IrcCommand::RPL_WELCOME == cmd) { // Server doesn't support CAP negotiation thread->slot_.disconnect(); } }); }