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:
parent
de8d9d23fd
commit
3d01909801
3 changed files with 309 additions and 50 deletions
259
instance.cpp
259
instance.cpp
|
@ -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() {
|
||||
|
|
92
instance.hpp
92
instance.hpp
|
@ -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) {
|
||||
|
|
8
uid.hpp
8
uid.hpp
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue