From 47de861f45ad72a59aceabdf313401dbd3f8d2a0 Mon Sep 17 00:00:00 2001 From: jackb-p Date: Fri, 5 Aug 2016 02:27:46 +0100 Subject: [PATCH] Now tracks and caches all (I think) required data --- TriviaBot/bot/GatewayHandler.cpp | 557 ++++++++++++++---- TriviaBot/bot/GatewayHandler.hpp | 31 +- TriviaBot/bot/TriviaBot.cpp | 9 +- TriviaBot/bot/data_structures/Channel.hpp | 14 + TriviaBot/bot/data_structures/Guild.hpp | 34 +- TriviaBot/bot/data_structures/GuildMember.hpp | 68 +++ TriviaBot/bot/data_structures/Role.hpp | 118 ++++ TriviaBot/bot/data_structures/User.hpp | 3 + 8 files changed, 718 insertions(+), 116 deletions(-) create mode 100644 TriviaBot/bot/data_structures/GuildMember.hpp create mode 100644 TriviaBot/bot/data_structures/Role.hpp diff --git a/TriviaBot/bot/GatewayHandler.cpp b/TriviaBot/bot/GatewayHandler.cpp index 7fef1cf..9f359af 100644 --- a/TriviaBot/bot/GatewayHandler.cpp +++ b/TriviaBot/bot/GatewayHandler.cpp @@ -3,8 +3,8 @@ #include #include "APIHelper.hpp" -#include "data_structures/User.hpp" #include "Logger.hpp" +#include "data_structures/GuildMember.hpp" extern std::string bot_token; @@ -64,131 +64,482 @@ void GatewayHandler::on_dispatch(json decoded, client &c, websocketpp::connectio json data = decoded["d"]; if (event_name == "READY") { - user_object.load_from_json(data["user"]); - - Logger::write("Sign-on confirmed. (@" + user_object.username + "#" + user_object.discriminator + ")", Logger::LogLevel::Info); + on_event_ready(data); } else if (event_name == "GUILD_CREATE") { - std::string guild_id = data["id"]; - guilds[guild_id] = std::make_unique(data); - - Logger::write("Loaded guild: " + guilds[guild_id]->name, Logger::LogLevel::Debug); - - for (json channel : data["channels"]) { - std::string channel_id = channel["id"]; - channel["guild_id"] = guild_id; - // create channel obj, add to overall channel list - channels[channel_id] = std::make_shared(channel); - // add ptr to said channel list to guild's channel list - guilds[guild_id]->channels.push_back(std::shared_ptr(channels[channel_id])); - } - - if (v8_instances.count(guild_id) == 0) { - v8_instances[guild_id] = std::make_unique(ah); - Logger::write("Created v8 instance for guild " + guild_id, Logger::LogLevel::Debug); - } - - Logger::write("Loaded " + std::to_string(guilds.size()) + " guild(s)", Logger::LogLevel::Info); + on_event_guild_create(data); + } + else if (event_name == "GUILD_UPDATE") { + on_event_guild_update(data); + } + else if (event_name == "GUILD_DELETE") { + on_event_guild_delete(data); + } + else if (event_name == "GUILD_MEMBER_ADD") { + on_event_guild_member_add(data); + } + else if (event_name == "GUILD_MEMBER_UPDATE") { + on_event_guild_member_update(data); + } + else if (event_name == "GUILD_MEMBER_REMOVE") { + on_event_guild_member_remove(data); + } + else if (event_name == "GUILD_ROLE_CREATE") { + on_event_guild_role_create(data); + } + else if (event_name == "GUILD_ROLE_UPDATE") { + on_event_guild_role_update(data); + } + else if (event_name == "GUILD_ROLE_DELETE") { + on_event_guild_role_delete(data); + } + else if (event_name == "CHANNEL_CREATE") { + on_event_channel_create(data); + } + else if (event_name == "CHANNEL_UPDATE") { + on_event_channel_update(data); + } + else if (event_name == "CHANNEL_DELETE") { + on_event_channel_delete(data); } - else if (event_name == "TYPING_START") {} else if (event_name == "MESSAGE_CREATE") { - std::string message = data["content"]; - auto channel = channels[data["channel_id"]]; + on_event_message_create(data); + } +} - DiscordObjects::User sender(data["author"]); +void GatewayHandler::on_event_ready(json data) { + user_object.load_from_json(data["user"]); - std::vector words; - boost::split(words, message, boost::is_any_of(" ")); - Command custom_command; - if (words[0] == "`trivia" || words[0] == "`t") { - int questions = 10; - int delay = 8; + Logger::write("Sign-on confirmed. (@" + user_object.username + "#" + user_object.discriminator + ")", Logger::LogLevel::Info); +} - if (words.size() > 3) { - ah->send_message(channel->id, ":exclamation: Invalid arguments!"); +void GatewayHandler::on_event_guild_create(json data) { + guilds[data["id"]] = DiscordObjects::Guild(data); + DiscordObjects::Guild &guild = guilds[data["id"]]; + + Logger::write("Loaded guild " + guild.id + ", now in " + std::to_string(guilds.size()) + " guild(s)", Logger::LogLevel::Info); + + int channels_added = 0, roles_added = 0, members_added = 0; + + for (json channel : data["channels"]) { + std::string channel_id = channel["id"]; + channel["guild_id"] = guild.id; + + channels[channel_id] = DiscordObjects::Channel(channel); + guilds[guild.id].channels.push_back(&channels[channel_id]); + + channels_added++; + } + for (json role : data["roles"]) { + std::string role_id = role["id"]; + + roles[role_id] = DiscordObjects::Role(role); + guilds[guild.id].roles.push_back(&roles[role_id]); + + roles_added++; + } + for (json member : data["members"]) { + std::string user_id = member["user"]["id"]; + + auto it = users.find(user_id); + if (it == users.end()) { // new user + users[user_id] = DiscordObjects::User(member["user"]); + } + users[user_id].guilds.push_back(guild.id); + + DiscordObjects::GuildMember guild_member(member, &users[user_id]); + for (std::string role_id : member["roles"]) { + guild_member.roles.push_back(&roles[role_id]); + } + + guilds[guild.id].members.push_back(guild_member); + } + + if (v8_instances.count(guild.id) == 0) { + v8_instances[guild.id] = std::make_unique(ah); + Logger::write("Created v8 instance for guild " + guild.id, Logger::LogLevel::Debug); + } + + Logger::write("Loaded " + std::to_string(channels_added) + " channels, " + std::to_string(roles_added) + + " roles and " + std::to_string(members_added) + " members to guild " + guild.id, Logger::LogLevel::Debug); +} + +void GatewayHandler::on_event_guild_update(json data) { + std::string guild_id = data["id"]; + + guilds[guild_id].load_from_json(data); + Logger::write("Updated guild " + guild_id, Logger::LogLevel::Debug); +} + +void GatewayHandler::on_event_guild_delete(json data) { + std::string guild_id = data["id"]; + bool unavailable = data.value("unavailable", false); + + if (unavailable) { + Logger::write("Guild " + guild_id + " has become unavailable", Logger::LogLevel::Info); + guilds[guild_id].unavailable = true; + } else { + int channels_removed = 0; + for (auto it = channels.cbegin(); it != channels.cend();) { + if (it->second.guild_id == guild_id) { + channels.erase(it++); + channels_removed++; + } else { + ++it; + } + } + + guilds.erase(guilds.find(guild_id)); + Logger::write("Guild " + guild_id + " and " + std::to_string(channels_removed) + " channels removed", Logger::LogLevel::Info); + } +} + +void GatewayHandler::on_event_guild_member_add(json data) { + std::string guild_id = data["guild_id"]; + std::string user_id = data["user"]["id"]; + + auto it = users.find(user_id); + if (it == users.end()) { // new user + users[user_id] = DiscordObjects::User(data["user"]); + } + users[user_id].guilds.push_back(guild_id); + + DiscordObjects::GuildMember guild_member(data, &users[user_id]); + for (std::string role_id : data["roles"]) { + guild_member.roles.push_back(&roles[role_id]); + } + + guilds[guild_id].members.push_back(guild_member); + + Logger::write("Added new member " + guild_member.user->id + " to guild " + guild_id, Logger::LogLevel::Debug); +} + +void GatewayHandler::on_event_guild_member_update(json data) { + std::cout << data.dump(4) << std::endl; + + std::string user_id = data["user"]["id"]; + DiscordObjects::Guild &guild = guilds[data["guild_id"]]; + + auto it = std::find_if(guild.members.begin(), guild.members.begin(), [user_id](const DiscordObjects::GuildMember &gm) { + return gm.user->id == user_id; + }); + if (it != guild.members.end()) { + bool nick_changed = false; + size_t roles_change = 0; + + std::string nick = data.value("nick", it->nick); + if (it->nick != nick) { + it->nick = nick; + nick_changed = true; + } + + roles_change = it->roles.size(); + it->roles.clear(); // reset and re-fill, changing the differences is probably more expensive anyway. + for (std::string role_id : data["roles"]) { + it->roles.push_back(&roles[role_id]); + } + roles_change = it->roles.size() - roles_change; + + std::string debug_string = "Updated member " + user_id + " of guild " + guild.id; + if (nick_changed) debug_string += ". Nick changed to " + nick; + if (roles_change != 0) debug_string += ". No. of roles changed by " + std::to_string(roles_change); + debug_string += "."; + + Logger::write(debug_string, Logger::LogLevel::Debug); + } + else { + Logger::write("Tried to update member " + user_id + " (of guild " + guild.id + ") who does not exist.", Logger::LogLevel::Warning); + } +} + +void GatewayHandler::on_event_guild_member_remove(json data) { + DiscordObjects::Guild &guild = guilds[data["guild_id"]]; + std::string user_id = data["user"]["id"]; + + auto it = std::find_if(guild.members.begin(), guild.members.begin(), [user_id](const DiscordObjects::GuildMember &gm) { + return gm.user->id == user_id; + }); + + if (it != guilds[guild.id].members.end()) { + guild.members.erase(it); + + DiscordObjects::User &user = users[user_id]; + user.guilds.erase(std::remove(user.guilds.begin(), user.guilds.end(), user.id)); + + if (user.guilds.size() == 0) { + users.erase(users.find(user_id)); + Logger::write("User " + user_id + " removed from guild " + guild.id + " and no longer visible, deleted.", Logger::LogLevel::Debug); + } + else { + Logger::write("User " + user_id + " removed from guild " + guild.id, Logger::LogLevel::Debug); + } + } + else { + Logger::write("Tried to remove guild member " + user_id + " which doesn't exist", Logger::LogLevel::Warning); + } +} + +void GatewayHandler::on_event_guild_role_create(json data) { + std::string role_id = data["role"]["id"]; + std::string guild_id = data["guild_id"]; + roles[role_id] = DiscordObjects::Role(data["role"]); + + guilds[guild_id].roles.push_back(&roles[role_id]); + + Logger::write("Created role " + role_id + " on guild " + guild_id, Logger::LogLevel::Debug); +} + +void GatewayHandler::on_event_guild_role_update(json data) { + std::string role_id = data["role"]["id"]; + + roles[role_id].load_from_json(data["role"]); +} + +void GatewayHandler::on_event_guild_role_delete(json data) { + std::string role_id = data["role_id"]; + auto it = roles.find(role_id); + + if (it != roles.end()) { + DiscordObjects::Role &role = roles[role_id]; + DiscordObjects::Guild &guild = guilds[data["guild_id"]]; + + auto check_lambda = [role_id](const DiscordObjects::Role *r) { + return r->id == role_id; + }; + + auto it2 = std::find_if(guild.roles.begin(), guild.roles.end(), check_lambda); + if (it2 != guild.roles.end()) { + guild.roles.erase(it2); + } + else { + Logger::write("Tried to delete role " + role_id + " from guild " + guild.id + " but it doesn't exist there", Logger::LogLevel::Warning); + } + + roles.erase(it); + Logger::write("Deleted role " + role_id + " (guild " + guild.id + ").", Logger::LogLevel::Debug); + } + else { + Logger::write("Tried to delete role " + role_id + " but it doesn't exist.", Logger::LogLevel::Warning); + } +} + +void GatewayHandler::on_event_channel_create(json data) { + std::string channel_id = data["id"]; + std::string guild_id = data["guild_id"]; + + channels[channel_id] = DiscordObjects::Channel(data); + Logger::write("Added channel " + channel_id + " to channel list. Now " + std::to_string(channels.size()) + " channels stored", Logger::LogLevel::Debug); + guilds[guild_id].channels.push_back(&channels[channel_id]); + Logger::write("Added channel " + channel_id + " to guild " + guild_id + "'s list. Now " + std::to_string(guilds[guild_id].channels.size()) + " channels stored", Logger::LogLevel::Debug); +} + +void GatewayHandler::on_event_channel_update(json data) { + std::cout << "Update: " << data.dump(4) << std::endl; + + std::string channel_id = data["id"]; + + auto it = channels.find(channel_id); + if (it == channels.end()) { + Logger::write("Got channel update for channel " + channel_id + " that doesn't exist. Creating channel instead.", Logger::LogLevel::Warning); + on_event_channel_create(data); + } else { + channels[channel_id].load_from_json(data); + Logger::write("Updated channel " + channel_id, Logger::LogLevel::Debug); + } +} + +void GatewayHandler::on_event_channel_delete(json data) { + std::cout << "Delete: " << data.dump(4) << std::endl; + + std::string channel_id = data["id"]; + std::string guild_id = data["guild_id"]; + + auto it = channels.find(channel_id); + if (it == channels.end()) { + Logger::write("Tried to delete channel " + channel_id + " which doesn't exist", Logger::LogLevel::Warning); + } + else { + auto it2 = std::find_if(guilds[guild_id].channels.begin(), guilds[guild_id].channels.begin(), [channel_id](const DiscordObjects::Channel *c) { + return c->id == channel_id; + }); + guilds[guild_id].channels.erase(it2); + Logger::write("Removed channel " + channel_id + " from guild " + guild_id + "'s list. Now " + + std::to_string(guilds[guild_id].channels.size()) + " channels stored", Logger::LogLevel::Debug); + + channels.erase(it); + Logger::write("Removed channel " + channel_id + " from channel list. Now " + std::to_string(channels.size()) + " channels stored.", Logger::LogLevel::Debug); + } +} + +void GatewayHandler::on_event_message_create(json data) { + std::string message = data["content"]; + auto channel = channels[data["channel_id"]]; + + DiscordObjects::User sender(data["author"]); + + std::vector words; + boost::split(words, message, boost::is_any_of(" ")); + Command custom_command; + if (words[0] == "`trivia" || words[0] == "`t") { + int questions = 10; + int delay = 8; + + if (words.size() > 3) { + ah->send_message(channel.id, ":exclamation: Invalid arguments!"); + return; + } + else if (words.size() > 1) { + if (words[1] == "help" || words[1] == "h") { + std::string help = "**Base command \\`t[rivia]**. Arguments:\n"; + help += "\\`trivia **{x}** **{y}**: Makes the game last **x** number of questions, optionally sets the time interval between hints to **y** seconds\n"; + help += "\\`trivia **stop**: stops the ongoing game.\n"; + help += "\\`trivia **help**: prints this message\n"; + + ah->send_message(channel.id, help); return; } - else if(words.size() > 1) { - if (words[1] == "help" || words[1] == "h") { - std::string help = "**Base command \\`t[rivia]**. Arguments:\n"; - help += "\\`trivia **{x}** **{y}**: Makes the game last **x** number of questions, optionally sets the time interval between hints to **y** seconds\n"; - help += "\\`trivia **stop**: stops the ongoing game.\n"; - help += "\\`trivia **help**: prints this message\n"; - - ah->send_message(channel->id, help); + else if (words[1] == "stop" || words[1] == "s") { + if (games.find(channel.id) != games.end()) { + delete_game(channel.id); return; } - else if (words[1] == "stop" || words[1] == "s") { - if (games.find(channel->id) != games.end()) { - delete_game(channel->id); - return; + } + else { + try { + questions = std::stoi(words[1]); + if (words.size() == 3) { + delay = std::stoi(words[2]); } } + catch (std::invalid_argument e) { + ah->send_message(channel.id, ":exclamation: Invalid arguments!"); + return; + } + } + } + + games[channel.id] = std::make_unique(this, ah, channel.id, questions, delay); + games[channel.id]->start(); + } + else if (words[0] == "`guilds") { + std::string m = "Guild List:\n"; + for (auto &gu : guilds) { + m += "> " + gu.second.name + " (" + gu.second.id + ") Channels: " + std::to_string(gu.second.channels.size()) + "\n"; + } + ah->send_message(channel.id, m); + } + else if (words[0] == "`info") { + ah->send_message(channel.id, ":information_source: trivia-bot by Jack. "); + } + else if (words[0] == "`js" && message.length() > 4) { + std::string js = message.substr(4); + auto it = v8_instances.find(channel.guild_id); + if (it != v8_instances.end() && js.length() > 0) { + it->second->exec_js(js, channel.id); + } + } + else if (words[0] == "`createjs" && message.length() > 8) { + std::string args = message.substr(10); + size_t seperator_loc = args.find("|"); + if (seperator_loc != std::string::npos) { + std::string command_name = args.substr(0, seperator_loc); + std::string script = args.substr(seperator_loc + 1); + int result = command_helper->insert_command(channel.guild_id, command_name, script); + switch (result) { + case 0: + ah->send_message(channel.id, ":warning: Error!"); break; + case 1: + ah->send_message(channel.id, ":new: Command `" + command_name + "` successfully created."); break; + case 2: + ah->send_message(channel.id, ":arrow_heading_up: Command `" + command_name + "` successfully updated."); break; + } + } + } + else if (words[0] == "`shutdown" && sender.id == "82232146579689472") { // it me + ah->send_message(channel.id, ":zzz: Goodbye!"); + // TODO: without needing c, hdl - c.close(hdl, websocketpp::close::status::going_away, "`shutdown command used."); + } + else if (words[0] == "`debug") { + if (words[1] == "channel" && words.size() == 3) { + auto it = channels.find(words[2]); + if (it != channels.end()) { + ah->send_message(channel.id, it->second.to_debug_string()); + } + else { + ah->send_message(channel.id, ":question: Unrecognised channel."); + } + } + else if (words[1] == "guild" && words.size() == 3) { + auto it = guilds.find(words[2]); + if (it != guilds.end()) { + ah->send_message(channel.id, it->second.to_debug_string()); + } + else { + ah->send_message(channel.id, ":question: Unrecognised guild."); + } + } + else if (words[1] == "member" && words.size() == 4) { + auto it = guilds.find(words[2]); + if (it != guilds.end()) { + std::string user_id = words[3]; + auto it2 = std::find_if(it->second.members.begin(), it->second.members.end(), [user_id](const DiscordObjects::GuildMember &gm) { + return user_id == gm.user->id; + }); + + if (it2 != it->second.members.end()) { + ah->send_message(channel.id, it2->to_debug_string()); + } else { - try { - questions = std::stoi(words[1]); - if (words.size() == 3) { - delay = std::stoi(words[2]); - } - } - catch (std::invalid_argument e) { - ah->send_message(channel->id, ":exclamation: Invalid arguments!"); - return; - } + ah->send_message(channel.id, ":question: Unrecognised user."); } } + else { + ah->send_message(channel.id, ":question: Unrecognised guild."); + } + } + else if (words[1] == "role" && words.size() == 3) { + auto it = roles.find(words[2]); + if (it != roles.end()) { + ah->send_message(channel.id, it->second.to_debug_string()); + } + else { + ah->send_message(channel.id, ":question: Unrecognised role."); + } + } + else if (words[1] == "role" && words.size() == 4) { + std::string role_name = words[3]; - games[channel->id] = std::make_unique(this, ah, channel->id, questions, delay); - games[channel->id]->start(); - } - else if (words[0] == "`guilds") { - std::string m = "Guild List:\n"; - for (auto &gu : guilds) { - m += "> " + gu.second->name + " (" + gu.second->id + ") Channels: " + std::to_string(gu.second->channels.size()) + "\n"; - } - ah->send_message(channel->id, m); - } - else if (words[0] == "`info") { - ah->send_message(channel->id, ":information_source: trivia-bot by Jack. "); - } - else if (words[0] == "`js" && message.length() > 4) { - std::string js = message.substr(4); - auto it = v8_instances.find(channel->guild_id); - if (it != v8_instances.end() && js.length() > 0) { - it->second->exec_js(js, channel->id); - } - } - else if (words[0] == "`createjs" && message.length() > 8) { - std::string args = message.substr(10); - size_t seperator_loc = args.find("|"); - if (seperator_loc != std::string::npos) { - std::string command_name = args.substr(0, seperator_loc); - std::string script = args.substr(seperator_loc + 1); - int result = command_helper->insert_command(channel->guild_id, command_name, script); - switch (result) { - case 0: - ah->send_message(channel->id, ":warning: Error!"); break; - case 1: - ah->send_message(channel->id, ":new: Command `" + command_name + "` successfully created."); break; - case 2: - ah->send_message(channel->id, ":arrow_heading_up: Command `" + command_name + "` successfully updated."); break; + auto it = guilds.find(words[2]); + if (it != guilds.end()) { + auto check_lambda = [role_name](DiscordObjects::Role *r) { + return role_name == r->name; + }; + + auto it2 = std::find_if(it->second.roles.begin(), it->second.roles.end(), check_lambda); + if (it2 != it->second.roles.end()) { + ah->send_message(channel.id, (*it2)->to_debug_string()); + } + else { + ah->send_message(channel.id, ":question: Unrecognised role."); } } - } - else if (words[0] == "`shutdown" && sender.id == "82232146579689472") { // it me - ah->send_message(channel->id, ":zzz: Goodbye!"); - c.close(hdl, websocketpp::close::status::going_away, "`shutdown command used."); - } - else if (command_helper->get_command(channel->guild_id, words[0], custom_command)) { - auto it = v8_instances.find(channel->guild_id); - if (it != v8_instances.end() && custom_command.script.length() > 0) { - it->second->exec_js(custom_command.script, channel->id); + else { + ah->send_message(channel.id, ":question: Unrecognised guild."); } } - else if (games.find(channel->id) != games.end()) { // message received in channel with ongoing game - games[channel->id]->handle_answer(message, sender); + else { + ah->send_message(channel.id, ":question: Unknown parameters."); } } + else if (command_helper->get_command(channel.guild_id, words[0], custom_command)) { + auto it = v8_instances.find(channel.guild_id); + if (it != v8_instances.end() && custom_command.script.length() > 0) { + it->second->exec_js(custom_command.script, channel.id); + } + } + else if (games.find(channel.id) != games.end()) { // message received in channel with ongoing game + games[channel.id]->handle_answer(message, sender); + } } void GatewayHandler::identify(client &c, websocketpp::connection_hdl &hdl) { diff --git a/TriviaBot/bot/GatewayHandler.hpp b/TriviaBot/bot/GatewayHandler.hpp index 0b781bd..19d579c 100644 --- a/TriviaBot/bot/GatewayHandler.hpp +++ b/TriviaBot/bot/GatewayHandler.hpp @@ -14,6 +14,7 @@ #include "data_structures/User.hpp" #include "data_structures/Guild.hpp" #include "data_structures/Channel.hpp" +#include "data_structures/Role.hpp" typedef websocketpp::client client; using json = nlohmann::json; @@ -58,6 +59,27 @@ private: int last_seq; int heartbeat_interval; + void on_event_ready(json data); + + /* guild events */ + void on_event_guild_create(json data); // https://discordapp.com/developers/docs/topics/gateway#guild-create + void on_event_guild_update(json data); // https://discordapp.com/developers/docs/topics/gateway#guild-update + void on_event_guild_delete(json data); // https://discordapp.com/developers/docs/topics/gateway#guild-delete + void on_event_guild_member_add(json data); // https://discordapp.com/developers/docs/topics/gateway#guild-member-add + void on_event_guild_member_update(json data); // https://discordapp.com/developers/docs/topics/gateway#guild-member-update + void on_event_guild_member_remove(json data); // https://discordapp.com/developers/docs/topics/gateway#guild-member-remove + void on_event_guild_role_create(json data); // https://discordapp.com/developers/docs/topics/gateway#guild-role-create + void on_event_guild_role_update(json data); // https://discordapp.com/developers/docs/topics/gateway#guild-role-update + void on_event_guild_role_delete(json data); // https://discordapp.com/developers/docs/topics/gateway#guild-role-delete + + /* channel events */ + void on_event_channel_create(json data); // https://discordapp.com/developers/docs/topics/gateway#channel-create + void on_event_channel_update(json data); // https://discordapp.com/developers/docs/topics/gateway#channel-update + void on_event_channel_delete(json data); // https://discordapp.com/developers/docs/topics/gateway#channel-delete + + /* message events */ + void on_event_message_create(json data); // https://discordapp.com/developers/docs/topics/gateway#message-create + const int protocol_version = 5; // bot's user obj @@ -65,10 +87,11 @@ private: std::unique_ptr command_helper; - // - std::map> guilds; - // channels pointers are shared pointers, held here but also in guild objects - std::map> channels; + /* */ + std::map guilds; + std::map channels; + std::map users; + std::map roles; // std::map> games; diff --git a/TriviaBot/bot/TriviaBot.cpp b/TriviaBot/bot/TriviaBot.cpp index d284b93..1a22042 100644 --- a/TriviaBot/bot/TriviaBot.cpp +++ b/TriviaBot/bot/TriviaBot.cpp @@ -34,13 +34,13 @@ int main(int argc, char *argv[]) { conn.start(uri); } catch (const std::exception &e) { - Logger::write(e.what(), Logger::LogLevel::Severe); + Logger::write("std exception: " + std::string(e.what()), Logger::LogLevel::Severe); } catch (websocketpp::lib::error_code e) { - Logger::write(e.message(), Logger::LogLevel::Severe); + Logger::write("websocketpp exception: " + e.message(), Logger::LogLevel::Severe); } catch (...) { - Logger::write("Other exception.", Logger::LogLevel::Severe); + Logger::write("other exception.", Logger::LogLevel::Severe); } v8::V8::Dispose(); @@ -51,5 +51,8 @@ int main(int argc, char *argv[]) { Logger::write("Cleaned up", Logger::LogLevel::Info); + std::cout << "Press enter to exit" << std::endl; + std::getchar(); + return 0; } \ No newline at end of file diff --git a/TriviaBot/bot/data_structures/Channel.hpp b/TriviaBot/bot/data_structures/Channel.hpp index 9aef03d..1529db3 100644 --- a/TriviaBot/bot/data_structures/Channel.hpp +++ b/TriviaBot/bot/data_structures/Channel.hpp @@ -32,6 +32,7 @@ namespace DiscordObjects { Channel(json data); void load_from_json(json data); + std::string to_debug_string(); bool operator==(Channel rhs); @@ -73,6 +74,19 @@ namespace DiscordObjects { user_limit = data.value("user_limit", -1); } + inline std::string Channel::to_debug_string() { + return "**__Channel " + id + "__**" + + "\n**guild_id:** " + guild_id + + "\n**name:** " + name + + "\n**type:** " + type + + "\n**position:** " + std::to_string(position) + + "\n**is_private:** " + std::to_string(is_private) + + "\n**topic:** " + (topic == "" ? "[empty]" : topic) + + "\n**last_message_id:** " + last_message_id + + "\n**bitrate:** " + std::to_string(bitrate) + + "\n**user_limit:** " + std::to_string(user_limit); + } + inline bool Channel::operator==(Channel rhs) { return id == rhs.id && id != "null"; } diff --git a/TriviaBot/bot/data_structures/Guild.hpp b/TriviaBot/bot/data_structures/Guild.hpp index 933d082..78ab956 100644 --- a/TriviaBot/bot/data_structures/Guild.hpp +++ b/TriviaBot/bot/data_structures/Guild.hpp @@ -1,13 +1,15 @@ -#ifndef BOT_DATA__STRUCTURES_Guild -#define BOT_DATA__STRUCTURES_Guild +#ifndef BOT_DATA__STRUCTURES_GUILD +#define BOT_DATA__STRUCTURES_GUILD #include -#include +#include #include "../json/json.hpp" #include "Channel.hpp" #include "User.hpp" +#include "Role.hpp" +#include "GuildMember.hpp" using json = nlohmann::json; @@ -48,6 +50,7 @@ namespace DiscordObjects { Guild(json data); void load_from_json(json data); + std::string to_debug_string(); bool operator==(Guild rhs); @@ -62,13 +65,15 @@ namespace DiscordObjects { // bool embed_enabled; // std::string embed_channel_id; int verification_level; - // TODO: Implement all guil fields + // TODO: Implement all guild fields // std::vector voice_states - // std::vector roles // std::vector emojis // std::vector features + bool unavailable; - std::vector> channels; + std::vector channels; + std::vector members; + std::vector roles; //std::vector> users; }; @@ -93,6 +98,23 @@ namespace DiscordObjects { afk_channel_id = data.value("afk_channel_id", "null"); afk_timeout = data.value("afk_timeout", -1); verification_level = data.value("verification_level", -1); + unavailable = data.value("unavailable", false); + } + + inline std::string Guild::to_debug_string() { + return "**__Guild " + id + "__**" + + "\n**name:** " + name + + "\n**icon:** " + icon + + "\n**splash:** " + splash + + "\n**owner_id:** " + owner_id + + "\n**region:** " + region + + "\n**afk_channel_id:** " + afk_channel_id + + "\n**afk_timeout:** " + std::to_string(afk_timeout) + + "\n**verification_level:** " + std::to_string(verification_level) + + "\n**unavailable:** " + std::to_string(unavailable) + + "\n**channels:** " + std::to_string(channels.size()) + + "\n**roles:** " + std::to_string(roles.size()) + + "\n**members:** " + std::to_string(members.size()); } inline bool Guild::operator==(Guild rhs) { diff --git a/TriviaBot/bot/data_structures/GuildMember.hpp b/TriviaBot/bot/data_structures/GuildMember.hpp new file mode 100644 index 0000000..0997c21 --- /dev/null +++ b/TriviaBot/bot/data_structures/GuildMember.hpp @@ -0,0 +1,68 @@ +#ifndef BOT_DATA__STRUCTURES_GUILDMEMBER +#define BOT_DATA__STRUCTURES_GUILDMEMBER + +#include +#include + +#include "../json/json.hpp" + +#include "User.hpp" +#include "Role.hpp" + +namespace DiscordObjects { + class GuildMember { + public: + GuildMember(); + GuildMember(json data, User *user); + + void load_from_json(json data); + std::string to_debug_string(); + + bool operator==(GuildMember rhs); + + User *user; + std::string nick; + std::vector roles; + std::string joined_at; // TODO: better type + bool deaf; + bool mute; + }; + + inline GuildMember::GuildMember() { + user = nullptr; + nick = "null"; + joined_at = "null"; + deaf = false; + mute = false; + } + + inline GuildMember::GuildMember(json data, User *user) { + this->user = user; + load_from_json(data); + } + + inline void GuildMember::load_from_json(json data) { + nick = data.value("nick", "null"); + joined_at = data.value("joined_at", "null"); + deaf = data.value("deaf", false); + mute = data.value("mute", false); + } + + inline std::string GuildMember::to_debug_string() { + return "**__GuildMember " + user->id + "__**" + + "\n**mention:** <@" + user->id + "> / " + user->username + "#" + user->discriminator + + "\n**bot:** " + std::to_string(user->bot) + + "\n**mfa_enabled:** " + std::to_string(user->mfa_enabled) + + "\n**avatar:** " + user->avatar + + "\n**nick:** " + nick + + "\n**joined_at:** " + joined_at + + "\n**deaf:** " + std::to_string(deaf) + + "\n**mute:** " + std::to_string(mute); + } + + inline bool GuildMember::operator==(GuildMember rhs) { + return user->id == rhs.user->id; + } +} + +#endif \ No newline at end of file diff --git a/TriviaBot/bot/data_structures/Role.hpp b/TriviaBot/bot/data_structures/Role.hpp new file mode 100644 index 0000000..3776e06 --- /dev/null +++ b/TriviaBot/bot/data_structures/Role.hpp @@ -0,0 +1,118 @@ +#ifndef BOT_DATA__STRUCTURES_ROLE +#define BOT_DATA__STRUCTURES_ROLE + +#include +#include +#include + +#include "../json/json.hpp" + +using json = nlohmann::json; + +namespace DiscordObjects { + + class Role { + public: + Role(); + Role(json data); + + void load_from_json(json data); + std::string to_debug_string(); + + bool operator==(Role rhs); + + std::string id; + std::string name; + int colour; + bool hoist; + int position; + int permissions; + bool managed; + bool mentionable; + }; + + inline Role::Role() { + id = "null"; + name = "null"; + colour = -1; + hoist = false; + position = -1; + permissions = 0; + managed = false; + mentionable = false; + } + + inline Role::Role(json data) { + load_from_json(data); + } + + inline void Role::load_from_json(json data) { + id = data.value("id", "null"); + name = data.value("name", "null"); + colour = data.value("color", -1); + hoist = data.value("hoist", false); + position = data.value("position", -1); + permissions = data.value("permissions", 0); + managed = data.value("managed", false); + mentionable = data.value("mentionable", false); + } + + inline std::string Role::to_debug_string() { + std::stringstream colour_ss; + colour_ss << std::setw(6) << std::setfill('0') << colour; + std::string colour_str = colour_ss.str(); + + return "**__Role " + id + "__**" + + "\n**name:** " + name + + "\n**colour:** #" + colour_str + + "\n**hoist:** " + std::to_string(hoist) + + "\n**position:** " + std::to_string(position) + + "\n**permissions:** " + std::to_string(permissions) + + "\n**managed:** " + std::to_string(managed) + + "\n**mentionable:** " + std::to_string(mentionable); + } + + inline bool Role::operator==(Role rhs) { + return id == rhs.id; + } + + + /* permission values */ + enum class Permission { + CreateInstantInvite = 0x00000001, // Allows creation of instant invites + KickMembers = 0x00000002, // Allows kicking members + BanMembers = 0x00000004, // Allows banning members + Administrator = 0x00000008, // Allows all permissions and bypasses channel permission overwrites + ManageChannels = 0x00000010, // Allows management and editing of channels + ManageGuild = 0x00000020, // Allows management and editing of the guild + ReadMessages = 0x00000400, // Allows reading messages in a channel.The channel will not appear for users without this permission + SendMessages = 0x00000800, // Allows for sending messages in a channel. + SendTTSMessages = 0x00001000, // Allows for sending of / tts messages + ManageMessages = 0x00002000, // Allows for deletion of other users messages + EmbedLinks = 0x00004000, // Links sent by this user will be auto - embedded + AttachFiles = 0x00008000, // Allows for uploading images and files + ReadMessageHistory = 0x00010000, // Allows for reading of message history + MentionEveryone = 0x00020000, // Allows for using the @everyone tag to notify all users in a channel, and the @here tag to notify all online users in a channel + Connect = 0x00100000, // Allows for joining of a voice channel + Speak = 0x00200000, // Allows for speaking in a voice channel + MuteMembers = 0x00400000, // Allows for muting members in a voice channel + DeafenMembers = 0x00800000, // Allows for deafening of members in a voice channel + MoveMembers = 0x01000000, // Allows for moving of members between voice channels + UseVAD = 0x02000000, // Allows for using voice - activity - detection in a voice channel + ChangeNickname = 0x04000000, // Allows for modification of own nickname + ManageNicknames = 0x08000000, // Allows for modification of other users nicknames + ManageRoles = 0x10000000 // Allows management and editing of roles + }; + + /* implement bitwise operators */ + inline Permission operator|(Permission lhs, Permission rhs) { + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + inline Permission operator|=(Permission &lhs, Permission rhs) { + lhs = static_cast(static_cast(lhs) | static_cast(rhs)); + return lhs; + } +} + +#endif \ No newline at end of file diff --git a/TriviaBot/bot/data_structures/User.hpp b/TriviaBot/bot/data_structures/User.hpp index 1bc36e8..546a1fc 100644 --- a/TriviaBot/bot/data_structures/User.hpp +++ b/TriviaBot/bot/data_structures/User.hpp @@ -2,6 +2,7 @@ #define BOT_DATA__STRUCTURES_USER #include +#include #include "../json/json.hpp" @@ -37,6 +38,8 @@ namespace DiscordObjects { std::string avatar; bool bot; bool mfa_enabled; + + std::vector guilds; }; inline User::User() {