diff --git a/TriviaBot/bot/GatewayHandler.cpp b/TriviaBot/bot/GatewayHandler.cpp index 7e3af06..1916ebc 100644 --- a/TriviaBot/bot/GatewayHandler.cpp +++ b/TriviaBot/bot/GatewayHandler.cpp @@ -11,6 +11,7 @@ GatewayHandler::GatewayHandler() { last_seq = 0; ah = std::make_shared(); + command_helper = std::make_unique(); } void GatewayHandler::handle_data(std::string data, client &c, websocketpp::connection_hdl &hdl) { @@ -95,6 +96,7 @@ void GatewayHandler::on_dispatch(json decoded, client &c, websocketpp::connectio 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; @@ -136,14 +138,6 @@ void GatewayHandler::on_dispatch(json decoded, client &c, websocketpp::connectio games[channel->id] = std::make_unique(this, ah, channel->id, questions, delay); games[channel->id]->start(); } - else if (words[0] == "`channels") { - std::string m = "Channel List:\n"; - for (auto ch : channels) { - m += "> " + ch.second->name + " (" + ch.second->id + ") [" + ch.second->type + "] Guild: " - + guilds[ch.second->guild_id]->name + " (" + ch.second->guild_id + ")\n"; - } - ah->send_message(channel->id, m); - } else if (words[0] == "`guilds") { std::string m = "Guild List:\n"; for (auto &gu : guilds) { @@ -154,13 +148,40 @@ void GatewayHandler::on_dispatch(json decoded, client &c, websocketpp::connectio else if (words[0] == "`info") { ah->send_message(channel->id, ":information_source: trivia-bot by Jack. "); } - else if (words[0] == "`js") { - std::string js = message.erase(0, 3); + 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()) { + 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!"); + 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 if (games.find(channel->id) != games.end()) { // message received in channel with ongoing game games[channel->id]->handle_answer(message, sender); } diff --git a/TriviaBot/bot/GatewayHandler.hpp b/TriviaBot/bot/GatewayHandler.hpp index 775a1a6..0b781bd 100644 --- a/TriviaBot/bot/GatewayHandler.hpp +++ b/TriviaBot/bot/GatewayHandler.hpp @@ -9,6 +9,7 @@ #include "json/json.hpp" #include "TriviaGame.hpp" +#include "js/CommandHelper.hpp" #include "js/V8Instance.hpp" #include "data_structures/User.hpp" #include "data_structures/Guild.hpp" @@ -62,6 +63,8 @@ private: // bot's user obj DiscordObjects::User user_object; + std::unique_ptr command_helper; + // std::map> guilds; // channels pointers are shared pointers, held here but also in guild objects diff --git a/TriviaBot/bot/TriviaBot.cpp b/TriviaBot/bot/TriviaBot.cpp index aeee6ca..10d8ceb 100644 --- a/TriviaBot/bot/TriviaBot.cpp +++ b/TriviaBot/bot/TriviaBot.cpp @@ -40,8 +40,6 @@ int main(int argc, char *argv[]) { std::cerr << "other exception" << std::endl; } - std::getchar(); - v8::V8::Dispose(); v8::V8::ShutdownPlatform(); delete platform; diff --git a/TriviaBot/bot/db/schema.sqlite b/TriviaBot/bot/db/schema.sqlite index 462cf6a..7cae0cd 100644 --- a/TriviaBot/bot/db/schema.sqlite +++ b/TriviaBot/bot/db/schema.sqlite @@ -1,14 +1,20 @@ BEGIN TRANSACTION; CREATE TABLE "TotalScores" ( - `User` TEXT UNIQUE, - `TotalScore` INTEGER, - `AverageTime` INTEGER, + `User` TEXT NOT NULL, + `TotalScore` INTEGER NOT NULL, + `AverageTime` INTEGER NOT NULL, PRIMARY KEY(User) ); CREATE TABLE "Questions" ( - `ID` INTEGER PRIMARY KEY AUTOINCREMENT, - `Category` TEXT, - `Question` TEXT, - `Answer` TEXT + `ID` INTEGER PRIMARY KEY AUTOINCREMENT, + `Category` TEXT NOT NULL, + `Question` TEXT NOT NULL, + `Answer` TEXT NOT NULL ); -COMMIT; \ No newline at end of file +CREATE TABLE `CustomJS` ( + `ID` INTEGER PRIMARY KEY AUTOINCREMENT, + `GuildID` TEXT NOT NULL, + `CommandName` TEXT NOT NULL, + `Script` TEXT NOT NULL +); +COMMIT; diff --git a/TriviaBot/bot/js/CommandHelper.cpp b/TriviaBot/bot/js/CommandHelper.cpp new file mode 100644 index 0000000..dade323 --- /dev/null +++ b/TriviaBot/bot/js/CommandHelper.cpp @@ -0,0 +1,153 @@ +#include "CommandHelper.hpp" + +#include +#include + +#include + +CommandHelper::CommandHelper() { + sqlite3 *db; int return_code; + return_code = sqlite3_open("bot/db/trivia.db", &db); + + std::string sql = "SELECT * FROM CustomJS"; + + sqlite3_stmt *stmt; + return_code = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0); + + + while (return_code != SQLITE_DONE) { + return_code = sqlite3_step(stmt); + + if (return_code == SQLITE_ROW) { + std::string guild_id = reinterpret_cast(sqlite3_column_text(stmt, 0)); + std::string command_name = reinterpret_cast(sqlite3_column_text(stmt, 1)); + std::string script = reinterpret_cast(sqlite3_column_text(stmt, 2)); + + commands.push_back({ guild_id, command_name, script }); + } + else if (return_code != SQLITE_DONE) { + sqlite3_finalize(stmt); + std::cerr << "SQLite error." << std::endl; + return; + } + } + + std::cout << commands.size() << " commands loaded." << std::endl; + + sqlite3_finalize(stmt); + sqlite3_close(db); +} + +bool CommandHelper::get_command(std::string guild_id, std::string command_name, Command &command) { + auto check_lambda = [guild_id, command_name](const Command &c) { + return guild_id == c.guild_id && command_name == c.command_name; + }; + + auto it = std::find_if(commands.begin(), commands.end(), check_lambda); + if (it == commands.end()) { + command = {}; + return false; + } else { + command = { it->guild_id, it->command_name, it->script }; + } +} + +// returns: 0 error, 1 inserted, 2 updated +int CommandHelper::insert_command(std::string guild_id, std::string command_name, std::string script) { + // TODO: if script empty, delete command + + Command command { guild_id, command_name, script }; + + int ret_value; + std::string sql; + if (command_in_db(guild_id, command_name)) { + sql = "UPDATE CustomJS SET Script=?1 WHERE GuildID=?2 AND CommandName=?3;"; + std::cout << "Command already exists, updating." << std::endl; + ret_value = 2; + } else { + sql = "INSERT INTO CustomJS(Script, GuildID, CommandName) VALUES (?1, ?2, ?3);"; + std::cout << "Inserting new command." << std::endl; + ret_value = 1; + } + + sqlite3 *db; int return_code; + return_code = sqlite3_open("bot/db/trivia.db", &db); + if (!return_code_ok(return_code)) return 0; + + sqlite3_stmt *stmt; + return_code = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0); + if (!return_code_ok(return_code)) return 0; + + return_code = sqlite3_bind_text(stmt, 1, script.c_str(), -1, (sqlite3_destructor_type)-1); + if (!return_code_ok(return_code)) return 0; + + return_code = sqlite3_bind_text(stmt, 2, guild_id.c_str(), -1, (sqlite3_destructor_type)-1); + if (!return_code_ok(return_code)) return 0; + + return_code = sqlite3_bind_text(stmt, 3, command_name.c_str(), -1, (sqlite3_destructor_type)-1); + if (!return_code_ok(return_code)) return 0; + + return_code = sqlite3_step(stmt); + bool success = return_code == SQLITE_DONE; + + sqlite3_finalize(stmt); + sqlite3_close(db); + + if (success) { + if (ret_value == 1) { + commands.push_back({ guild_id, command_name, script }); + } + if (ret_value == 2) { + // update command, don't add + auto check_lambda = [guild_id, command_name](const Command &c) { + return guild_id == c.guild_id && command_name == c.command_name; + }; + + auto it = std::find_if(commands.begin(), commands.end(), check_lambda); + if (it == commands.end()) { + return 0; + } else { + it->script = script; + } + } + + return ret_value; + } + + return 0; +} + +bool CommandHelper::command_in_db(std::string guild_id, std::string command_name) { + sqlite3 *db; int return_code; + return_code = sqlite3_open("bot/db/trivia.db", &db); + if (!return_code_ok(return_code)) return false; + + std::string sql = "SELECT EXISTS(SELECT 1 FROM CustomJS WHERE GuildID=?1 AND CommandName=?2);"; + + sqlite3_stmt *stmt; + return_code = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0); + if (!return_code_ok(return_code)) return false; + + return_code = sqlite3_bind_text(stmt, 1, guild_id.c_str(), -1, (sqlite3_destructor_type) -1); + if (!return_code_ok(return_code)) return false; + + return_code = sqlite3_bind_text(stmt, 2, command_name.c_str(), -1, (sqlite3_destructor_type) -1); + if (!return_code_ok(return_code)) return false; + + sqlite3_step(stmt); + + bool exists = sqlite3_column_int(stmt, 0) == 1; // returns 1 (true) if exists + + sqlite3_finalize(stmt); + sqlite3_close(db); + + return exists; +} + +bool CommandHelper::return_code_ok(int return_code) { + if (return_code != SQLITE_OK) { + std::cerr << "SQLite error. " << std::endl; + return false; + } + return true; +} \ No newline at end of file diff --git a/TriviaBot/bot/js/CommandHelper.hpp b/TriviaBot/bot/js/CommandHelper.hpp new file mode 100644 index 0000000..2f95fd9 --- /dev/null +++ b/TriviaBot/bot/js/CommandHelper.hpp @@ -0,0 +1,25 @@ +#ifndef BOT_JS_COMMANDHELPER +#define BOT_JS_COMMANDHELPER + +#include + +struct Command { + std::string guild_id; + std::string command_name; + std::string script; +}; + +class CommandHelper { +public: + CommandHelper(); + int insert_command(std::string guild_id, std::string command_name, std::string script); + bool get_command(std::string guild_id, std::string name, Command &command); + +private: + bool command_in_db(std::string guild_id, std::string command_name); + bool return_code_ok(int return_code); + + std::vector commands; +}; + +#endif \ No newline at end of file