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

First very very very basic ChanServ implementation

This commit is contained in:
Nils 2021-06-28 12:06:55 +02:00
parent 8773d0b886
commit 541239b2d5
11 changed files with 81 additions and 67 deletions

View file

@ -33,6 +33,9 @@ struct DesyncError : public std::exception {
const char *what() const throw() {
return "Server has desynced!!!";
}
DesyncError() {
abort();
}
};
struct InsufficientArgsError : public ParseError {

View file

@ -25,6 +25,4 @@ struct User;
struct Channel;
struct Cache;
struct NetworkInfo;
using u_User = std::unique_ptr<User>;
using u_Channel = std::unique_ptr<Channel>;
#endif

View file

@ -145,8 +145,8 @@ void User::parse_euid(const Event& event, NetworkInfo& netInfo) {
realname = std::move(event.text);
}
void User::removeChannel(const u_Channel& channel) {
std::remove_if(channels.begin(), channels.end(), [&] (auto obj) {return obj == &channel;});
void User::removeChannel(const Channel *channel) {
std::remove_if(channels.begin(), channels.end(), [&] (auto obj) {return obj == channel;});
}
@ -154,13 +154,14 @@ void Channel::parse_sjoin(const Event& event, Cache& cache, NetworkInfo& netInfo
this->server = std::get<SUID>(event.sender.id);
// Check size
argsSizeCheck("SJOIN", event.args, 3);
// Move values
name = std::move(event.args[1]);
// Set values
creationTime = std::stoull(std::string(event.args[0]));
name = event.args[1];
mode.parse<true>(event.args[2], netInfo);
// Get members
for (auto& raw_uuid : strSplit(event.text, ' ')) {
// Erase prefix
if (raw_uuid.size() > UUID_len) {
while (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];
@ -172,18 +173,18 @@ void Channel::parse_sjoin(const Event& event, Cache& cache, NetworkInfo& netInfo
if (res == cache.users.end()) {
throw DesyncError();
}
u_User& member = *res;
User *member = res->get();
// Append member to list
members.push_back(&member);
members.push_back(member);
}
}
void Channel::removeMember(const u_User& member) {
std::remove_if(members.begin(), members.end(), [&] (auto obj) {return *obj == member;});
void Channel::removeMember(const User *member) {
std::remove_if(members.begin(), members.end(), [&] (auto obj) {return obj == member;});
}
std::vector<u_User>::iterator Cache::find_user_by_nick(std::string_view nick) {
std::vector<std::unique_ptr<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;
@ -191,7 +192,7 @@ std::vector<u_User>::iterator Cache::find_user_by_nick(std::string_view nick) {
}
}
std::vector<u_User>::iterator Cache::find_user_by_uid(const UUID& uid) {
std::vector<std::unique_ptr<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;
@ -199,7 +200,7 @@ std::vector<u_User>::iterator Cache::find_user_by_uid(const UUID& uid) {
}
}
std::vector<u_Channel>::iterator Cache::find_channel_by_name(std::string_view name) {
std::vector<std::unique_ptr<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;
@ -388,7 +389,7 @@ async::result<void> Instance::process(Event event) {
}
// Delete user from all channels
for (auto& channel : res->get()->channels) {
channel->get()->removeMember({*res});
channel->removeMember(res->get());
}
// Delete user from cache
cache.users.erase(res);
@ -466,10 +467,10 @@ async::result<void> Instance::process(Event event) {
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);
c_res->get()->members.push_back(u_res->get());
u_res->get()->channels.push_back(c_res->get());
// Update channel modes
c_res->get()->mode.parse<true>(event.args[2], netInfo);
c_res->get()->mode.parse<true>(event.fromArgToEnd(event.args[2]), netInfo);
} else if (event.name == "PART" || event.name == "KICK") {
// User left channel
// Get channel from cache
@ -483,8 +484,8 @@ async::result<void> Instance::process(Event event) {
throw DesyncError();
}
// Remove channel from both user and channel
c_res->get()->removeMember(*u_res);
u_res->get()->removeChannel(*c_res);
c_res->get()->removeMember(u_res->get());
u_res->get()->removeChannel(c_res->get());
} else if (event.name == "TMODE" || event.name == "BMASK") {
// Channel modes changed
argsSizeCheck(event.name, event.args, 3);
@ -492,7 +493,7 @@ async::result<void> Instance::process(Event event) {
std::string_view modes, channelName;
{
channelName = event.args[1];
modes = event.args[2];
modes = event.fromArgToEnd(event.args[2]);
}
// Get channel from cache
auto c_res = cache.find_channel_by_name(channelName);
@ -500,7 +501,7 @@ async::result<void> Instance::process(Event event) {
throw DesyncError();
}
// Apply changes
c_res->get()->mode.parse<true>("+{} {}"_format(modes, event.text), netInfo);
c_res->get()->mode.parse<true>("+{}"_format(modes), netInfo);
}
// Messages
else if (event.name == "PRIVMSG") {
@ -517,7 +518,7 @@ async::result<void> Instance::process(Event event) {
// Pass to services with ownership over target
for (auto& service : services) {
if (service->ready && service->uuid.str() == target.str()) {
co_await service->on_direct_privmsg(event.text, *res);
co_await service->on_direct_privmsg(event.text, res->get());
}
}
}

View file

@ -52,6 +52,9 @@ struct Event { // DON'T std::move() this class. EVER!!!
void splitArgs() {
args = Utility::strSplit(raw_args, ' ');
}
std::string_view fromArgToEnd(std::string_view arg) {
return {arg.data(), raw_args.size() - (arg.data() - raw_args.data())};
}
};
struct Command {
@ -83,17 +86,18 @@ struct Channel {
SUID server;
std::string name;
ModeSet mode;
time_t timeStamp = 0;
std::string topic;
std::vector<u_User*> members;
std::vector<User*> members;
void parse_sjoin(const Event &event, Cache& cache, NetworkInfo& netInfo);
void removeMember(const u_User& member);
void removeMember(const User *member);
Event get_sjoin(const UUID& initial_user) const {
Event get_sjoin(const UUID& initial_user, const SUID& my_sid) const {
return {
.sender = SUID(server),
.sender = my_sid,
.name = "SJOIN",
.raw_args = fmt::format("{} {} +{}", time(nullptr), name, mode.str),
.raw_args = fmt::format("{} {} +", timeStamp?timeStamp:time(nullptr), name),
.text = fmt::format("@{}", initial_user.str())
};
}
@ -105,11 +109,11 @@ struct Channel {
.text = std::string(topic)
};
}
Event get_tmode(const ModeSet& modes, const UUID& user) const {
Event get_tmode(const ModeSet& modes, const AnyUID& sender) const {
return {
.sender = user,
.sender = AnyUID(sender),
.name = "TMODE",
.raw_args = fmt::format("{} {} {}", time(nullptr), name, modes.fullStr())
.raw_args = fmt::format("{} {} +{}", timeStamp?timeStamp:time(nullptr), name, modes.fullStr())
};
}
};
@ -125,11 +129,11 @@ struct User {
std::string realhost;
UUID uid;
std::string realname;
std::vector<u_Channel*> channels;
std::vector<Channel*> channels;
std::optional<std::string> loginName;
void parse_euid(const Event &event, NetworkInfo& netInfo);
void removeChannel(const u_Channel &channel);
void removeChannel(const Channel *channel);
Event get_euid() const {
return {
@ -200,12 +204,12 @@ struct User {
};
struct Cache {
std::vector<u_User> users;
std::vector<u_Channel> channels;
std::vector<std::unique_ptr<User>> users;
std::vector<std::unique_ptr<Channel>> channels;
std::vector<u_User>::iterator find_user_by_nick(std::string_view nick);
std::vector<u_User>::iterator find_user_by_uid(const UUID& uid);
std::vector<u_Channel>::iterator find_channel_by_name(std::string_view name);
std::vector<std::unique_ptr<User>>::iterator find_user_by_nick(std::string_view nick);
std::vector<std::unique_ptr<User>>::iterator find_user_by_uid(const UUID& uid);
std::vector<std::unique_ptr<Channel>>::iterator find_channel_by_name(std::string_view name);
};
struct NetworkInfo {

View file

@ -19,6 +19,7 @@
#include "instance.hpp"
#include "services/nickserv.hpp"
#include "services/chanserv.hpp"
#include <uvpp.hpp>
#include <async/result.hpp>
@ -34,6 +35,7 @@ int main() {
Instance instance(service, parseConfig("config.inil"));
instance.services.push_back(std::make_unique<NickServ>());
instance.services.push_back(std::make_unique<ChanServ>());
async::detach(instance.run());
async::run_forever(rq.run_token(), loop_service_wrapper{service});

View file

@ -50,7 +50,7 @@ std::optional<std::string_view> ServiceBase::getConfig(const std::string& sectio
return {k_res->second};
}
async::result<void> ServiceBase::on_direct_privmsg(std::string_view msg, u_User& author) {
async::result<void> ServiceBase::on_direct_privmsg(std::string_view msg, User *author) {
auto split = Utility::strSplit(msg, ' ');
User user = {.server = i->config.server.uid, .uid = uuid};

View file

@ -38,11 +38,11 @@ public:
UUID uuid;
Instance *i;
bool ready = false;
std::unordered_map<std::string_view, std::tuple<std::function<async::result<void> (u_User& sender, const std::vector<std::string_view>& args)>, std::string_view, size_t, bool>> commands;
std::unordered_map<std::string_view, std::tuple<std::function<async::result<void> (User *sender, const std::vector<std::string_view>& args)>, std::string_view, size_t, bool>> commands;
virtual async::result<void> intitialize() = 0;
virtual async::result<void> on_event(const Event& event);
virtual async::result<void> on_direct_privmsg(std::string_view msg, u_User& author);
virtual async::result<void> on_direct_privmsg(std::string_view msg, User *author);
async::result<void> mark_ready(const User& user);
std::optional<std::string_view> getConfig(const std::string& section, const std::string& key);

View file

@ -38,7 +38,8 @@ async::result<void> ChanServ::intitialize() {
.uid = uuid,
.realname = i->netInfo.name
};
co_await mark_ready(user);
opMe.parse<true>("+o {}"_format(user.uid.str()), i->netInfo);
using namespace sqlite_orm;
storage = std::make_unique<Storage>(make_storage(
@ -51,16 +52,16 @@ async::result<void> ChanServ::intitialize() {
storage->sync_schema();
commands = {
{"register", {[this] (u_User& sender, std::vector<std::string_view> args) -> async::result<void> {
{"register", {[this] (User *sender, std::vector<std::string_view> args) -> async::result<void> {
// Get channel
u_Channel *channel;
Channel *channel;
{
auto res = i->cache.find_channel_by_name(args[0]);
if (res == i->cache.channels.end()) {
co_await i->send_event(user.get_notice("Error: {} does not exist!"_format(args[0]), sender->uid));
co_return;
}
channel = &*res;
channel = res->get();
}
// Check if user is operator in the channel
// TODO
@ -68,7 +69,7 @@ async::result<void> ChanServ::intitialize() {
{
auto channelregs_found = storage->get_all<ChannelReg>(where(c(&ChannelReg::name) == std::string(args[0])));
if (!channelregs_found.empty()) {
co_await i->send_event(user.get_notice("Error: {} has already been registered!"_format(sender->nick), sender->uid));
co_await i->send_event(user.get_notice("Error: {} has already been registered!"_format(args[0]), sender->uid));
co_return;
}
}
@ -85,17 +86,21 @@ async::result<void> ChanServ::intitialize() {
}, "<channel>", 1, true}
},
};
co_await mark_ready(user);
}
async::result<void> ChanServ::initializeChannel(u_Channel *channel, const ChannelReg& channelReg) {
// We need to loop twice because co_await'ing inside a loop through a non-local dynamically-sized container is DANGEROUS
std::vector<u_User*> to_mark_as_admin;
for (const auto member : channel->get()->members) {
if (member->get()->loginName.value_or("") == channelReg.owner) {
to_mark_as_admin.push_back(member);
async::result<void> ChanServ::initializeChannel(Channel *channel, const ChannelReg& channelReg) {
// Get all owners nicks mode +o
ModeSet initialModes;
for (const auto& member : channel->members) {
if (member->loginName.has_value() && member->loginName.value() == channelReg.owner) {
initialModes.parse<true>("+o {}"_format(member->uid.str()), i->netInfo);
}
}
for (const auto member : to_mark_as_admin) {
co_await i->send_event(channel->get()->get_tmode({"o", {std::tuple<char, std::string>{'o', member->get()->uid.str()}}}, member->get()->uid));
}
// Join the channel
auto je = channel->get_sjoin(user.uid, i->config.server.uid); // Needs to be done seperately to avoid some... super weird... crash
co_await i->send_event(je);
// Grant owners nicks OP
co_await i->send_event(channel->get_tmode(initialModes, user.uid));
}

View file

@ -15,8 +15,8 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _TEST_HPP
#define _TEST_HPP
#ifndef _CHANSERV_HPP
#define _CHANSERV_HPP
#include "../instance.hpp"
#include "../serviceBase.hpp"
@ -32,14 +32,15 @@ struct ChannelReg {
std::string name, owner;
};
class ChanServ : public ServiceBase {
using Storage = sqlite_orm::internal::storage_t<sqlite_orm::internal::table_t<ChannelReg, sqlite_orm::internal::column_t<ChannelReg, int, const int& (ChannelReg::*)() const, void (ChannelReg::*)(int), sqlite_orm::constraints::autoincrement_t, sqlite_orm::constraints::primary_key_t<> >, sqlite_orm::internal::column_t<ChannelReg, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (ChannelReg::*)() const, void (ChannelReg::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, sqlite_orm::internal::column_t<ChannelReg, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (ChannelReg::*)() const, void (ChannelReg::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)> > >;
class ChanServ : public ServiceBase {
std::unique_ptr<Storage> storage;
User user;
ModeSet opMe;
virtual async::result<void> intitialize() override;
async::result<void> initializeChannel(u_Channel *channel, const ChannelReg& channelReg);
async::result<void> initializeChannel(Channel *channel, const ChannelReg& channelReg);
};
#endif

View file

@ -55,7 +55,7 @@ async::result<void> NickServ::intitialize() {
storage->sync_schema();
commands = {
{"identify", {[this] (u_User& sender, const std::vector<std::string_view>& args) -> async::result<void> {
{"identify", {[this] (User *sender, const std::vector<std::string_view>& args) -> async::result<void> {
// Check that user is not already identified
if (sender->loginName.has_value()) {
co_await i->send_event(user.get_notice("Error: You've already been identified!", sender->uid));
@ -88,12 +88,12 @@ async::result<void> NickServ::intitialize() {
co_await i->send_event(user.get_notice("You have been identified!", sender->uid));
}, "[username] <password>", 1, false}
},
{"deidentify", {[this] (u_User& sender, const std::vector<std::string_view>& args) -> async::result<void> {
{"deidentify", {[this] (User *sender, const std::vector<std::string_view>& args) -> async::result<void> {
co_await i->send_event(sender->get_encap_su("", i->config.server.uid));
co_await i->send_event(user.get_notice("You have been deidentified!", sender->uid));
}, "", 0, true}
},
{"register", {[this] (u_User& sender, std::vector<std::string_view> args) -> async::result<void> {
{"register", {[this] (User *sender, std::vector<std::string_view> args) -> async::result<void> {
// Check args
if (args.size() != 2) {
co_await i->send_event(user.get_notice("Error: Passwords may not contain spaces!", sender->uid));

View file

@ -15,8 +15,8 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _TEST_HPP
#define _TEST_HPP
#ifndef _NICKSERV_HPP
#define _NICKSERV_HPP
#include "../instance.hpp"
#include "../serviceBase.hpp"
@ -32,9 +32,9 @@ struct Account {
std::string name, email, password_hash;
};
class NickServ : public ServiceBase {
using Storage = sqlite_orm::internal::storage_t<sqlite_orm::internal::table_t<Account, sqlite_orm::internal::column_t<Account, int, const int& (Account::*)() const, void (Account::*)(int), sqlite_orm::constraints::autoincrement_t, sqlite_orm::constraints::primary_key_t<> >, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, sqlite_orm::internal::column_t<Account, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >& (Account::*)() const, void (Account::*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)> > >;
class NickServ : public ServiceBase {
std::unique_ptr<Storage> storage;
User user;