xbot/registration_thread.cpp
2023-11-26 15:08:55 -08:00

217 lines
5.1 KiB
C++

#include "registration_thread.hpp"
#include <memory>
#include <unordered_set>
#include <unordered_map>
namespace {
struct RegistrationThread : std::enable_shared_from_this<RegistrationThread>
{
Connection * connection_;
std::string password_;
std::string username_;
std::string realname_;
std::string nickname_;
std::unordered_map<std::string, std::string> caps;
std::unordered_set<std::string> outstanding;
Connection::Handle<ConnectEvent> connect_handle_;
Connection::Handle<IrcMsgEvent> message_handle_;
enum class Stage
{
LsReply,
AckReply,
};
Stage stage_;
RegistrationThread(
Connection * connection_,
std::string password,
std::string username,
std::string realname,
std::string nickname
);
auto on_connect() -> void;
auto send_req() -> void;
auto capack(IrcMsg const& msg) -> void;
auto capls(IrcMsg const& msg) -> void;
auto on_msg(IrcMsg const& msg) -> void;
};
RegistrationThread::RegistrationThread(
Connection * connection,
std::string password,
std::string username,
std::string realname,
std::string nickname
)
: connection_{connection}
, password_{password}
, username_{username}
, realname_{realname}
, nickname_{nickname}
, stage_{Stage::LsReply}
{}
auto RegistrationThread::on_connect() -> void
{
write_irc(*connection_, "CAP", "LS", "302");
write_irc(*connection_, "PASS", password_);
write_irc(*connection_, "USER", username_, "*", "*", realname_);
write_irc(*connection_, "NICK", nickname_);
connection_->remove_listener(connect_handle_);
}
auto RegistrationThread::send_req() -> void
{
std::string request;
char const* const want[] = {
"extended-join",
"account-notify",
"draft/chathistory",
"batch",
"soju.im/no-implicit-names",
"chghost",
"setname",
"account-tag",
"solanum.chat/oper",
"solanum.chat/identify-msg",
"solanum.chat/realhost",
"server-time",
"invite-notify",
"extended-join"
};
for (auto cap : want)
{
if (caps.contains(cap))
{
request.append(cap);
request.push_back(' ');
outstanding.insert(cap);
}
}
if (not outstanding.empty())
{
request.pop_back();
write_irc(*connection_, "CAP", "REQ", request);
stage_ = Stage::AckReply;
}
else
{
write_irc(*connection_, "CAP", "END");
connection_->remove_listener(message_handle_);
}
}
auto RegistrationThread::capack(IrcMsg const& msg) -> void
{
auto const n = msg.args.size();
if (n >= 2 && "*" == msg.args[0] && "ACK" == msg.args[1])
{
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())
{
write_irc(*connection_, "CAP", "END");
connection_->remove_listener(message_handle_);
}
}
}
auto RegistrationThread::capls(IrcMsg const& msg) -> void
{
auto const n = msg.args.size();
if (n >= 2 && "*" == msg.args[0] && "LS" == msg.args[1])
{
std::string_view const* kvs;
bool last;
if (3 == n)
{
kvs = &msg.args[2];
last = true;
}
else if (4 == n && "*" == 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) {
auto const 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 RegistrationThread::on_msg(IrcMsg const& msg) -> void
{
switch (stage_)
{
case Stage::LsReply: capls(msg); return;
case Stage::AckReply: capack(msg); return;
}
}
} // namespace
auto registration_thread(
Connection * connection,
std::string password,
std::string username,
std::string realname,
std::string nickname
) -> void
{
auto const thread = std::make_shared<RegistrationThread>(connection, password, username, realname, nickname);
thread->message_handle_ = connection->add_listener<IrcMsgEvent>([thread](IrcMsgEvent const& event)
{
if (IrcCommand::CAP == event.command)
{
thread->on_msg(event.irc);
}
});
thread->connect_handle_ = connection->add_listener<ConnectEvent>([thread](ConnectEvent const&)
{
thread->on_connect();
});
}