1
0
Fork 0
mirror of https://gitlab.com/niansa/asbots.git synced 2025-03-06 20:48:25 +01:00
asbots/instance.cpp
2021-06-18 15:25:14 +02:00

207 lines
6.5 KiB
C++

#include "instance.hpp"
#include "config.hpp"
#include "uid.hpp"
#include <fmt/format.h>
#include <string>
#include <string_view>
#include <ctime>
using fmt::operator""_format;
inline std::string_view operator ""_sv(const char *str, unsigned long len) {
return {str, static_cast<std::string_view::size_type>(len)};
}
static std::vector<std::string> strsplit(std::string_view s, char delimiter, std::vector<std::string>::size_type times = 0) {
std::vector<std::string> to_return;
decltype(s.size()) start = 0, finish = 0;
while ((finish = s.find_first_of(delimiter, start)) != std::string_view::npos) {
to_return.emplace_back(s.substr(start, finish - start));
start = finish + 1;
if (to_return.size() == times) { break; }
}
to_return.emplace_back(s.substr(start));
return to_return;
}
void Event::parse(std::string_view str) {
auto split = strsplit(str, ' ', 2);
// Check split size
if (split.size() < 2) {
throw ParseError("In event parser: Events are expected to have both a sender and name");
}
// Move values
split[0].erase(0, 1); // Erase leading ':'
auto id_len = split[0].size();
if (id_len == SUID_len) {
sender = SUID(std::move(split[0]));
} else if (id_len == UUID_len) {
sender = UUID(std::move(split[0]));
} else {
sender = std::move(split[0]);
}
name = std::move(split[1]);
if (split.size() == 3) {
// Get args and text
split = strsplit(std::move(split[2]), ':', 1);
if (split.size() == 2) {
text = std::move(split[1]);
}
if (!split.empty()) {
args = std::move(split[0]);
}
}
}
void Command::parse(std::string_view str) {
auto split = strsplit(str, ' ', 1);
name = std::move(split[0]);
// Get text from args
split = strsplit(std::move(split[1]), ':', 1);
if (split.size() == 2) {
text = std::move(split[1]);
}
if (!split.empty()) {
args = std::move(split[0]);
}
}
void User::parse(Event event) {
this->server = std::get<SUID>(event.sender.id);
auto split = strsplit(event.args, ' ', 8);
// Check size
if (split.size() != 9) {
throw ParseError("In euid parser: This euid event does not have enough arguments");
}
// Move values
nick = std::move(split[0]);
hops = std::stoull(std::move(split[1]));
ts = std::stoull(std::move(split[2]));
umode = std::move(split[3]);
ident = std::move(split[4]);
host = std::move(split[5]);
realhost = std::move(split[6]);
id = UUID(std::move(split[7]));
// Get realname
realname = std::move(event.text);
}
async::result<void> Instance::run() {
// Create connection
socket = new uvpp::tcp{s};
co_await socket->connect(addr);
socket->recv_start();
// Login
co_await login();
// Mainloop
while (true) {
// Read
auto data = co_await socket->recv();
// Check for general error
if (data->error()) {
continue;
}
// Check for broken connection
if (data->broken()) {
break;
}
// Make string
auto dataStr = std::string_view{
data->data.get(),
static_cast<std::string_view::size_type>(data->nread)
};
// Split by newlines
for (auto& line : strsplit(dataStr, '\n')) {
if (line.size() < 2) {
continue; // Empty line
}
// Remove \r
if (line.back() == '\r') {
line.pop_back();
}
// Check if server sent an event or command, then parse and process it
if (line[0] == ':') {
Event event;
event.parse(std::move(line));
std::clog << event.dump() << std::flush;
async::detach(process(std::move(event)));
} else {
Command command;
command.parse(std::move(line));
std::clog << command.dump() << std::flush;
async::detach(process(std::move(command)));
}
}
}
}
async::result<void> Instance::login() {
co_await socket->send("PASS {} TS 6 :{}\n"_format(config.auth.send_password, config.server.uid.str()));
co_await socket->send("CAPAB :QS EX IE KLN UNKLN ENCAP TB SERVICES EUID EOPMOD MLOCK\n"_sv);
co_await socket->send("SERVER {} 1 :{}{}\n"_format(config.server.name, config.server.hidden ? "(H) " : "", config.server.description));
co_await socket->send("SVINFO 6 3 0 :{}\n"_format(time(nullptr)));
}
async::result<void> Instance::burst() {
// End burst
co_await send_ping();
client_bursting = false;
std::cout << "I'm done bursting too. Ready." << std::endl;
}
async::result<void> Instance::process(const Command command) {
if (command.name == "PASS") {
// Check password
{
auto given_password = strsplit(command.args, ' ', 1)[0];
if (given_password != config.auth.accept_password) {
throw ConnectionError("Server supplied wrong password during authentication");
}
authed = true;
}
// Get server ID
connected_server.uid = SUID(command.text);
} if (!authed) {
throw ConnectionError("Server tried to execute a command before authenticating");
} else if (command.name == "SERVER") {
// Get name and description of connected server
connected_server.name = strsplit(command.args, ' ', 1)[0];
connected_server.description = command.text;
} else if (command.name == "PING") {
// End of burst
if (server_bursting) {
server_bursting = false;
std::cout << "Server burst is over. It's my turn." << std::endl;
co_await burst();
}
// Reply
co_await socket->send(":{} PONG {} {} :{}\n"_format(config.server.uid.str(), config.server.name, command.args, command.text));
}
}
async::result<void> Instance::process(const Event event) {
if (event.name == "NOTICE") {
// Don't do anything special
} else if (!authed) {
throw ConnectionError("Server tried to send an event before authenticating");
} else if (event.name == "EUID") {
User user;
user.parse(event);
std::cout << "User registered: " << user.dump().dump() << std::flush;
users[user.id.str()] = std::move(user);
}
co_return;
}
async::result<void> Instance::send_ping() {
co_await socket->send("PING :{}\n"_format(connected_server.uid.str()));
}