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

497 lines
18 KiB
C++

#include "instance.hpp"
#include "config.hpp"
#include "uid.hpp"
#include <fmt/format.h>
#include <string>
#include <tuple>
#include <string_view>
#include <algorithm>
#include <memory>
#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_view> strSplit(std::string_view s, char delimiter, size_t times = 0) {
std::vector<std::string_view> 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;
}
static std::tuple<std::string_view, std::string_view> colonSplit(std::string_view s) {
// Find the colon
auto colonPos = s.find(" :");
if (colonPos == s.npos) {
return {s, ""};
}
// Split there
return {s.substr(0, colonPos), s.substr(colonPos+2, s.size()-1)};
}
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] = {split[0].data()+1, split[0].size()-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::string(split[0]);
}
name = std::move(split[1]);
if (split.size() == 3) {
// Get args and text
std::tie(args, text) = colonSplit(split[2]);
}
}
void Command::parse(std::string_view str) {
auto split = strSplit(str, ' ', 1);
name = std::move(split[0]);
// Get text from args
std::tie(args, text) = colonSplit(split[1]);
}
template<bool channelModes>
void ModeSet::parse(std::string_view in, NetworkInfo &netInfo) {
enum {
ADDING,
REMOVING
} op;
// Split up
auto split = strSplit(in, ' ');
auto paramIt = split.begin() + 1;
// Iterate through mode characters
for (const char character : split[0]) {
if (character == '+') {
op = ADDING;
} else if (character == '-') {
op = REMOVING;
} else {
if (op == ADDING) {
if (str.find(character) == str.npos || netInfo.channelModes.isListMode(character)) {
str.push_back(character);
if constexpr(channelModes) {
if (netInfo.channelModes.takesParamOnSet(character)) {
if (paramIt == split.end()) {
throw DesyncError();
}
params.push_back({character, std::string(*(paramIt++))});
}
}
} else {
throw DesyncError();
}
} else if (op == REMOVING) {
auto res = str.find(character);
if (res != str.npos) {
str.erase(res, 1);
if constexpr(channelModes) {
if (netInfo.channelModes.takesParamOnUnset(character)) {
if (paramIt == split.end()) {
throw DesyncError();
}
for (auto it = params.begin(); ; it++) {
if (it == params.end()) {
throw DesyncError();
}
auto &[c, d] = *it;
if (c == character && (d == *paramIt || *paramIt == "*")) {
params.erase(it);
break;
}
}
paramIt++;
}
}
} else {
throw DesyncError();
}
}
}
}
}
void User::parse_euid(const Event& event, NetworkInfo& netInfo) {
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::string(split[1]));
umode.parse<false>(split[3], netInfo);
ident = std::move(split[4]);
host = std::move(split[5]);
realhost = std::move(split[6]);
uid = UUID(std::move(split[7]));
// Get realname
realname = std::move(event.text);
}
void User::removeChannel(const u_Channel& channel) {
std::remove_if(channels.begin(), channels.end(), [&] (auto obj) {return obj.get() == channel;});
}
void Channel::parse_sjoin(const Event& event, Cache& cache, NetworkInfo& netInfo) {
this->server = std::get<SUID>(event.sender.id);
auto split = strSplit(event.args, ' ', 2);
// Check size
if (split.size() != 3) {
throw ParseError("In euid parser: This euid event does not have enough arguments");
}
// Move values
name = std::move(split[1]);
mode.parse<true>(split[2], netInfo);
// Get members
for (auto& raw_uuid : strSplit(event.text, ' ')) {
// Erase prefix
if (raw_uuid.size() > UUID_len) {
char prefixChar = raw_uuid[0];
raw_uuid = {raw_uuid.data()+1, raw_uuid.size()-1};
char modeChar = netInfo.channelModes.prefixMap[prefixChar];
mode.str.push_back(modeChar);
mode.params.push_back({modeChar, std::string(raw_uuid)});
}
// Find user in cache
auto res = cache.find_user_by_uid(UUID(raw_uuid));
if (res == cache.users.end()) {
throw DesyncError();
}
u_User& member = *res;
// Append member to list
members.push_back(member);
}
}
void Channel::removeMember(const u_User& member) {
std::remove_if(members.begin(), members.end(), [&] (auto obj) {return obj.get() == member;});
}
std::vector<u_User>::iterator Cache::find_user_by_nick(std::string_view nick) {
for (auto it = users.begin(); ; it++) {
if (it == users.end() || it->get()->nick == nick) {
return it;
}
}
}
std::vector<u_User>::iterator Cache::find_user_by_uid(const UUID& uid) {
for (auto it = users.begin(); ; it++) {
if (it == users.end() || it->get()->uid == uid) {
return it;
}
}
}
std::vector<u_Channel>::iterator Cache::find_channel_by_name(std::string_view name) {
for (auto it = channels.begin(); ; it++) {
if (it == channels.end() || it->get()->name == name) {
return it;
}
}
}
async::result<void> Instance::run() {
// Create connection
socket.reset(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 = {line.data(), line.size()-1};
}
// Check if server sent an event or command, then parse and process it
if (line[0] == ':') {
Event event;
event.parse(std::move(line));
async::detach(process(std::move(event)));
} else {
Command command;
command.parse(std::move(line));
async::detach(process(std::move(command)));
}
}
}
}
async::result<void> Instance::login() {
co_await socket->send("PASS {} TS 6 :{}\n"_format(config.get().auth.send_password, config.get().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.get().server.name, config.get().server.hidden ? "(H) " : "", config.get().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;
// Wait for burst to complete
std::cout << "I'm done bursting too. Waiting for network informations..." << std::endl;
co_await socket->send("VERSION\n"_sv);
co_await netInfo.wait_ready();
std::cout << "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.get().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.get().server.uid.str(), config.get().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");
}
// Fetched info
else if (event.name == "005") {
// Check if that 005 was for me
if (event.args.find(config.get().server.uid.str()) != 0) {
co_return;
}
// Split the list
auto split = strSplit(event.args, ' ');
// Iterate and find the information we need
for (const auto& field : split) {
// Split into key and value
auto split = strSplit(field, '=', 1);
// Check size
if (split.size() != 2) {
continue;
}
// Check if we've got the right key
if (split[0] == "NETWORK") {
// Network name
netInfo.name = std::move(split[1]);
netInfo.fields_received++;
} else if (split[0] == "CHANMODES") {
// Channel modes
auto modeLists = strSplit(split[1], ',', 3);
netInfo.channelModes = {
.listModes = std::string(modeLists[0]),
.paramOnSetAndUnsetModes = std::string(modeLists[1]),
.paramOnSetOnlyModes = std::string(modeLists[2]),
.paramLessModes = std::string(modeLists[3])
};
netInfo.fields_received++;
} else if (split[0] == "PREFIX") {
auto val = split[1];
// User prefixes
auto modesIt = val.find("(") + 1;
auto prefixesIt = val.find(")") + 1;
while (val[modesIt] != ')') {
netInfo.channelModes.prefixMap[val[prefixesIt]] = val[modesIt];
netInfo.channelModes.listModes.push_back(val[modesIt]);
modesIt++;
prefixesIt++;
}
netInfo.fields_received++;
}
}
// Check if everything needed has been fetched
if (netInfo.fields_received == 3 && !netInfo.ready) {
netInfo.mark_ready();
}
} else {
co_await netInfo.wait_ready();
std::clog << event.dump() << std::flush;
// User updates
if (event.name == "EUID") {
// User connected to the network
// Create user and parse event
auto user = std::make_unique<User>();
user->parse_euid(event, netInfo);
// Append user to cache
cache.users.push_back(std::move(user));
} else if (event.name == "QUIT") {
// User disconnected from the network
// Find user in cache
auto res = cache.find_user_by_uid(std::get<UUID>(event.sender.id));
if (res == cache.users.end()) {
throw DesyncError();
}
// Delete user from all channels
for (auto& channel : res->get()->channels) {
channel.get()->removeMember({*res});
}
// Delete user from cache
cache.users.erase(res);
} else if (event.name == "NICK") {
// User changed their nick
// Find user in cache
auto res = cache.find_user_by_uid(std::get<UUID>(event.sender.id));
if (res == cache.users.end()) {
throw DesyncError();
}
// Set nick
res->get()->nick = strSplit(event.args, ' ', 1)[0];
} else if (event.name == "MODE") {
// User changed their mode
// Find user in cache
auto res = cache.find_user_by_uid(std::get<UUID>(event.sender.id));
if (res == cache.users.end()) {
throw DesyncError();
}
// Update mode
res->get()->umode.parse<false>(event.text, netInfo);
}
// Channel updates
else if (event.name == "SJOIN") {
// Channel was created
// Create channel and parse event
auto channel = std::make_unique<Channel>();
channel->parse_sjoin(event, cache, netInfo);
// Append channel to cache
cache.channels.push_back(std::move(channel));
} else if (event.name == "TOPIC" || event.name == "TB") {
// Channels topic changed
// Find channel in cache
auto res = cache.find_channel_by_name(strSplit(event.args, ' ', 1)[0]);
if (res == cache.channels.end()) {
throw DesyncError();
}
// Set topic
res->get()->topic = event.text;
} else if (event.name == "JOIN") {
// User joined existing channel
// Split args
auto split = strSplit(event.args, ' ', 2);
if (split.size() != 3) {
throw ParseError("In join event parser: join even did not receive enough arguments (expected 3)");
}
// Get channel from cache
auto c_res = cache.find_channel_by_name(split[1]);
if (c_res == cache.channels.end()) {
throw DesyncError();
}
// Get user from cache
auto u_res = cache.find_user_by_uid(std::get<UUID>(event.sender.id));
if (u_res == cache.users.end()) {
throw DesyncError();
}
// Assign user to channel and vice versa
c_res->get()->members.push_back(*u_res);
u_res->get()->channels.push_back(*c_res);
// Update channel modes
c_res->get()->mode.parse<true>(split[2], netInfo);
} else if (event.name == "PART" || event.name == "KICK") {
// User left channel
// Get channel from cache
auto c_res = cache.find_channel_by_name(strSplit(event.args, ' ', 1)[0]);
if (c_res == cache.channels.end()) {
throw DesyncError();
}
// Get user from cache
auto u_res = cache.find_user_by_uid(std::get<UUID>(event.sender.id));
if (u_res == cache.users.end()) {
throw DesyncError();
}
// Remove channel from both user and channel
c_res->get()->removeMember(*u_res);
u_res->get()->removeChannel(*c_res);
} else if (event.name == "TMODE" || event.name == "BMASK") {
// Channel modes changed
// Split args
std::string_view modes, channelName;
{
auto split = strSplit(event.args, ' ', 2);
if (split.size() != 3) {
throw DesyncError();
}
channelName = split[1];
modes = split[2];
}
// Get channel from cache
auto c_res = cache.find_channel_by_name(channelName);
if (c_res == cache.channels.end()) {
throw DesyncError();
}
// Apply changes
c_res->get()->mode.parse<true>("+{} {}"_format(modes, event.text), netInfo);
}
}
//std::clog << event.dump() << std::flush;
}
async::result<void> Instance::send_ping() {
co_await socket->send("PING :{}\n"_format(connected_server.uid.str()));
}