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

Huge jump forwards

This commit is contained in:
Nils 2021-06-18 21:23:19 +02:00
parent de8d9d23fd
commit 3d01909801
3 changed files with 309 additions and 50 deletions

View file

@ -4,7 +4,10 @@
#include <fmt/format.h>
#include <string>
#include <tuple>
#include <string_view>
#include <algorithm>
#include <memory>
#include <ctime>
using fmt::operator""_format;
@ -15,8 +18,8 @@ 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;
static std::vector<std::string_view> strSplit(std::string_view s, char delimiter, std::vector<std::string>::size_type 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));
@ -27,71 +30,156 @@ static std::vector<std::string> strsplit(std::string_view s, char delimiter, std
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+1), s.substr(colonPos+2, s.size()-1)};
}
void Event::parse(std::string_view str) {
auto split = strsplit(str, ' ', 2);
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 ':'
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::move(split[0]);
sender = std::string(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]);
}
std::tie(args, text) = colonSplit(split[2]);
}
}
void Command::parse(std::string_view str) {
auto split = strsplit(str, ' ', 1);
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]);
std::tie(args, text) = colonSplit(split[1]);
}
void ModeSet::parse(std::string_view in, NetworkInfo &netInfo) {
enum {
ADDING,
REMOVING
} op;
for (const auto character : in) {
if (character == '+') {
op = ADDING;
} else if (character == '-') {
op = REMOVING;
} else {
if (op == ADDING) {
if (str.find(character) == str.npos) {
str.push_back(character);
}
} else if (op == REMOVING) {
auto res = str.find(character);
if (res != str.npos) {
str.erase(res);
}
}
}
}
}
void User::parse(Event event) {
void User::parse_euid(const Event& event, NetworkInfo& netInfo) {
this->server = std::get<SUID>(event.sender.id);
auto split = strsplit(event.args, ' ', 8);
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]);
hops = std::stoull(std::string(split[1]));
umode.parse(split[3], netInfo);
ident = std::move(split[4]);
host = std::move(split[5]);
realhost = std::move(split[6]);
id = UUID(std::move(split[7]));
uid = UUID(std::move(split[7]));
// Get realname
realname = std::move(event.text);
}
void User::removeChannel(const s_Channel& channel) {
std::remove(channels.begin(), channels.end(), 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(split[2], netInfo);
// Get members
for (auto& raw_uuid : strSplit(event.text, ' ')) {
s_User member;
// Erase leading sign
if (raw_uuid.size() > UUID_len) {
raw_uuid = {raw_uuid.data()+1, raw_uuid.size()-1};
}
// Find user in cache
auto res = cache.find_user_by_uid(UUID(raw_uuid));
if (res == cache.users.end()) {
throw DesyncError();
}
member = *res;
// Append member to list
members.push_back(std::move(member));
}
}
void Channel::removeMember(const s_User &member) {
std::remove(members.begin(), members.end(), member);
}
std::vector<s_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<s_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<s_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
@ -120,24 +208,22 @@ async::result<void> Instance::run() {
static_cast<std::string_view::size_type>(data->nread)
};
// Split by newlines
for (auto& line : strsplit(dataStr, '\n')) {
for (auto& line : strSplit(dataStr, '\n')) {
if (line.size() < 2) {
continue; // Empty line
}
// Remove \r
if (line.back() == '\r') {
line.pop_back();
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));
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)));
}
}
@ -155,14 +241,18 @@ async::result<void> Instance::burst() {
// End burst
co_await send_ping();
client_bursting = false;
std::cout << "I'm done bursting too. Ready." << std::endl;
// 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];
auto given_password = strSplit(command.args, ' ', 1)[0];
if (given_password != config.auth.accept_password) {
throw ConnectionError("Server supplied wrong password during authentication");
}
@ -174,7 +264,7 @@ async::result<void> Instance::process(const Command command) {
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.name = strSplit(command.args, ' ', 1)[0];
connected_server.description = command.text;
} else if (command.name == "PING") {
// End of burst
@ -193,13 +283,106 @@ async::result<void> Instance::process(const Event event) {
// 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;
// Fetched info
else if (event.name == "005") {
// Check if that 005 was for me
if (event.args.find(config.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
std::cout << split[0] << ' ' << split[1] << std::endl;
if (split[0] == "NETWORK") {
netInfo.name = std::move(split[1]);
netInfo.fields_received++;
} else if (split[0] == "CHANMODES") {
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++;
}
}
// Check if everything needed has been fetched
if (netInfo.fields_received == 2 && !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_shared<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->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(event.text, netInfo);
}
// Channel updates
else if (event.name == "SJOIN") {
// Channel was created
// Create channel and parse event
auto channel = std::make_shared<Channel>();
channel->parse_sjoin(event, cache, netInfo);
// Append channel to cache
cache.channels.push_back(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;
}
}
//std::clog << event.dump() << std::flush;
}
async::result<void> Instance::send_ping() {

View file

@ -4,10 +4,13 @@
#include <uvpp.hpp>
#include <async/result.hpp>
#include <async/oneshot-event.hpp>
#include <frg/std_compat.hpp>
#include <fmt/format.h>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
#include <memory>
#include <stdexcept>
#include <ctime>
@ -19,6 +22,21 @@ struct ParseError : public std::runtime_error {
struct ConnectionError : public std::runtime_error {
using std::runtime_error::runtime_error;
};
struct DesyncError : public std::exception {
const char *what() const throw() {
return "Server has desynced!!!";
}
DesyncError() {
std::cout << "DESCONACCREKO" << std::endl;
}
};
struct User;
struct Channel;
struct Cache;
struct NetworkInfo;
using s_User = std::shared_ptr<User>;
using s_Channel = std::shared_ptr<Channel>;
struct Event {
AnyUID sender;
@ -39,31 +57,80 @@ struct Command {
void parse(std::string_view str);
};
struct ModeSet {
std::string str;
void parse(std::string_view str, NetworkInfo& netInfo);
ModeSet() {}
};
struct User {
SUID server;
// ... == "EUID"
std::string nick;
size_t hops;
time_t ts;
std::string umode;
ModeSet umode;
std::string ident;
std::string host;
std::string realhost;
UUID id;
UUID uid;
std::string realname;
User() {
ts = time(nullptr);
}
std::vector<s_Channel> channels;
Event dump() const {
return Event{
.sender = SUID(server),
.name = "EUID",
.args = fmt::format("{} 1 {} {} {} {} 0 {} * * :{}", nick, ts, umode, ident, host, id.str(), realname)
.args = fmt::format("{} 1 {} {} {} {} 0 {} * * :{}", nick, time(nullptr), umode.str, ident, host, uid.str(), realname)
};
}
void parse(Event event);
void parse_euid(const Event &event, NetworkInfo& netInfo);
void removeChannel(const s_Channel &channel);
};
struct Channel {
SUID server;
std::string name;
ModeSet mode;
std::string topic;
std::vector<s_User> members;
void parse_sjoin(const Event &event, Cache& cache, NetworkInfo& netInfo);
void removeMember(const s_User& member);
};
struct Cache {
std::vector<s_User> users;
std::vector<s_Channel> channels;
std::vector<s_User>::iterator find_user_by_nick(std::string_view nick);
std::vector<s_User>::iterator find_user_by_uid(const UUID& uid);
std::vector<s_Channel>::iterator find_channel_by_name(std::string_view name);
};
struct NetworkInfo {
async::oneshot_event ready_event;
size_t fields_received = 0;
bool ready = false;
std::string name;
struct {
// list modes first, then modes that take a param when setting but not when unsetting (just +k), then modes that can only be set once and take args, then modes that can only be set once but take no args.
std::string listModes, // Lists like mode b
paramOnSetAndUnsetModes, // Properties like k
paramOnSetOnlyModes, // Properties like f
paramLessModes; // Properties like z
} channelModes;
async::result<void> wait_ready() {
if (!ready) {
co_await ready_event.wait();
}
}
void mark_ready() {
ready = true;
ready_event.raise();
}
};
class Instance {
@ -72,10 +139,11 @@ class Instance {
uvpp::Addr addr;
uvpp::tcp *socket = nullptr;
Config::Server connected_server;
NetworkInfo netInfo;
Cache cache;
bool server_bursting = true, client_bursting = true;
bool authed = false;
std::unordered_map<std::string_view, User> users;
public:
Instance(uvpp::loop_service &s, const Config& config) : s(s), config(config) {

View file

@ -23,6 +23,10 @@ public:
*this = UID(std::string_view{initializer, len});
}
constexpr bool operator ==(const UID& other) {
return str() == other.str();
}
constexpr bool has_value() const {
return array[0] != '\0';
}
@ -62,6 +66,10 @@ struct AnyUID {
}
}
bool operator ==(const AnyUID& other) {
return str() == other.str();
}
auto operator =(const SUID& val) {
type = SERVER;
id = val;