xbot/registration.cpp
2025-01-27 09:30:09 -08:00

212 lines
4.8 KiB
C++

#include "registration.hpp"
#include "connection.hpp"
#include "ircmsg.hpp"
#include "sasl_mechanism.hpp"
#include <memory>
#include <random>
#include <unordered_map>
#include <unordered_set>
Registration::Registration(
const Settings &settings,
std::shared_ptr<Client> client
)
: settings_{settings}
, client_{std::move(client)}
{
}
auto Registration::on_connect() -> void
{
client_->get_connection().send_cap_ls();
slot_ = client_->get_connection().sig_ircmsg.connect(
[self = shared_from_this()](const auto cmd, auto &msg)
{
self->on_ircmsg(cmd, msg);
}
);
if (not settings_.password.empty())
{
client_->get_connection().send_pass(settings_.password);
}
client_->get_connection().send_user(settings_.username, settings_.realname);
client_->get_connection().send_nick(settings_.nickname);
}
auto Registration::send_req() -> void
{
std::string request;
std::vector<const char *> 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();
client_->get_connection().send_cap_req(request);
}
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<std::string>{in},
std::istream_iterator<std::string>{},
[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<SaslPlain>(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<std::string>{in},
std::istream_iterator<std::string>{},
[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> client
) -> std::shared_ptr<Registration>
{
const auto thread = std::make_shared<Registration>(std::move(settings), std::move(client));
thread->slot_ = thread->client_->get_connection().sig_connect.connect([thread]() {
thread->slot_.disconnect();
thread->on_connect();
});
return thread;
}
auto Registration::randomize_nick() -> void
{
std::string new_nick;
new_nick += settings_.nickname.substr(0, 8);
std::random_device rd;
std::mt19937 gen{rd()};
std::uniform_int_distribution<> distrib(0, 35);
for (int i = 0; i < 8; ++i) {
const auto x = distrib(gen);
new_nick += x < 10 ? '0' + x : 'A' + (x-10);
}
client_->get_connection().send_nick(new_nick);
}
auto Registration::on_ircmsg(const IrcCommand cmd, const IrcMsg &msg) -> void
{
switch (cmd)
{
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:
randomize_nick();
break;
case IrcCommand::RPL_WELCOME:
slot_.disconnect();
break;
case IrcCommand::RPL_SASLSUCCESS:
case IrcCommand::ERR_SASLFAIL:
client_->get_connection().send_cap_end();
break;
}
}