commit
c9af4566c6
12
.gitignore
vendored
12
.gitignore
vendored
@ -9,3 +9,15 @@
|
|||||||
# Data files
|
# Data files
|
||||||
/TriviaBot/data_management/questions
|
/TriviaBot/data_management/questions
|
||||||
/TriviaBot/bot/db/trivia.db
|
/TriviaBot/bot/db/trivia.db
|
||||||
|
|
||||||
|
# Config file
|
||||||
|
config.json
|
||||||
|
|
||||||
|
# Compiled sqlite file
|
||||||
|
sqlite3.obj
|
||||||
|
|
||||||
|
# V8 lib
|
||||||
|
lib/v8
|
||||||
|
|
||||||
|
# Built files
|
||||||
|
build/
|
||||||
|
25
README.md
25
README.md
@ -32,22 +32,23 @@ LoadDB.cpp takes some time to execute.
|
|||||||
### Commands
|
### Commands
|
||||||
`` `trivia`` is the base command.
|
`` `trivia`` is the base command.
|
||||||
|
|
||||||
| Argument | Description |
|
| Argument | Description |
|
||||||
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| --- | --- |
|
||||||
| `questions` `interval` | Where `questions` and `interval` are integers. Makes the game last `questions` number of questions, optionally sets the time interval between hints to `interval` seconds. |
|
| `questions` `interval` | Where `questions` and `interval` are integers. Makes the game last `questions` number of questions, optionally sets the time interval between hints to `interval` seconds. |
|
||||||
| stop | Stops the trivia game currently in the channel the message is sent from, if there is one. |
|
| stop | Stops the trivia game currently in the channel the message is sent from, if there is one. |
|
||||||
| help | Prints a help list, similar to this table. |
|
| help | Prints a help list, similar to this table. |
|
||||||
|
|
||||||
|
|
||||||
### Compiling
|
### Compiling
|
||||||
#### Dependencies
|
#### Dependencies
|
||||||
| Name | Website | Notes |
|
| Name | Website | Notes |
|
||||||
| ------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
| --- | --- | --- |
|
||||||
| boost | [boost.org](http://www.boost.org/) | |
|
| boost | [boost.org](http://www.boost.org/) | |
|
||||||
| websocketpp | [zaphoyd/websocketpp](https://github.com/zaphoyd/websocketpp) | Included as submodule. |
|
| websocketpp | [zaphoyd/websocketpp](https://github.com/zaphoyd/websocketpp) | Included as submodule. |
|
||||||
| cURL | [curl.haxx.se](https://curl.haxx.se/) | |
|
| cURL | [curl.haxx.se](https://curl.haxx.se/) | |
|
||||||
| sqlite3 | [sqlite.org](https://www.sqlite.org/) | Included as submodule. Uses a [different repo](https://github.com/azadkuh/sqlite-amalgamation/). |
|
| sqlite3 | [sqlite.org](https://www.sqlite.org/) | Included as submodule. Uses a [different repo](https://github.com/azadkuh/sqlite-amalgamation/). |
|
||||||
| nlohmann/json | [nlohmann/json](https://github.com/nlohmann/json) | (Slightly modified) source file included in repo. |
|
| nlohmann/json | [nlohmann/json](https://github.com/nlohmann/json) | (Slightly modified) source file included in repo. |
|
||||||
|
| V8 | [Google V8](https://developers.google.com/v8/) | Debian/Ubuntu `libv8` packages are too outdated. |
|
||||||
|
|
||||||
#### Linux (debian)
|
#### Linux (debian)
|
||||||
c++14 support is required. gcc 5 and above recommended.
|
c++14 support is required. gcc 5 and above recommended.
|
||||||
|
@ -7,6 +7,8 @@ project(TriviaBot)
|
|||||||
|
|
||||||
file(GLOB_RECURSE sources bot/*.cpp bot/*.hpp ../lib/sqlite3/sqlite3.c)
|
file(GLOB_RECURSE sources bot/*.cpp bot/*.hpp ../lib/sqlite3/sqlite3.c)
|
||||||
|
|
||||||
|
link_directories(../lib/v8/lib)
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
## target definitions #########################################################
|
## target definitions #########################################################
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@ -29,8 +31,14 @@ target_link_libraries(TriviaBot PUBLIC
|
|||||||
${Boost_LIBRARIES}
|
${Boost_LIBRARIES}
|
||||||
${OPENSSL_LIBRARIES}
|
${OPENSSL_LIBRARIES}
|
||||||
${CURL_LIBRARIES}
|
${CURL_LIBRARIES}
|
||||||
pthread
|
v8
|
||||||
|
v8_libplatform
|
||||||
|
v8_libbase
|
||||||
|
icui18n
|
||||||
|
icuuc
|
||||||
|
rt
|
||||||
dl
|
dl
|
||||||
|
pthread
|
||||||
)
|
)
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
@ -39,7 +47,10 @@ include_directories(
|
|||||||
${CURL_INCLUDE_DIR}
|
${CURL_INCLUDE_DIR}
|
||||||
../lib/websocketpp
|
../lib/websocketpp
|
||||||
../lib/sqlite3
|
../lib/sqlite3
|
||||||
|
../lib/v8
|
||||||
)
|
)
|
||||||
|
|
||||||
# don't know if necessary, too scared to remove
|
# don't know if necessary, too scared to remove
|
||||||
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
|
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
|
||||||
|
|
||||||
|
set(CMAKE_BUILD_TYPE Release)
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
#include "http/HTTPHelper.hpp"
|
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
#include <thread>
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
#include "APIHelper.hpp"
|
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
|
||||||
|
|
||||||
APIHelper::APIHelper() : BASE_URL("https://discordapp.com/api"), CHANNELS_URL(BASE_URL + "/channels"),
|
|
||||||
JSON_CTYPE("application/json") {
|
|
||||||
http = new HTTPHelper();
|
|
||||||
}
|
|
||||||
|
|
||||||
void APIHelper::send_message(std::string channel_id, std::string message) {
|
|
||||||
const std::string url = CHANNELS_URL + "/" + channel_id + "/messages";
|
|
||||||
json data = {
|
|
||||||
{ "content", message }
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string response;
|
|
||||||
long response_code = -1;
|
|
||||||
response = http->post_request(url, JSON_CTYPE, data.dump(), &response_code);
|
|
||||||
|
|
||||||
int retries = 0;
|
|
||||||
while (response_code != 200 && retries < 2) {
|
|
||||||
std::this_thread::sleep_for(100ms);
|
|
||||||
// try 3 times. usually enough to prevent 502 bad gateway issues
|
|
||||||
response = http->post_request(url, JSON_CTYPE, data.dump(), &response_code);
|
|
||||||
retries++;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
#ifndef BOT_APIHELPER
|
|
||||||
#define BOT_APIHELPER
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "json/json.hpp"
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
class HTTPHelper;
|
|
||||||
|
|
||||||
class APIHelper {
|
|
||||||
public:
|
|
||||||
APIHelper();
|
|
||||||
|
|
||||||
void send_message(std::string channel_id, std::string message);
|
|
||||||
|
|
||||||
private:
|
|
||||||
const std::string BASE_URL;
|
|
||||||
const std::string CHANNELS_URL;
|
|
||||||
const std::string JSON_CTYPE;
|
|
||||||
|
|
||||||
HTTPHelper *http;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
60
TriviaBot/bot/BotConfig.cpp
Normal file
60
TriviaBot/bot/BotConfig.cpp
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#include "BotConfig.hpp"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
#include "json/json.hpp"
|
||||||
|
|
||||||
|
#include "Logger.hpp"
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
BotConfig::BotConfig() {
|
||||||
|
is_new_config = false;
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
|
std::ifstream config_file("config.json");
|
||||||
|
if(!config_file) {
|
||||||
|
config_file.close();
|
||||||
|
create_new_file();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << config_file.rdbuf();
|
||||||
|
config_file.close();
|
||||||
|
std::string config = ss.str();
|
||||||
|
load_from_json(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotConfig::load_from_json(std::string data) {
|
||||||
|
json parsed = json::parse(data);
|
||||||
|
|
||||||
|
token = parsed.value("bot_token", "");
|
||||||
|
owner_id = parsed.value("owner_id", "");
|
||||||
|
cert_location = parsed.value("api_cert_file", "bot/http/DiscordCA.crt");
|
||||||
|
|
||||||
|
createjs_roles = parsed["v8"].value("createjs_allowed_roles", std::unordered_set<std::string> { "Admin", "Coder" });
|
||||||
|
|
||||||
|
Logger::write("config.json file loaded", Logger::LogLevel::Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BotConfig::create_new_file() {
|
||||||
|
std::string config = json {
|
||||||
|
{ "bot_token", "" },
|
||||||
|
{ "owner_id", "" },
|
||||||
|
{ "api_cert_file", "bot/http/DiscordCA.crt" },
|
||||||
|
{ "v8", {
|
||||||
|
{ "createjs_allowed_roles", {
|
||||||
|
"Admin", "Coder", "Bot Commander"
|
||||||
|
} }
|
||||||
|
} }
|
||||||
|
}.dump(4);
|
||||||
|
|
||||||
|
std::ofstream config_file("config.json");
|
||||||
|
config_file << config;
|
||||||
|
config_file.close();
|
||||||
|
|
||||||
|
Logger::write("Created new config.json file", Logger::LogLevel::Info);
|
||||||
|
is_new_config = true;
|
||||||
|
}
|
23
TriviaBot/bot/BotConfig.hpp
Normal file
23
TriviaBot/bot/BotConfig.hpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#ifndef BOT_BOTCONFIG
|
||||||
|
#define BOT_BOTCONFIG
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
class BotConfig {
|
||||||
|
public:
|
||||||
|
BotConfig();
|
||||||
|
|
||||||
|
bool is_new_config;
|
||||||
|
|
||||||
|
std::string token;
|
||||||
|
std::string owner_id;
|
||||||
|
std::string cert_location;
|
||||||
|
std::unordered_set<std::string> createjs_roles;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void load_from_json(std::string data);
|
||||||
|
void create_new_file();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -3,73 +3,74 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include "GatewayHandler.hpp"
|
#include "Logger.hpp"
|
||||||
|
#include "BotConfig.hpp"
|
||||||
|
|
||||||
ClientConnection::ClientConnection() {
|
ClientConnection::ClientConnection(BotConfig &c) : config(c), gh(config) {
|
||||||
// Reset the log channels
|
// Reset the log channels
|
||||||
c.clear_access_channels(websocketpp::log::alevel::all);
|
cli.clear_access_channels(websocketpp::log::alevel::all);
|
||||||
|
|
||||||
// Only want application logging, logging from the initial connection stages or any error logging
|
// Only want application logging, logging from the initial connection stages or any error logging
|
||||||
c.set_access_channels(websocketpp::log::alevel::app | websocketpp::log::alevel::connect);
|
cli.set_access_channels(websocketpp::log::alevel::app | websocketpp::log::alevel::connect);
|
||||||
c.set_error_channels(websocketpp::log::elevel::all);
|
cli.set_error_channels(websocketpp::log::elevel::all);
|
||||||
|
|
||||||
// Initialize ASIO
|
// Initialize ASIO
|
||||||
c.init_asio();
|
cli.init_asio();
|
||||||
|
|
||||||
// Bind handlers
|
// Bind handlers
|
||||||
c.set_socket_init_handler(bind(
|
cli.set_socket_init_handler(bind(
|
||||||
&ClientConnection::on_socket_init,
|
&ClientConnection::on_socket_init,
|
||||||
this,
|
this,
|
||||||
websocketpp::lib::placeholders::_1
|
websocketpp::lib::placeholders::_1
|
||||||
));
|
));
|
||||||
c.set_tls_init_handler(bind<context_ptr>(
|
cli.set_tls_init_handler(bind<context_ptr>(
|
||||||
&ClientConnection::on_tls_init,
|
&ClientConnection::on_tls_init,
|
||||||
this,
|
this,
|
||||||
websocketpp::lib::placeholders::_1
|
websocketpp::lib::placeholders::_1
|
||||||
));
|
));
|
||||||
c.set_message_handler(bind(
|
cli.set_message_handler(bind(
|
||||||
&ClientConnection::on_message,
|
&ClientConnection::on_message,
|
||||||
this,
|
this,
|
||||||
websocketpp::lib::placeholders::_1,
|
websocketpp::lib::placeholders::_1,
|
||||||
websocketpp::lib::placeholders::_2
|
websocketpp::lib::placeholders::_2
|
||||||
));
|
));
|
||||||
c.set_open_handler(bind(
|
cli.set_open_handler(bind(
|
||||||
&ClientConnection::on_open,
|
&ClientConnection::on_open,
|
||||||
this,
|
this,
|
||||||
websocketpp::lib::placeholders::_1
|
websocketpp::lib::placeholders::_1
|
||||||
));
|
));
|
||||||
c.set_close_handler(bind(
|
cli.set_close_handler(bind(
|
||||||
&ClientConnection::on_close,
|
&ClientConnection::on_close,
|
||||||
this,
|
this,
|
||||||
websocketpp::lib::placeholders::_1
|
websocketpp::lib::placeholders::_1
|
||||||
));
|
));
|
||||||
c.set_fail_handler(bind(
|
cli.set_fail_handler(bind(
|
||||||
&ClientConnection::on_fail,
|
&ClientConnection::on_fail,
|
||||||
this,
|
this,
|
||||||
websocketpp::lib::placeholders::_1
|
websocketpp::lib::placeholders::_1
|
||||||
));
|
));
|
||||||
|
|
||||||
gh = std::make_unique<GatewayHandler>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a connection to the URI provided
|
// Open a connection to the URI provided
|
||||||
void ClientConnection::start(std::string uri) {
|
void ClientConnection::start(std::string uri) {
|
||||||
websocketpp::lib::error_code ec;
|
websocketpp::lib::error_code ec;
|
||||||
client::connection_ptr con = c.get_connection(uri, ec);
|
client::connection_ptr con = cli.get_connection(uri, ec);
|
||||||
|
|
||||||
if (ec) { // failed to create connection
|
if (ec) { // failed to create connection
|
||||||
c.get_alog().write(websocketpp::log::alevel::app, ec.message());
|
Logger::write("Failed to create connection: " + ec.message(), Logger::LogLevel::Severe);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the connection
|
// Open the connection
|
||||||
c.connect(con);
|
cli.connect(con);
|
||||||
c.run();
|
cli.run();
|
||||||
|
|
||||||
|
Logger::write("Finished running", Logger::LogLevel::Debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
void ClientConnection::on_socket_init(websocketpp::connection_hdl) {
|
void ClientConnection::on_socket_init(websocketpp::connection_hdl) {
|
||||||
c.get_alog().write(websocketpp::log::alevel::app, "Socket intialised.");
|
Logger::write("Socket initialised", Logger::LogLevel::Debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
context_ptr ClientConnection::on_tls_init(websocketpp::connection_hdl) {
|
context_ptr ClientConnection::on_tls_init(websocketpp::connection_hdl) {
|
||||||
@ -81,41 +82,43 @@ context_ptr ClientConnection::on_tls_init(websocketpp::connection_hdl) {
|
|||||||
boost::asio::ssl::context::no_sslv3 |
|
boost::asio::ssl::context::no_sslv3 |
|
||||||
boost::asio::ssl::context::single_dh_use);
|
boost::asio::ssl::context::single_dh_use);
|
||||||
}
|
}
|
||||||
catch (std::exception& e) {
|
catch (std::exception &e) {
|
||||||
std::cout << "Error in context pointer: " << e.what() << std::endl;
|
Logger::write("[tls] Error in context pointer: " + std::string(e.what()), Logger::LogLevel::Severe);
|
||||||
}
|
}
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientConnection::on_fail(websocketpp::connection_hdl hdl) {
|
void ClientConnection::on_fail(websocketpp::connection_hdl hdl) {
|
||||||
client::connection_ptr con = c.get_con_from_hdl(hdl);
|
client::connection_ptr con = cli.get_con_from_hdl(hdl);
|
||||||
|
|
||||||
// Print as much information as possible
|
// Print as much information as possible
|
||||||
c.get_elog().write(websocketpp::log::elevel::warn,
|
Logger::write("Fail handler: \n" +
|
||||||
"Fail handler: \n" +
|
|
||||||
std::to_string(con->get_state()) + "\n" +
|
std::to_string(con->get_state()) + "\n" +
|
||||||
std::to_string(con->get_local_close_code()) + "\n" +
|
std::to_string(con->get_local_close_code()) + "\n" +
|
||||||
con->get_local_close_reason() + "\n" +
|
con->get_local_close_reason() + "\n" +
|
||||||
std::to_string(con->get_remote_close_code()) + "\n" +
|
std::to_string(con->get_remote_close_code()) + "\n" +
|
||||||
con->get_remote_close_reason() + "\n" +
|
con->get_remote_close_reason() + "\n" +
|
||||||
std::to_string(con->get_ec().value()) + " - " + con->get_ec().message() + "\n");
|
std::to_string(con->get_ec().value()) + " - " + con->get_ec().message() + "\n",
|
||||||
|
Logger::LogLevel::Severe);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientConnection::on_open(websocketpp::connection_hdl hdl) {
|
void ClientConnection::on_open(websocketpp::connection_hdl hdl) {
|
||||||
|
Logger::write("Connection opened", Logger::LogLevel::Debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientConnection::on_message(websocketpp::connection_hdl hdl, message_ptr message) {
|
void ClientConnection::on_message(websocketpp::connection_hdl hdl, message_ptr message) {
|
||||||
if (message->get_opcode() != websocketpp::frame::opcode::text) {
|
if (message->get_opcode() != websocketpp::frame::opcode::text) {
|
||||||
// If the message is not text, just print as hex
|
// If the message is not text, just print as hex
|
||||||
std::cout << "<< " << websocketpp::utility::to_hex(message->get_payload()) << std::endl;
|
Logger::write("Non-text message received: " + websocketpp::utility::to_hex(message->get_payload()), Logger::LogLevel::Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Pass the message to the gateway handler
|
// Pass the message to the gateway handler
|
||||||
gh->handle_data(message->get_payload(), c, hdl);
|
gh.handle_data(message->get_payload(), cli, hdl);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientConnection::on_close(websocketpp::connection_hdl) {
|
void ClientConnection::on_close(websocketpp::connection_hdl) {
|
||||||
std::cout << "Closed." << std::endl;
|
Logger::write("Connection closed", Logger::LogLevel::Info);
|
||||||
|
cli.stop();
|
||||||
}
|
}
|
@ -5,6 +5,8 @@
|
|||||||
#include <websocketpp/config/asio_client.hpp>
|
#include <websocketpp/config/asio_client.hpp>
|
||||||
#include "json/json.hpp"
|
#include "json/json.hpp"
|
||||||
|
|
||||||
|
#include "GatewayHandler.hpp"
|
||||||
|
|
||||||
typedef websocketpp::client<websocketpp::config::asio_tls_client> client;
|
typedef websocketpp::client<websocketpp::config::asio_tls_client> client;
|
||||||
|
|
||||||
using websocketpp::lib::bind;
|
using websocketpp::lib::bind;
|
||||||
@ -14,15 +16,20 @@ typedef websocketpp::config::asio_tls_client::message_type::ptr message_ptr;
|
|||||||
typedef websocketpp::lib::shared_ptr<boost::asio::ssl::context> context_ptr;
|
typedef websocketpp::lib::shared_ptr<boost::asio::ssl::context> context_ptr;
|
||||||
typedef client::connection_ptr connection_ptr;
|
typedef client::connection_ptr connection_ptr;
|
||||||
|
|
||||||
#include "GatewayHandler.hpp"
|
class BotConfig;
|
||||||
|
|
||||||
class ClientConnection {
|
class ClientConnection {
|
||||||
public:
|
public:
|
||||||
ClientConnection();
|
ClientConnection(BotConfig &c);
|
||||||
|
|
||||||
// Open a connection to the URI provided
|
// Open a connection to the URI provided
|
||||||
void start(std::string uri);
|
void start(std::string uri);
|
||||||
|
|
||||||
|
private:
|
||||||
|
client cli;
|
||||||
|
BotConfig &config;
|
||||||
|
GatewayHandler gh;
|
||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
void on_socket_init(websocketpp::connection_hdl);
|
void on_socket_init(websocketpp::connection_hdl);
|
||||||
context_ptr on_tls_init(websocketpp::connection_hdl);
|
context_ptr on_tls_init(websocketpp::connection_hdl);
|
||||||
@ -30,10 +37,6 @@ public:
|
|||||||
void on_open(websocketpp::connection_hdl hdl);
|
void on_open(websocketpp::connection_hdl hdl);
|
||||||
void on_message(websocketpp::connection_hdl hdl, message_ptr message);
|
void on_message(websocketpp::connection_hdl hdl, message_ptr message);
|
||||||
void on_close(websocketpp::connection_hdl);
|
void on_close(websocketpp::connection_hdl);
|
||||||
|
|
||||||
private:
|
|
||||||
client c;
|
|
||||||
std::unique_ptr<GatewayHandler> gh;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
84
TriviaBot/bot/DiscordAPI.cpp
Normal file
84
TriviaBot/bot/DiscordAPI.cpp
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#include "DiscordAPI.hpp"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "http/HTTP.hpp"
|
||||||
|
#include "Logger.hpp"
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
namespace DiscordAPI {
|
||||||
|
const std::string base_url = "https://discordapp.com/api";
|
||||||
|
const std::string channels_url = base_url + "/channels";
|
||||||
|
const std::string gateway_url = base_url + "/gateway";
|
||||||
|
|
||||||
|
const std::string json_mime_type = "application/json";
|
||||||
|
|
||||||
|
void send_message(std::string channel_id, std::string message, std::string token, std::string ca_location) {
|
||||||
|
if (message == "") {
|
||||||
|
Logger::write("[API] [send_message] Tried to send empty message", Logger::LogLevel::Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.length() > 4000) {
|
||||||
|
Logger::write("[API] [send_message] Tried to send a message over 4000 characters", Logger::LogLevel::Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (message.length() > 2000) {
|
||||||
|
std::cout << message.length() << std::endl;
|
||||||
|
|
||||||
|
std::string first = message.substr(0, 2000);
|
||||||
|
std::string second = message.substr(2000);
|
||||||
|
send_message(channel_id, first, token, ca_location);
|
||||||
|
std::this_thread::sleep_for(50ms);
|
||||||
|
send_message(channel_id, second, token, ca_location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string url = channels_url + "/" + channel_id + "/messages";
|
||||||
|
json data = {
|
||||||
|
{ "content", message }
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string response;
|
||||||
|
long response_code = 0;
|
||||||
|
response = HTTP::post_request(url, json_mime_type, data.dump(), &response_code, token, ca_location);
|
||||||
|
|
||||||
|
int retries = 0;
|
||||||
|
while (response_code != 200 && retries < 2) {
|
||||||
|
Logger::write("[API] [send_message] Got non-200 response code, retrying", Logger::LogLevel::Warning);
|
||||||
|
std::this_thread::sleep_for(100ms);
|
||||||
|
// try 3 times. usually enough to prevent 502 bad gateway issues
|
||||||
|
response = HTTP::post_request(url, json_mime_type, data.dump(), &response_code, token, ca_location);
|
||||||
|
retries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response_code != 200) {
|
||||||
|
Logger::write("[API] [send_message] Giving up on sending message", Logger::LogLevel::Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json get_gateway(std::string ca_location) {
|
||||||
|
std::string response;
|
||||||
|
long response_code;
|
||||||
|
response = HTTP::get_request(gateway_url, &response_code, "", ca_location);
|
||||||
|
|
||||||
|
int retries = 0;
|
||||||
|
while (response_code != 200 && retries < 4) {
|
||||||
|
Logger::write("[API] [get_gateway] Got non-200 response code, retrying", Logger::LogLevel::Warning);
|
||||||
|
std::this_thread::sleep_for(100ms);
|
||||||
|
// try 3 times. usually enough to prevent 502 bad gateway issues
|
||||||
|
response = HTTP::get_request(gateway_url, &response_code, "", ca_location);
|
||||||
|
retries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response_code != 200) {
|
||||||
|
Logger::write("[API] [get_gateway] Giving up on getting gateway url", Logger::LogLevel::Warning);
|
||||||
|
return json {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return json::parse(response);
|
||||||
|
}
|
||||||
|
}
|
17
TriviaBot/bot/DiscordAPI.hpp
Normal file
17
TriviaBot/bot/DiscordAPI.hpp
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#ifndef BOT_APIHELPER
|
||||||
|
#define BOT_APIHELPER
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "json/json.hpp"
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
class BotConfig;
|
||||||
|
|
||||||
|
namespace DiscordAPI {
|
||||||
|
json get_gateway(std::string ca_location);
|
||||||
|
void send_message(std::string channel_id, std::string message, std::string token, std::string ca_location);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
|
|
||||||
#include "APIHelper.hpp"
|
#include "DiscordAPI.hpp"
|
||||||
#include "data_structures/User.hpp"
|
#include "Logger.hpp"
|
||||||
|
#include "data_structures/GuildMember.hpp"
|
||||||
|
#include "BotConfig.hpp"
|
||||||
|
|
||||||
extern std::string bot_token;
|
GatewayHandler::GatewayHandler(BotConfig &c) : config(c) {
|
||||||
|
|
||||||
GatewayHandler::GatewayHandler() {
|
|
||||||
last_seq = 0;
|
last_seq = 0;
|
||||||
|
|
||||||
ah = new APIHelper();
|
CommandHelper::init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GatewayHandler::handle_data(std::string data, client &c, websocketpp::connection_hdl &hdl) {
|
void GatewayHandler::handle_data(std::string data, client &c, websocketpp::connection_hdl &hdl) {
|
||||||
@ -26,14 +26,22 @@ void GatewayHandler::handle_data(std::string data, client &c, websocketpp::conne
|
|||||||
on_hello(decoded, c, hdl);
|
on_hello(decoded, c, hdl);
|
||||||
break;
|
break;
|
||||||
case 11:
|
case 11:
|
||||||
c.get_alog().write(websocketpp::log::alevel::app, "Heartbeat acknowledged.");
|
Logger::write("Heartbeat acknowledged", Logger::LogLevel::Debug);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GatewayHandler::heartbeat(client *c, websocketpp::connection_hdl hdl, int interval) {
|
void GatewayHandler::send_heartbeat(client *c, websocketpp::connection_hdl hdl, int interval) {
|
||||||
while (true) {
|
while (true) {
|
||||||
boost::this_thread::sleep(boost::posix_time::milliseconds(interval));
|
boost::this_thread::sleep(boost::posix_time::milliseconds(interval));
|
||||||
|
if (!c) {
|
||||||
|
Logger::write("[send_heartbeat] Client pointer is null", Logger::LogLevel::Severe);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (c->stopped()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
json heartbeat = {
|
json heartbeat = {
|
||||||
{ "op", 1 },
|
{ "op", 1 },
|
||||||
@ -42,18 +50,53 @@ void GatewayHandler::heartbeat(client *c, websocketpp::connection_hdl hdl, int i
|
|||||||
|
|
||||||
c->send(hdl, heartbeat.dump(), websocketpp::frame::opcode::text);
|
c->send(hdl, heartbeat.dump(), websocketpp::frame::opcode::text);
|
||||||
|
|
||||||
c->get_alog().write(websocketpp::log::alevel::app, "Sent heartbeat. (seq: " + std::to_string(last_seq) + ")");
|
Logger::write("Sent heartbeat (seq: " + std::to_string(last_seq) + ")", Logger::LogLevel::Debug);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GatewayHandler::send_identify(client &c, websocketpp::connection_hdl &hdl) {
|
||||||
|
json identify = {
|
||||||
|
{ "op", 2 },
|
||||||
|
{ "d", {
|
||||||
|
{ "token", config.token },
|
||||||
|
{ "properties",{
|
||||||
|
{ "$browser", "Microsoft Windows 10" },
|
||||||
|
{ "$device", "TriviaBot-0.0" },
|
||||||
|
{ "$referrer", "" },
|
||||||
|
{ "$referring_domain", "" }
|
||||||
|
} },
|
||||||
|
{ "compress", false },
|
||||||
|
{ "large_threshold", 250 },
|
||||||
|
{ "shard",{ 0, 1 } }
|
||||||
|
} }
|
||||||
|
};
|
||||||
|
|
||||||
|
c.send(hdl, identify.dump(), websocketpp::frame::opcode::text);
|
||||||
|
Logger::write("Sent identify payload", Logger::LogLevel::Debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GatewayHandler::send_request_guild_members(client &c, websocketpp::connection_hdl &hdl, std::string guild_id) {
|
||||||
|
json request_guild_members = {
|
||||||
|
{ "op", 8 },
|
||||||
|
{ "d", {
|
||||||
|
{ "guild_id", guild_id },
|
||||||
|
{ "query", "" },
|
||||||
|
{ "limit", 0 }
|
||||||
|
} }
|
||||||
|
};
|
||||||
|
|
||||||
|
c.send(hdl, request_guild_members.dump(), websocketpp::frame::opcode::text);
|
||||||
|
Logger::write("Requested guild members for " + guild_id, Logger::LogLevel::Debug);
|
||||||
|
}
|
||||||
|
|
||||||
void GatewayHandler::on_hello(json decoded, client &c, websocketpp::connection_hdl &hdl) {
|
void GatewayHandler::on_hello(json decoded, client &c, websocketpp::connection_hdl &hdl) {
|
||||||
heartbeat_interval = decoded["d"]["heartbeat_interval"];
|
heartbeat_interval = decoded["d"]["heartbeat_interval"];
|
||||||
|
|
||||||
c.get_alog().write(websocketpp::log::alevel::app, "Heartbeat interval: " + std::to_string(heartbeat_interval / 1000.0f) + " seconds");
|
Logger::write("Heartbeat interval: " + std::to_string(heartbeat_interval / 1000.0f) + " seconds", Logger::LogLevel::Debug);
|
||||||
|
|
||||||
heartbeat_thread = std::make_unique<boost::thread>(boost::bind(&GatewayHandler::heartbeat, this, &c, hdl, heartbeat_interval));
|
heartbeat_thread = std::make_unique<boost::thread>(boost::bind(&GatewayHandler::send_heartbeat, this, &c, hdl, heartbeat_interval));
|
||||||
|
|
||||||
identify(c, hdl);
|
send_identify(c, hdl);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GatewayHandler::on_dispatch(json decoded, client &c, websocketpp::connection_hdl &hdl) {
|
void GatewayHandler::on_dispatch(json decoded, client &c, websocketpp::connection_hdl &hdl) {
|
||||||
@ -62,125 +105,554 @@ void GatewayHandler::on_dispatch(json decoded, client &c, websocketpp::connectio
|
|||||||
json data = decoded["d"];
|
json data = decoded["d"];
|
||||||
|
|
||||||
if (event_name == "READY") {
|
if (event_name == "READY") {
|
||||||
user_object.load_from_json(data["user"]);
|
on_event_ready(data);
|
||||||
|
|
||||||
c.get_alog().write(websocketpp::log::alevel::app, "Sign-on confirmed. (@" + user_object.username + "#" + user_object.discriminator + ")");
|
|
||||||
}
|
}
|
||||||
else if (event_name == "GUILD_CREATE") {
|
else if (event_name == "GUILD_CREATE") {
|
||||||
std::string guild_id = data["id"];
|
on_event_guild_create(data);
|
||||||
try {
|
}
|
||||||
guilds[guild_id] = std::make_unique<DiscordObjects::Guild>(data);
|
else if (event_name == "GUILD_UPDATE") {
|
||||||
}
|
on_event_guild_update(data);
|
||||||
catch (std::domain_error err) {
|
}
|
||||||
// this doesn't even work
|
else if (event_name == "GUILD_DELETE") {
|
||||||
c.get_alog().write(websocketpp::log::elevel::rerror, "Domain error");
|
on_event_guild_delete(data);
|
||||||
}
|
}
|
||||||
|
else if (event_name == "GUILD_MEMBER_ADD") {
|
||||||
|
on_event_guild_member_add(data);
|
||||||
c.get_alog().write(websocketpp::log::alevel::app, "Loaded guild: " + guilds[guild_id]->name);
|
}
|
||||||
|
else if (event_name == "GUILD_MEMBER_UPDATE") {
|
||||||
for (json channel : data["channels"]) {
|
on_event_guild_member_update(data);
|
||||||
std::string channel_id = channel["id"];
|
}
|
||||||
channel["guild_id"] = guild_id;
|
else if (event_name == "GUILD_MEMBER_REMOVE") {
|
||||||
// create channel obj, add to overall channel list
|
on_event_guild_member_remove(data);
|
||||||
channels[channel_id] = std::make_shared<DiscordObjects::Channel>(channel);
|
}
|
||||||
// add ptr to said channel list to guild's channel list
|
else if (event_name == "GUILD_ROLE_CREATE") {
|
||||||
guilds[guild_id]->channels.push_back(std::shared_ptr<DiscordObjects::Channel>(channels[channel_id]));
|
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") {
|
else if (event_name == "MESSAGE_CREATE") {
|
||||||
std::string message = data["content"];
|
on_event_message_create(data, c, hdl);
|
||||||
auto channel = channels[data["channel_id"]];
|
}
|
||||||
|
else if (event_name == "PRESENCE_UPDATE") {
|
||||||
DiscordObjects::User sender(data["author"]);
|
on_event_presence_update(data);
|
||||||
|
|
||||||
std::vector<std::string> words;
|
|
||||||
boost::split(words, message, boost::is_any_of(" "));
|
|
||||||
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[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<TriviaGame>(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) {
|
|
||||||
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. <http://github.com/jackb-p/TriviaDiscord>");
|
|
||||||
}
|
|
||||||
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) {
|
void GatewayHandler::on_event_ready(json data) {
|
||||||
json identify = {
|
user_object.load_from_json(data["user"]);
|
||||||
{ "op", 2 },
|
|
||||||
{ "d", {
|
|
||||||
{ "token", bot_token },
|
|
||||||
{ "properties", {
|
|
||||||
{ "$browser", "Microsoft Windows 10" },
|
|
||||||
{ "$device", "TriviaBot-0.0" },
|
|
||||||
{ "$referrer", "" },
|
|
||||||
{ "$referring_domain", "" }
|
|
||||||
} },
|
|
||||||
{ "compress", false },
|
|
||||||
{ "large_threshold", 250 },
|
|
||||||
{ "shard", { 0, 1 } }
|
|
||||||
} }
|
|
||||||
};
|
|
||||||
|
|
||||||
c.send(hdl, identify.dump(), websocketpp::frame::opcode::text);
|
Logger::write("Sign-on confirmed. (@" + user_object.username + "#" + user_object.discriminator + ")", Logger::LogLevel::Info);
|
||||||
c.get_alog().write(websocketpp::log::alevel::app, "Sent identify payload.");
|
}
|
||||||
|
|
||||||
|
void GatewayHandler::on_event_presence_update(json data) {
|
||||||
|
std::string user_id = data["user"]["id"];
|
||||||
|
|
||||||
|
auto it = users.find(user_id);
|
||||||
|
if (it != users.end()) {
|
||||||
|
it->second.status = data.value("status", "offline");
|
||||||
|
if (data["game"] == nullptr) {
|
||||||
|
it->second.game = "null";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
it->second.game = data["game"].value("name", "null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Logger::write("Tried to add presence for user " + user_id + " who doesn't exist", Logger::LogLevel::Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GatewayHandler::on_event_guild_create(json data) {
|
||||||
|
guilds[data["id"]] = DiscordObjects::Guild(data);
|
||||||
|
DiscordObjects::Guild &guild = guilds[data["id"]];
|
||||||
|
|
||||||
|
Logger::write("Received info for 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, presences_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 = new DiscordObjects::GuildMember(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);
|
||||||
|
|
||||||
|
members_added++;
|
||||||
|
}
|
||||||
|
for (json presence : data["presences"]) {
|
||||||
|
std::string user_id = presence["user"]["id"];
|
||||||
|
|
||||||
|
auto it = users.find(user_id);
|
||||||
|
if (it != users.end()) {
|
||||||
|
it->second.status = presence.value("status", "offline");
|
||||||
|
if (presence["game"] == nullptr) {
|
||||||
|
it->second.game = "null";
|
||||||
|
} else {
|
||||||
|
it->second.game = presence["game"].value("name", "null");
|
||||||
|
}
|
||||||
|
|
||||||
|
presences_added++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Logger::write("Tried to add presence for user " + user_id + " who doesn't exist", Logger::LogLevel::Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v8_instances.count(guild.id) == 0) {
|
||||||
|
v8_instances[guild.id] = std::make_unique<V8Instance>(config, guild.id, &guilds, &channels, &users, &roles);
|
||||||
|
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 (with " + std::to_string(presences_added) + " presences) 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 = new DiscordObjects::GuildMember(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::string user_id = data["user"]["id"];
|
||||||
|
DiscordObjects::Guild &guild = guilds[data["guild_id"]];
|
||||||
|
|
||||||
|
auto it = std::find_if(guild.members.begin(), guild.members.end(), [user_id](DiscordObjects::GuildMember *member) {
|
||||||
|
return user_id == member->user->id;
|
||||||
|
});
|
||||||
|
if (it != guild.members.end()) {
|
||||||
|
bool nick_changed = false;
|
||||||
|
int roles_change = 0;
|
||||||
|
|
||||||
|
DiscordObjects::GuildMember *member = (*it);
|
||||||
|
|
||||||
|
std::string nick = data.value("nick", "null");
|
||||||
|
if (member->nick != nick) {
|
||||||
|
member->nick = nick;
|
||||||
|
nick_changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
roles_change = member->roles.size();
|
||||||
|
member->roles.clear(); // reset and re-fill, changing the differences is probably more expensive anyway.
|
||||||
|
for (std::string role_id : data["roles"]) {
|
||||||
|
member->roles.push_back(&roles[role_id]);
|
||||||
|
}
|
||||||
|
roles_change = member->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.end(), [user_id](DiscordObjects::GuildMember *member) {
|
||||||
|
return user_id == member->user->id;
|
||||||
|
});
|
||||||
|
if (it != guild.members.end()) {
|
||||||
|
delete (*it);
|
||||||
|
guild.members.erase(it);
|
||||||
|
|
||||||
|
users[user_id].guilds.erase(std::remove(users[user_id].guilds.begin(), users[user_id].guilds.end(), guild.id));
|
||||||
|
|
||||||
|
if (users[user_id].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 + " who 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::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::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::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, client &c, websocketpp::connection_hdl &hdl) {
|
||||||
|
std::string message = data["content"];
|
||||||
|
|
||||||
|
DiscordObjects::Channel &channel = channels[data["channel_id"]];
|
||||||
|
DiscordObjects::Guild &guild = guilds[channel.guild_id];
|
||||||
|
DiscordObjects::User &sender = users[data["author"]["id"]];
|
||||||
|
|
||||||
|
if (sender.bot) return;
|
||||||
|
|
||||||
|
std::vector<std::string> words;
|
||||||
|
boost::split(words, message, boost::is_any_of(" "));
|
||||||
|
CommandHelper::Command custom_command;
|
||||||
|
if (words[0] == "`trivia" || words[0] == "`t") {
|
||||||
|
int questions = 10;
|
||||||
|
int delay = 8;
|
||||||
|
|
||||||
|
if (words.size() > 3) {
|
||||||
|
DiscordAPI::send_message(channel.id, ":exclamation: Invalid arguments!", config.token, config.cert_location);
|
||||||
|
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";
|
||||||
|
|
||||||
|
DiscordAPI::send_message(channel.id, help, config.token, config.cert_location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (words[1] == "stop" || words[1] == "s") {
|
||||||
|
if (games.find(channel.id) != games.end()) {
|
||||||
|
delete_game(channel.id);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DiscordAPI::send_message(channel.id, ":warning: Couldn't find an ongoing trivia game for this channel.", config.token, config.cert_location);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
questions = std::stoi(words[1]);
|
||||||
|
if (words.size() == 3) {
|
||||||
|
delay = std::stoi(words[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (std::invalid_argument e) {
|
||||||
|
DiscordAPI::send_message(channel.id, ":exclamation: Invalid arguments!", config.token, config.cert_location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
games[channel.id] = std::make_unique<TriviaGame>(config, this, channel.id, questions, delay);
|
||||||
|
games[channel.id]->start();
|
||||||
|
}
|
||||||
|
else if (words[0] == "`guilds") {
|
||||||
|
std::string m = "**Guild List:**\n";
|
||||||
|
for (auto &gu : guilds) {
|
||||||
|
m += ":small_orange_diamond: " + gu.second.name + " (" + gu.second.id + ") Channels: " + std::to_string(gu.second.channels.size()) + "\n";
|
||||||
|
}
|
||||||
|
DiscordAPI::send_message(channel.id, m, config.token, config.cert_location);
|
||||||
|
}
|
||||||
|
else if (words[0] == "`info") {
|
||||||
|
DiscordAPI::send_message(channel.id, ":information_source: **trivia-bot** by Jack. <http://github.com/jackb-p/TriviaDiscord>", config.token, config.cert_location);
|
||||||
|
}
|
||||||
|
else if (words[0] == "~js" && words.size() > 1) {
|
||||||
|
DiscordObjects::GuildMember *member = *std::find_if(guild.members.begin(), guild.members.end(), [sender](DiscordObjects::GuildMember *m) {
|
||||||
|
return sender.id == m->user->id;
|
||||||
|
});
|
||||||
|
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, member);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (words[0] == "~createjs" && words.size() > 1) {
|
||||||
|
auto &member = *std::find_if(guild.members.begin(), guild.members.end(), [sender](DiscordObjects::GuildMember *m) { return sender.id == m->user->id; });
|
||||||
|
BotConfig &conf = config;
|
||||||
|
bool disallowed = std::find_if(member->roles.begin(), member->roles.end(), [conf](DiscordObjects::Role *r) -> bool {
|
||||||
|
return conf.createjs_roles.count(r->name);
|
||||||
|
}) == member->roles.end(); // checks if the user has the required roles
|
||||||
|
|
||||||
|
if (disallowed) {
|
||||||
|
DiscordAPI::send_message(channel.id, ":warning: You do not have permission to use this command.", config.token, config.cert_location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = CommandHelper::insert_command(channel.guild_id, command_name, script);
|
||||||
|
switch (result) {
|
||||||
|
case 0:
|
||||||
|
DiscordAPI::send_message(channel.id, ":warning: Error!", config.token, config.cert_location); break;
|
||||||
|
case 1:
|
||||||
|
DiscordAPI::send_message(channel.id, ":new: Command `" + command_name + "` successfully created.", config.token, config.cert_location); break;
|
||||||
|
case 2:
|
||||||
|
DiscordAPI::send_message(channel.id, ":arrow_heading_up: Command `" + command_name + "` successfully updated.", config.token, config.cert_location); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (words[0] == "`shutdown" && sender.id == "82232146579689472") { // it me
|
||||||
|
DiscordAPI::send_message(channel.id, ":zzz: Goodbye!", config.token, config.cert_location);
|
||||||
|
for (auto &game : games) {
|
||||||
|
delete_game(game.first);
|
||||||
|
}
|
||||||
|
v8_instances.clear();
|
||||||
|
c.close(hdl, websocketpp::close::status::going_away, "");
|
||||||
|
}
|
||||||
|
else if (words[0] == "`debug") {
|
||||||
|
if (words[1] == "channel" && words.size() == 3) {
|
||||||
|
auto it = channels.find(words[2]);
|
||||||
|
if (it == channels.end()) {
|
||||||
|
DiscordAPI::send_message(channel.id, ":question: Unrecognised channel.", config.token, config.cert_location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordAPI::send_message(channel.id, it->second.to_debug_string(), config.token, config.cert_location);
|
||||||
|
}
|
||||||
|
else if (words[1] == "guild" && words.size() == 3) {
|
||||||
|
auto it = guilds.find(words[2]);
|
||||||
|
if (it == guilds.end()) {
|
||||||
|
DiscordAPI::send_message(channel.id, ":question: Unrecognised guild.", config.token, config.cert_location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordAPI::send_message(channel.id, it->second.to_debug_string(), config.token, config.cert_location);
|
||||||
|
}
|
||||||
|
else if (words[1] == "member" && words.size() == 4) {
|
||||||
|
auto it = guilds.find(words[2]);
|
||||||
|
if (it == guilds.end()) {
|
||||||
|
DiscordAPI::send_message(channel.id, ":question: Unrecognised guild.", config.token, config.cert_location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string user_id = words[3];
|
||||||
|
auto it2 = std::find_if(it->second.members.begin(), it->second.members.end(), [user_id](DiscordObjects::GuildMember *member) {
|
||||||
|
return user_id == member->user->id;
|
||||||
|
});
|
||||||
|
if (it2 == it->second.members.end()) {
|
||||||
|
DiscordAPI::send_message(channel.id, ":question: Unrecognised user.", config.token, config.cert_location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordAPI::send_message(channel.id, (*it2)->to_debug_string(), config.token, config.cert_location);
|
||||||
|
}
|
||||||
|
else if (words[1] == "role" && words.size() == 3) {
|
||||||
|
auto it = roles.find(words[2]);
|
||||||
|
if (it == roles.end()) {
|
||||||
|
DiscordAPI::send_message(channel.id, ":question: Unrecognised role.", config.token, config.cert_location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordAPI::send_message(channel.id, it->second.to_debug_string(), config.token, config.cert_location);
|
||||||
|
}
|
||||||
|
else if (words[1] == "role" && words.size() == 4) {
|
||||||
|
std::string role_name = words[3];
|
||||||
|
|
||||||
|
auto it = guilds.find(words[2]);
|
||||||
|
if (it == guilds.end()) {
|
||||||
|
DiscordAPI::send_message(channel.id, ":question: Unrecognised guild.", config.token, config.cert_location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it2 = std::find_if(it->second.roles.begin(), it->second.roles.end(), [role_name](DiscordObjects::Role *r) {
|
||||||
|
return role_name == r->name;
|
||||||
|
});
|
||||||
|
if (it2 == it->second.roles.end()) {
|
||||||
|
DiscordAPI::send_message(channel.id, ":question: Unrecognised role.", config.token, config.cert_location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordAPI::send_message(channel.id, (*it2)->to_debug_string(), config.token, config.cert_location);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DiscordAPI::send_message(channel.id, ":question: Unknown parameters.", config.token, config.cert_location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (CommandHelper::get_command(channel.guild_id, words[0], custom_command)) {
|
||||||
|
std::string args = "";
|
||||||
|
if (message.length() > (words[0].length() + 1)) {
|
||||||
|
args = message.substr(words[0].length() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (custom_command.script.length() == 0) {
|
||||||
|
DiscordAPI::send_message(channel.id, ":warning: Script has 0 length.", config.token, config.cert_location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = v8_instances.find(channel.guild_id);
|
||||||
|
if (it == v8_instances.end()) {
|
||||||
|
DiscordAPI::send_message(channel.id, ":warning: No V8 instance exists for this server - it's our fault not yours!", config.token, config.cert_location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordObjects::GuildMember *member = *std::find_if(guild.members.begin(), guild.members.end(), [sender](DiscordObjects::GuildMember *m) {
|
||||||
|
return sender.id == m->user->id;
|
||||||
|
});
|
||||||
|
it->second->exec_js(custom_command.script, &channel, member, args);
|
||||||
|
}
|
||||||
|
else if (games.find(channel.id) != games.end()) { // message received in channel with ongoing trivia game
|
||||||
|
games[channel.id]->handle_answer(message, sender);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GatewayHandler::delete_game(std::string channel_id) {
|
void GatewayHandler::delete_game(std::string channel_id) {
|
||||||
@ -191,6 +663,6 @@ void GatewayHandler::delete_game(std::string channel_id) {
|
|||||||
// remove from map
|
// remove from map
|
||||||
games.erase(it);
|
games.erase(it);
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "Tried to delete a game that didn't exist.";
|
Logger::write("Tried to delete a game that didn't exist (channel_id: " + channel_id + ")", Logger::LogLevel::Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,9 +9,12 @@
|
|||||||
#include "json/json.hpp"
|
#include "json/json.hpp"
|
||||||
|
|
||||||
#include "TriviaGame.hpp"
|
#include "TriviaGame.hpp"
|
||||||
|
#include "js/CommandHelper.hpp"
|
||||||
|
#include "js/V8Instance.hpp"
|
||||||
#include "data_structures/User.hpp"
|
#include "data_structures/User.hpp"
|
||||||
#include "data_structures/Guild.hpp"
|
#include "data_structures/Guild.hpp"
|
||||||
#include "data_structures/Channel.hpp"
|
#include "data_structures/Channel.hpp"
|
||||||
|
#include "data_structures/Role.hpp"
|
||||||
|
|
||||||
typedef websocketpp::client<websocketpp::config::asio_tls_client> client;
|
typedef websocketpp::client<websocketpp::config::asio_tls_client> client;
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
@ -34,44 +37,71 @@ using json = nlohmann::json;
|
|||||||
*****************************************************************************************************************************/
|
*****************************************************************************************************************************/
|
||||||
|
|
||||||
class TriviaGame;
|
class TriviaGame;
|
||||||
class APIHelper;
|
class BotConfig;
|
||||||
|
|
||||||
class GatewayHandler {
|
class GatewayHandler {
|
||||||
public:
|
public:
|
||||||
GatewayHandler();
|
GatewayHandler(BotConfig &c);
|
||||||
|
|
||||||
void handle_data(std::string data, client &c, websocketpp::connection_hdl &hdl);
|
void handle_data(std::string data, client &c, websocketpp::connection_hdl &hdl);
|
||||||
|
|
||||||
void heartbeat(client *c, websocketpp::connection_hdl hdl, int interval);
|
|
||||||
|
|
||||||
void on_hello(json decoded, client &c, websocketpp::connection_hdl &hdl);
|
|
||||||
|
|
||||||
void on_dispatch(json decoded, client &c, websocketpp::connection_hdl &hdl);
|
|
||||||
|
|
||||||
void identify(client &c, websocketpp::connection_hdl &hdl);
|
|
||||||
|
|
||||||
void delete_game(std::string channel_id);
|
void delete_game(std::string channel_id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
BotConfig &config;
|
||||||
|
|
||||||
int last_seq;
|
int last_seq;
|
||||||
int heartbeat_interval;
|
int heartbeat_interval;
|
||||||
|
|
||||||
|
/* payload dispatchers */
|
||||||
|
void send_heartbeat(client *c, websocketpp::connection_hdl hdl, int interval);
|
||||||
|
void send_identify(client &c, websocketpp::connection_hdl &hdl);
|
||||||
|
void send_request_guild_members(client &c, websocketpp::connection_hdl &hdl, std::string guild_id); // not sure if required atm
|
||||||
|
|
||||||
|
/* payload handlers */
|
||||||
|
void on_hello(json decoded, client &c, websocketpp::connection_hdl &hdl);
|
||||||
|
void on_dispatch(json decoded, client &c, websocketpp::connection_hdl &hdl);
|
||||||
|
|
||||||
|
/* misc events */
|
||||||
|
void on_event_ready(json data); // https://discordapp.com/developers/docs/topics/gateway#ready
|
||||||
|
void on_event_presence_update(json data); // https://discordapp.com/developers/docs/topics/gateway#presence-update
|
||||||
|
|
||||||
|
/* 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, client &c, websocketpp::connection_hdl &hdl); // https://discordapp.com/developers/docs/topics/gateway#message-create
|
||||||
|
|
||||||
const int protocol_version = 5;
|
const int protocol_version = 5;
|
||||||
|
|
||||||
// bot's user obj
|
// bot's user obj
|
||||||
DiscordObjects::User user_object;
|
DiscordObjects::User user_object;
|
||||||
|
|
||||||
// <id, ptr to data>
|
/* <id, obj> */
|
||||||
std::map<std::string, std::unique_ptr<DiscordObjects::Guild>> guilds;
|
std::map<std::string, DiscordObjects::Guild> guilds;
|
||||||
// channels pointers are shared pointers, held here but also in guild objects
|
std::map<std::string, DiscordObjects::Channel> channels;
|
||||||
std::map<std::string, std::shared_ptr<DiscordObjects::Channel>> channels;
|
std::map<std::string, DiscordObjects::User> users;
|
||||||
|
std::map<std::string, DiscordObjects::Role> roles;
|
||||||
|
|
||||||
// <channel_id, game obj>
|
// <channel_id, game obj>
|
||||||
std::map<std::string, std::unique_ptr<TriviaGame>> games;
|
std::map<std::string, std::unique_ptr<TriviaGame>> games;
|
||||||
|
// <guild_id, v8 instance>
|
||||||
|
std::map<std::string, std::unique_ptr<V8Instance>> v8_instances;
|
||||||
|
|
||||||
std::unique_ptr<boost::thread> heartbeat_thread;
|
std::unique_ptr<boost::thread> heartbeat_thread;
|
||||||
|
|
||||||
APIHelper *ah;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
47
TriviaBot/bot/Logger.cpp
Normal file
47
TriviaBot/bot/Logger.cpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#include "Logger.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
namespace Logger {
|
||||||
|
std::ostream &operator<<(std::ostream &out, const LogLevel log_level) {
|
||||||
|
switch (log_level) {
|
||||||
|
case LogLevel::Debug:
|
||||||
|
return out << "debug";
|
||||||
|
case LogLevel::Info:
|
||||||
|
return out << "info";
|
||||||
|
case LogLevel::Warning:
|
||||||
|
return out << "warning";
|
||||||
|
case LogLevel::Severe:
|
||||||
|
return out << "severe";
|
||||||
|
}
|
||||||
|
return out << "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream &get_ostream(LogLevel log_level) {
|
||||||
|
switch (log_level) {
|
||||||
|
case LogLevel::Debug:
|
||||||
|
case LogLevel::Info:
|
||||||
|
return std::clog;
|
||||||
|
case LogLevel::Severe:
|
||||||
|
case LogLevel::Warning:
|
||||||
|
return std::cerr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::cerr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(std::string text, LogLevel log_level) {
|
||||||
|
time_t rawtime;
|
||||||
|
struct tm *timeinfo;
|
||||||
|
char buffer[80];
|
||||||
|
|
||||||
|
time(&rawtime);
|
||||||
|
timeinfo = localtime(&rawtime);
|
||||||
|
|
||||||
|
strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", timeinfo);
|
||||||
|
std::string time_str(buffer);
|
||||||
|
|
||||||
|
get_ostream(log_level) << "[" << time_str << "] [" << log_level << "] " << text << std::endl;
|
||||||
|
}
|
||||||
|
}
|
14
TriviaBot/bot/Logger.hpp
Normal file
14
TriviaBot/bot/Logger.hpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef BOT_LOGGER
|
||||||
|
#define BOT_LOGGER
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Logger {
|
||||||
|
enum class LogLevel {
|
||||||
|
Debug, Info, Warning, Severe
|
||||||
|
};
|
||||||
|
|
||||||
|
void write(std::string text, LogLevel log_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -1,40 +1,66 @@
|
|||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
#include <include/libplatform/libplatform.h>
|
||||||
|
#include <include/v8.h>
|
||||||
|
|
||||||
#include "ClientConnection.hpp"
|
#include "ClientConnection.hpp"
|
||||||
|
#include "Logger.hpp"
|
||||||
std::string bot_token;
|
#include "DiscordAPI.hpp"
|
||||||
|
#include "BotConfig.hpp"
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
|
BotConfig config;
|
||||||
|
if (config.is_new_config) {
|
||||||
|
Logger::write("Since the config.json file is newly generated, the program will exit now to allow you to edit it.", Logger::LogLevel::Info);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||||
|
|
||||||
if (argc == 2) {
|
v8::V8::InitializeICUDefaultLocation(argv[0]);
|
||||||
bot_token = argv[1];
|
v8::V8::InitializeExternalStartupData(argv[0]);
|
||||||
}
|
v8::Platform* platform = v8::platform::CreateDefaultPlatform();
|
||||||
else {
|
v8::V8::InitializePlatform(platform);
|
||||||
std::cout << "Please enter your bot token: " << std::endl;
|
v8::V8::Initialize();
|
||||||
std::cin >> bot_token;
|
|
||||||
|
Logger::write("Initialised V8 and curl", Logger::LogLevel::Debug);
|
||||||
|
|
||||||
|
std::string args = "/?v=5&encoding=json";
|
||||||
|
std::string url = DiscordAPI::get_gateway(config.cert_location).value("url", "wss://gateway.discord.gg");
|
||||||
|
|
||||||
|
bool retry = true;
|
||||||
|
int exit_code = 0;
|
||||||
|
while (retry) {
|
||||||
|
retry = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
ClientConnection conn(config);
|
||||||
|
conn.start(url + args);
|
||||||
|
}
|
||||||
|
catch (const std::exception &e) {
|
||||||
|
Logger::write("std exception: " + std::string(e.what()), Logger::LogLevel::Severe);
|
||||||
|
exit_code = 1;
|
||||||
|
}
|
||||||
|
catch (websocketpp::lib::error_code e) {
|
||||||
|
Logger::write("websocketpp exception: " + e.message(), Logger::LogLevel::Severe);
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||||
|
retry = true; // should just be an occasional connection issue
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
Logger::write("other exception.", Logger::LogLevel::Severe);
|
||||||
|
exit_code = 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: get this using API
|
v8::V8::Dispose();
|
||||||
std::string uri = "wss://gateway.discord.gg/?v=5&encoding=json";
|
v8::V8::ShutdownPlatform();
|
||||||
|
delete platform;
|
||||||
try {
|
|
||||||
ClientConnection conn;
|
|
||||||
conn.start(uri);
|
|
||||||
}
|
|
||||||
catch (const std::exception & e) {
|
|
||||||
std::cerr << e.what() << std::endl;
|
|
||||||
}
|
|
||||||
catch (websocketpp::lib::error_code e) {
|
|
||||||
std::cerr << e.message() << std::endl;
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
std::cerr << "other exception" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::getchar();
|
|
||||||
|
|
||||||
curl_global_cleanup();
|
curl_global_cleanup();
|
||||||
|
|
||||||
return 0;
|
Logger::write("Cleaned up", Logger::LogLevel::Info);
|
||||||
|
|
||||||
|
return exit_code;
|
||||||
}
|
}
|
64
TriviaBot/bot/TriviaBot.cpp.save
Normal file
64
TriviaBot/bot/TriviaBot.cpp.save
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#include <curl/curl.h>
|
||||||
|
#include <include/libplatform/libplatform.>
|
||||||
|
#include <include/v8.h>
|
||||||
|
|
||||||
|
#include "ClientConnection.hpp"
|
||||||
|
#include "Logger.hpp"
|
||||||
|
#include "DiscordAPI.hpp"
|
||||||
|
|
||||||
|
std::string bot_token;
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||||
|
|
||||||
|
v8::V8::InitializeICUDefaultLocation(argv[0]);
|
||||||
|
v8::V8::InitializeExternalStartupData(argv[0]);
|
||||||
|
v8::Platform* platform = v8::platform::CreateDefaultPlatform();
|
||||||
|
v8::V8::InitializePlatform(platform);
|
||||||
|
v8::V8::Initialize();
|
||||||
|
|
||||||
|
Logger::write("Initialised V8 and curl", Logger::LogLevel::Debug);
|
||||||
|
|
||||||
|
if (argc == 2) {
|
||||||
|
bot_token = argv[1];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "Please enter your bot token: " << std::endl;
|
||||||
|
std::cin >> bot_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string args = "/?v=5&encoding=json";
|
||||||
|
std::string url = DiscordAPI::get_gateway().value("url", "wss://gateway.discord.gg");
|
||||||
|
|
||||||
|
bool retry = true;
|
||||||
|
while (retry) {
|
||||||
|
try {
|
||||||
|
ClientConnection conn;
|
||||||
|
conn.start(url + args);
|
||||||
|
}
|
||||||
|
catch (const std::exception &e) {
|
||||||
|
Logger::write("std exception: " + std::string(e.what()), Logger::LogLevel::Severe);
|
||||||
|
retry = false;
|
||||||
|
}
|
||||||
|
catch (websocketpp::lib::error_code e) {
|
||||||
|
Logger::write("websocketpp exception: " + e.message(), Logger::LogLevel::Severe);
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
Logger::write("other exception.", Logger::LogLevel::Severe);
|
||||||
|
retry = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v8::V8::Dispose();
|
||||||
|
v8::V8::ShutdownPlatform();
|
||||||
|
delete platform;
|
||||||
|
|
||||||
|
curl_global_cleanup();
|
||||||
|
|
||||||
|
Logger::write("Cleaned up", Logger::LogLevel::Info);
|
||||||
|
|
||||||
|
std::cout << "Press enter to exit" << std::endl;
|
||||||
|
std::getchar();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -10,12 +10,13 @@
|
|||||||
#include <boost/regex.hpp>
|
#include <boost/regex.hpp>
|
||||||
|
|
||||||
#include "GatewayHandler.hpp"
|
#include "GatewayHandler.hpp"
|
||||||
#include "APIHelper.hpp"
|
#include "DiscordAPI.hpp"
|
||||||
#include "data_structures/User.hpp"
|
#include "data_structures/User.hpp"
|
||||||
|
#include "Logger.hpp"
|
||||||
|
#include "BotConfig.hpp"
|
||||||
|
|
||||||
TriviaGame::TriviaGame(GatewayHandler *gh, APIHelper *ah, std::string channel_id, int total_questions, int delay) : interval(delay) {
|
TriviaGame::TriviaGame(BotConfig &c, GatewayHandler *gh, std::string channel_id, int total_questions, int delay) : config(c), interval(delay) {
|
||||||
this->gh = gh;
|
this->gh = gh;
|
||||||
this->ah = ah;
|
|
||||||
this->channel_id = channel_id;
|
this->channel_id = channel_id;
|
||||||
|
|
||||||
this->total_questions = total_questions;
|
this->total_questions = total_questions;
|
||||||
@ -26,7 +27,7 @@ TriviaGame::~TriviaGame() {
|
|||||||
current_thread.reset();
|
current_thread.reset();
|
||||||
|
|
||||||
if (scores.size() == 0) {
|
if (scores.size() == 0) {
|
||||||
ah->send_message(channel_id, ":red_circle: Game cancelled!");
|
DiscordAPI::send_message(channel_id, ":red_circle: Game cancelled!", config.token, config.cert_location);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,13 +51,13 @@ TriviaGame::~TriviaGame() {
|
|||||||
average_time.pop_back(); average_time.pop_back(); average_time.pop_back();
|
average_time.pop_back(); average_time.pop_back(); average_time.pop_back();
|
||||||
message += ":small_blue_diamond: <@!" + p.first + ">: " + std::to_string(p.second) + " (Avg: " + average_time + " seconds)\n";
|
message += ":small_blue_diamond: <@!" + p.first + ">: " + std::to_string(p.second) + " (Avg: " + average_time + " seconds)\n";
|
||||||
}
|
}
|
||||||
ah->send_message(channel_id, message);
|
DiscordAPI::send_message(channel_id, message, config.token, config.cert_location);
|
||||||
|
|
||||||
sqlite3 *db; int rc; std::string sql;
|
sqlite3 *db; int rc; std::string sql;
|
||||||
|
|
||||||
rc = sqlite3_open("bot/db/trivia.db", &db);
|
rc = sqlite3_open("bot/db/trivia.db", &db);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
std::cerr << "Cant't open database: " << sqlite3_errmsg(db) << std::endl;
|
Logger::write("Can't open database: " + *sqlite3_errmsg(db), Logger::LogLevel::Severe);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string sql_in_list;
|
std::string sql_in_list;
|
||||||
@ -71,7 +72,7 @@ TriviaGame::~TriviaGame() {
|
|||||||
|
|
||||||
rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
|
rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
|
||||||
if (rc != SQLITE_OK) {
|
if (rc != SQLITE_OK) {
|
||||||
std::cerr << "SQL error." << std::endl;
|
Logger::write("Error creating prepared statement: " + *sqlite3_errmsg(db), Logger::LogLevel::Severe);
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert arguments
|
// insert arguments
|
||||||
@ -79,7 +80,7 @@ TriviaGame::~TriviaGame() {
|
|||||||
rc = sqlite3_bind_text(stmt, i + 1, pairs[i].first.c_str(), -1, (sqlite3_destructor_type) -1);
|
rc = sqlite3_bind_text(stmt, i + 1, pairs[i].first.c_str(), -1, (sqlite3_destructor_type) -1);
|
||||||
|
|
||||||
if (rc != SQLITE_OK) {
|
if (rc != SQLITE_OK) {
|
||||||
std::cerr << "SQL error." << std::endl;
|
Logger::write("Error binding prepared statement argument: " + *sqlite3_errmsg(db), Logger::LogLevel::Severe);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,7 +98,7 @@ TriviaGame::~TriviaGame() {
|
|||||||
data[id] = std::pair<int, int>(total_score, average_time);
|
data[id] = std::pair<int, int>(total_score, average_time);
|
||||||
} else if (rc != SQLITE_DONE) {
|
} else if (rc != SQLITE_DONE) {
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
std::cerr << "SQLite error." << std::endl;
|
Logger::write("Error fetching results: " + *sqlite3_errmsg(db), Logger::LogLevel::Severe);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,7 +106,7 @@ TriviaGame::~TriviaGame() {
|
|||||||
|
|
||||||
std::string update_sql;
|
std::string update_sql;
|
||||||
if (data.size() < scores.size()) { // some users dont have entries yet
|
if (data.size() < scores.size()) { // some users dont have entries yet
|
||||||
std::string sql = "INSERT INTO TotalScores (User, TotalScore, AverageTime) VALUES ";
|
sql = "INSERT INTO TotalScores (User, TotalScore, AverageTime) VALUES ";
|
||||||
for (auto &i : scores) {
|
for (auto &i : scores) {
|
||||||
if (data.find(i.first) == data.end()) {
|
if (data.find(i.first) == data.end()) {
|
||||||
sql += "(?, ?, ?),";
|
sql += "(?, ?, ?),";
|
||||||
@ -116,7 +117,7 @@ TriviaGame::~TriviaGame() {
|
|||||||
|
|
||||||
rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
|
rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
|
||||||
if (rc != SQLITE_OK) {
|
if (rc != SQLITE_OK) {
|
||||||
std::cerr << "SQL error." << std::endl;
|
Logger::write("Error creating prepared statement: " + *sqlite3_errmsg(db), Logger::LogLevel::Severe);
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = 1;
|
int count = 1;
|
||||||
@ -140,7 +141,7 @@ TriviaGame::~TriviaGame() {
|
|||||||
if (update_sql != "") {
|
if (update_sql != "") {
|
||||||
rc = sqlite3_prepare_v2(db, update_sql.c_str(), -1, &stmt, 0);
|
rc = sqlite3_prepare_v2(db, update_sql.c_str(), -1, &stmt, 0);
|
||||||
if (rc != SQLITE_OK) {
|
if (rc != SQLITE_OK) {
|
||||||
std::cerr << "SQL error." << std::endl;
|
Logger::write("Error creating prepared statement: " + *sqlite3_errmsg(db), Logger::LogLevel::Severe);
|
||||||
}
|
}
|
||||||
|
|
||||||
int index = 1;
|
int index = 1;
|
||||||
@ -180,16 +181,15 @@ void TriviaGame::question() {
|
|||||||
/// open db
|
/// open db
|
||||||
rc = sqlite3_open("bot/db/trivia.db", &db);
|
rc = sqlite3_open("bot/db/trivia.db", &db);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
std::cerr << "Cant't open database: " << sqlite3_errmsg(db) << std::endl;
|
Logger::write("Error opening database: " + *sqlite3_errmsg(db), Logger::LogLevel::Severe);
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare statement
|
// prepare statement
|
||||||
sqlite3_stmt *stmt;
|
sqlite3_stmt *stmt;
|
||||||
sql = "SELECT * FROM Questions ORDER BY RANDOM() LIMIT 1;";
|
sql = "SELECT * FROM Questions ORDER BY RANDOM() LIMIT 1;";
|
||||||
rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
|
rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
|
||||||
|
|
||||||
if (rc != SQLITE_OK) {
|
if (rc != SQLITE_OK) {
|
||||||
std::cerr << "SQL error." << std::endl;
|
Logger::write("Error creating prepared statement: " + *sqlite3_errmsg(db), Logger::LogLevel::Severe);
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = sqlite3_step(stmt);
|
rc = sqlite3_step(stmt);
|
||||||
@ -205,16 +205,17 @@ void TriviaGame::question() {
|
|||||||
boost::split(current_answers, answer, boost::is_any_of("*"));
|
boost::split(current_answers, answer, boost::is_any_of("*"));
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (rc != SQLITE_DONE) {
|
else {
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
std::cerr << "SQLite error." << std::endl;
|
Logger::write("Error fetching question: " + *sqlite3_errmsg(db), Logger::LogLevel::Severe);
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
|
|
||||||
questions_asked++;
|
questions_asked++;
|
||||||
ah->send_message(channel_id, ":question: **(" + std::to_string(questions_asked) + "/" + std::to_string(total_questions) + ")** " + current_question);
|
DiscordAPI::send_message(channel_id, ":question: **(" + std::to_string(questions_asked) + "/" + std::to_string(total_questions) + ")** " + current_question,
|
||||||
|
config.token, config.cert_location);
|
||||||
question_start = boost::posix_time::microsec_clock::universal_time();
|
question_start = boost::posix_time::microsec_clock::universal_time();
|
||||||
|
|
||||||
give_hint(0, "");
|
give_hint(0, "");
|
||||||
@ -253,8 +254,8 @@ void TriviaGame::give_hint(int hints_given, std::string hint) {
|
|||||||
|
|
||||||
// count number of *s
|
// count number of *s
|
||||||
int length = 0;
|
int length = 0;
|
||||||
for (unsigned int i = 0; i < word.length(); i++) {
|
for (unsigned int j = 0; j < word.length(); j++) {
|
||||||
if (word[i] == hide_char) {
|
if (word[j] == hide_char) {
|
||||||
length++;
|
length++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -281,11 +282,11 @@ void TriviaGame::give_hint(int hints_given, std::string hint) {
|
|||||||
hints_given++; // now equal to the amount of [hide_char]s that need to be present in each word
|
hints_given++; // now equal to the amount of [hide_char]s that need to be present in each word
|
||||||
|
|
||||||
if (print) {
|
if (print) {
|
||||||
ah->send_message(channel_id, ":small_orange_diamond: Hint: **`" + hint + "`**");
|
DiscordAPI::send_message(channel_id, ":small_orange_diamond: Hint: **`" + hint + "`**", config.token, config.cert_location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boost::this_thread::sleep(interval);
|
boost::this_thread::sleep(interval);
|
||||||
ah->send_message(channel_id, ":exclamation: Question failed. Answer: ** `" + *current_answers.begin() + "` **");
|
DiscordAPI::send_message(channel_id, ":exclamation: Question failed. Answer: ** `" + *current_answers.begin() + "` **", config.token, config.cert_location);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TriviaGame::handle_answer(std::string answer, DiscordObjects::User sender) {
|
void TriviaGame::handle_answer(std::string answer, DiscordObjects::User sender) {
|
||||||
@ -300,7 +301,7 @@ void TriviaGame::handle_answer(std::string answer, DiscordObjects::User sender)
|
|||||||
// remove the last three 0s
|
// remove the last three 0s
|
||||||
time_taken.pop_back(); time_taken.pop_back(); time_taken.pop_back();
|
time_taken.pop_back(); time_taken.pop_back(); time_taken.pop_back();
|
||||||
|
|
||||||
ah->send_message(channel_id, ":heavy_check_mark: <@!" + sender.id + "> You got it! (" + time_taken + " seconds)");
|
DiscordAPI::send_message(channel_id, ":heavy_check_mark: <@!" + sender.id + "> You got it! (" + time_taken + " seconds)", config.token, config.cert_location);
|
||||||
|
|
||||||
increase_score(sender.id);
|
increase_score(sender.id);
|
||||||
update_average_time(sender.id, diff.total_milliseconds());
|
update_average_time(sender.id, diff.total_milliseconds());
|
||||||
|
@ -11,15 +11,14 @@
|
|||||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||||
|
|
||||||
class GatewayHandler;
|
class GatewayHandler;
|
||||||
class APIHelper;
|
class BotConfig;
|
||||||
namespace DiscordObjects {
|
namespace DiscordObjects {
|
||||||
class User;
|
class User;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TriviaGame {
|
class TriviaGame {
|
||||||
public:
|
public:
|
||||||
TriviaGame(GatewayHandler *gh, APIHelper *ah, std::string channel_id, int total_questions, int delay);
|
TriviaGame(BotConfig &c, GatewayHandler *gh, std::string channel_id, int total_questions, int delay);
|
||||||
~TriviaGame();
|
~TriviaGame();
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
@ -27,6 +26,8 @@ public:
|
|||||||
void handle_answer(std::string answer, DiscordObjects::User sender);
|
void handle_answer(std::string answer, DiscordObjects::User sender);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
BotConfig &config;
|
||||||
|
|
||||||
int questions_asked;
|
int questions_asked;
|
||||||
int total_questions;
|
int total_questions;
|
||||||
boost::posix_time::seconds interval;
|
boost::posix_time::seconds interval;
|
||||||
@ -38,7 +39,6 @@ private:
|
|||||||
|
|
||||||
std::string channel_id;
|
std::string channel_id;
|
||||||
GatewayHandler *gh;
|
GatewayHandler *gh;
|
||||||
APIHelper *ah;
|
|
||||||
|
|
||||||
const char hide_char = '#';
|
const char hide_char = '#';
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ namespace DiscordObjects {
|
|||||||
Channel(json data);
|
Channel(json data);
|
||||||
|
|
||||||
void load_from_json(json data);
|
void load_from_json(json data);
|
||||||
|
std::string to_debug_string();
|
||||||
|
|
||||||
bool operator==(Channel rhs);
|
bool operator==(Channel rhs);
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ namespace DiscordObjects {
|
|||||||
type = "text";
|
type = "text";
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Channel::Channel(json data) {
|
inline Channel::Channel(json data) : Channel() {
|
||||||
load_from_json(data);
|
load_from_json(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,6 +74,19 @@ namespace DiscordObjects {
|
|||||||
user_limit = data.value("user_limit", -1);
|
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) {
|
inline bool Channel::operator==(Channel rhs) {
|
||||||
return id == rhs.id && id != "null";
|
return id == rhs.id && id != "null";
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
#ifndef BOT_DATA__STRUCTURES_Guild
|
#ifndef BOT_DATA__STRUCTURES_GUILD
|
||||||
#define BOT_DATA__STRUCTURES_Guild
|
#define BOT_DATA__STRUCTURES_GUILD
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
#include "../json/json.hpp"
|
#include "../json/json.hpp"
|
||||||
|
|
||||||
#include "Channel.hpp"
|
#include "Channel.hpp"
|
||||||
#include "User.hpp"
|
#include "User.hpp"
|
||||||
|
#include "Role.hpp"
|
||||||
|
#include "GuildMember.hpp"
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
@ -48,6 +51,7 @@ namespace DiscordObjects {
|
|||||||
Guild(json data);
|
Guild(json data);
|
||||||
|
|
||||||
void load_from_json(json data);
|
void load_from_json(json data);
|
||||||
|
std::string to_debug_string();
|
||||||
|
|
||||||
bool operator==(Guild rhs);
|
bool operator==(Guild rhs);
|
||||||
|
|
||||||
@ -62,13 +66,15 @@ namespace DiscordObjects {
|
|||||||
// bool embed_enabled;
|
// bool embed_enabled;
|
||||||
// std::string embed_channel_id;
|
// std::string embed_channel_id;
|
||||||
int verification_level;
|
int verification_level;
|
||||||
// TODO: Implement all guil fields
|
// TODO: Implement all guild fields
|
||||||
// std::vector<?> voice_states
|
// std::vector<?> voice_states
|
||||||
// std::vector<?> roles
|
|
||||||
// std::vector<?> emojis
|
// std::vector<?> emojis
|
||||||
// std::vector<?> features
|
// std::vector<?> features
|
||||||
|
bool unavailable;
|
||||||
|
|
||||||
std::vector<std::shared_ptr<Channel>> channels;
|
std::vector<Channel *> channels;
|
||||||
|
std::vector<GuildMember *> members;
|
||||||
|
std::vector<Role *> roles;
|
||||||
//std::vector<std::unique_ptr<DiscordObjects::User>> users;
|
//std::vector<std::unique_ptr<DiscordObjects::User>> users;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -77,13 +83,11 @@ namespace DiscordObjects {
|
|||||||
afk_timeout = verification_level = -1;
|
afk_timeout = verification_level = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Guild::Guild(json data) {
|
inline Guild::Guild(json data) : Guild() {
|
||||||
load_from_json(data);
|
load_from_json(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Guild::load_from_json(json data) {
|
inline void Guild::load_from_json(json data) {
|
||||||
Guild();
|
|
||||||
|
|
||||||
id = data.value("id", "null");
|
id = data.value("id", "null");
|
||||||
name = data.value("name", "null");
|
name = data.value("name", "null");
|
||||||
icon = data.value("icon", "null");
|
icon = data.value("icon", "null");
|
||||||
@ -93,6 +97,23 @@ namespace DiscordObjects {
|
|||||||
afk_channel_id = data.value("afk_channel_id", "null");
|
afk_channel_id = data.value("afk_channel_id", "null");
|
||||||
afk_timeout = data.value("afk_timeout", -1);
|
afk_timeout = data.value("afk_timeout", -1);
|
||||||
verification_level = data.value("verification_level", -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) {
|
inline bool Guild::operator==(Guild rhs) {
|
||||||
|
69
TriviaBot/bot/data_structures/GuildMember.hpp
Normal file
69
TriviaBot/bot/data_structures/GuildMember.hpp
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#ifndef BOT_DATA__STRUCTURES_GUILDMEMBER
|
||||||
|
#define BOT_DATA__STRUCTURES_GUILDMEMBER
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<Role *> roles;
|
||||||
|
std::string joined_at; // TODO: better type
|
||||||
|
bool deaf;
|
||||||
|
bool mute;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline GuildMember::GuildMember() {
|
||||||
|
user = nullptr;
|
||||||
|
nick = joined_at = "null";
|
||||||
|
deaf = false;
|
||||||
|
mute = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline GuildMember::GuildMember(json data, User *user) : GuildMember() {
|
||||||
|
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**status:** " + user->status
|
||||||
|
+ "\n**game name:** " + user->game
|
||||||
|
+ "\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
|
114
TriviaBot/bot/data_structures/Role.hpp
Normal file
114
TriviaBot/bot/data_structures/Role.hpp
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#ifndef BOT_DATA__STRUCTURES_ROLE
|
||||||
|
#define BOT_DATA__STRUCTURES_ROLE
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
#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) : Role() {
|
||||||
|
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() {
|
||||||
|
return "**__Role " + id + "__**"
|
||||||
|
+ "\n**name:** " + name
|
||||||
|
+ "\n**colour:** " + std::to_string(colour)
|
||||||
|
+ "\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<Permission>(static_cast<int>(lhs) | static_cast<int>(rhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Permission operator|=(Permission &lhs, Permission rhs) {
|
||||||
|
lhs = static_cast<Permission>(static_cast<int>(lhs) | static_cast<int>(rhs));
|
||||||
|
return lhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -1,125 +0,0 @@
|
|||||||
Example data:
|
|
||||||
|
|
||||||
!!!!!!!!!!!!!!!!! GUILD_CREATE Event
|
|
||||||
{
|
|
||||||
"afk_channel_id": null,
|
|
||||||
"afk_timeout": 300,
|
|
||||||
"channels": [
|
|
||||||
{
|
|
||||||
"id": "200398901767962624",
|
|
||||||
"is_private": false,
|
|
||||||
"last_message_id": "201355522635595776",
|
|
||||||
"name": "general",
|
|
||||||
"permission_overwrites": [],
|
|
||||||
"position": 0,
|
|
||||||
"topic": "",
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bitrate": 64000,
|
|
||||||
"id": "200398901767962625",
|
|
||||||
"is_private": false,
|
|
||||||
"name": "General",
|
|
||||||
"permission_overwrites": [],
|
|
||||||
"position": 0,
|
|
||||||
"type": "voice",
|
|
||||||
"user_limit": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"default_message_notifications": 0,
|
|
||||||
"emojis": [],
|
|
||||||
"features": [],
|
|
||||||
"icon": null,
|
|
||||||
"id": "200398901767962624",
|
|
||||||
"joined_at": "2016-07-06T23:54:20.824000+00:00",
|
|
||||||
"large": false,
|
|
||||||
"member_count": 2,
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"deaf": false,
|
|
||||||
"joined_at": "2016-07-06T23:53:41.425000+00:00",
|
|
||||||
"mute": false,
|
|
||||||
"roles": [
|
|
||||||
"200399346498273280"
|
|
||||||
],
|
|
||||||
"user": {
|
|
||||||
"avatar": "1dc076d2d273615dd23546c86dbdfd9c",
|
|
||||||
"discriminator": "8212",
|
|
||||||
"id": "82232146579689472",
|
|
||||||
"username": "Jack"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"deaf": false,
|
|
||||||
"joined_at": "2016-07-06T23:54:20.824000+00:00",
|
|
||||||
"mute": false,
|
|
||||||
"roles": [
|
|
||||||
"200399601507893248"
|
|
||||||
],
|
|
||||||
"user": {
|
|
||||||
"avatar": "e871ceecaa362718af6d3174bc941977",
|
|
||||||
"bot": true,
|
|
||||||
"discriminator": "8194",
|
|
||||||
"id": "199657095258177539",
|
|
||||||
"username": "trivia-bot"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mfa_level": 0,
|
|
||||||
"name": "EleGiggle",
|
|
||||||
"owner_id": "82232146579689472",
|
|
||||||
"presences": [
|
|
||||||
{
|
|
||||||
"game": null,
|
|
||||||
"status": "online",
|
|
||||||
"user": {
|
|
||||||
"id": "82232146579689472"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"game": null,
|
|
||||||
"status": "online",
|
|
||||||
"user": {
|
|
||||||
"id": "199657095258177539"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"region": "london",
|
|
||||||
"roles": [
|
|
||||||
{
|
|
||||||
"color": 0,
|
|
||||||
"hoist": false,
|
|
||||||
"id": "200398901767962624",
|
|
||||||
"managed": false,
|
|
||||||
"mentionable": false,
|
|
||||||
"name": "@everyone",
|
|
||||||
"permissions": 36953089,
|
|
||||||
"position": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": 3066993,
|
|
||||||
"hoist": true,
|
|
||||||
"id": "200399346498273280",
|
|
||||||
"managed": false,
|
|
||||||
"mentionable": false,
|
|
||||||
"name": "All Perms",
|
|
||||||
"permissions": 506715199,
|
|
||||||
"position": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": 15844367,
|
|
||||||
"hoist": true,
|
|
||||||
"id": "200399601507893248",
|
|
||||||
"managed": false,
|
|
||||||
"mentionable": false,
|
|
||||||
"name": "Robot",
|
|
||||||
"permissions": 536083519,
|
|
||||||
"position": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"splash": null,
|
|
||||||
"unavailable": false,
|
|
||||||
"verification_level": 0,
|
|
||||||
"voice_states": []
|
|
||||||
}
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
|||||||
#define BOT_DATA__STRUCTURES_USER
|
#define BOT_DATA__STRUCTURES_USER
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "../json/json.hpp"
|
#include "../json/json.hpp"
|
||||||
|
|
||||||
@ -37,14 +38,21 @@ namespace DiscordObjects {
|
|||||||
std::string avatar;
|
std::string avatar;
|
||||||
bool bot;
|
bool bot;
|
||||||
bool mfa_enabled;
|
bool mfa_enabled;
|
||||||
|
|
||||||
|
// presence
|
||||||
|
std::string game;
|
||||||
|
std::string status;
|
||||||
|
|
||||||
|
std::vector<std::string> guilds;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline User::User() {
|
inline User::User() {
|
||||||
id = username = discriminator = avatar = "null";
|
id = username = discriminator = avatar = game = "null";
|
||||||
|
status = "offline";
|
||||||
bot = mfa_enabled = false;
|
bot = mfa_enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline User::User(json data) {
|
inline User::User(json data) : User() {
|
||||||
load_from_json(data);
|
load_from_json(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
BEGIN TRANSACTION;
|
BEGIN TRANSACTION;
|
||||||
CREATE TABLE "TotalScores" (
|
CREATE TABLE "TotalScores" (
|
||||||
`User` TEXT UNIQUE,
|
`User` TEXT NOT NULL,
|
||||||
`TotalScore` INTEGER,
|
`TotalScore` INTEGER NOT NULL,
|
||||||
`AverageTime` INTEGER,
|
`AverageTime` INTEGER NOT NULL,
|
||||||
PRIMARY KEY(User)
|
PRIMARY KEY(User)
|
||||||
);
|
);
|
||||||
CREATE TABLE "Questions" (
|
CREATE TABLE "Questions" (
|
||||||
`ID` INTEGER PRIMARY KEY AUTOINCREMENT,
|
`ID` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
`Category` TEXT,
|
`Category` TEXT NOT NULL,
|
||||||
`Question` TEXT,
|
`Question` TEXT NOT NULL,
|
||||||
`Answer` TEXT
|
`Answer` TEXT NOT NULL
|
||||||
|
);
|
||||||
|
CREATE TABLE `CustomJS` (
|
||||||
|
`ID` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
`GuildID` TEXT NOT NULL,
|
||||||
|
`CommandName` TEXT NOT NULL,
|
||||||
|
`Script` TEXT NOT NULL
|
||||||
);
|
);
|
||||||
COMMIT;
|
COMMIT;
|
102
TriviaBot/bot/http/HTTP.cpp
Normal file
102
TriviaBot/bot/http/HTTP.cpp
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
#include "HTTP.hpp"
|
||||||
|
|
||||||
|
#include "../Logger.hpp"
|
||||||
|
#include "../BotConfig.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Warning: (Awful) C Code
|
||||||
|
*/
|
||||||
|
namespace HTTP {
|
||||||
|
size_t write_callback(void *contents, size_t size, size_t nmemb, void *read_buffer) {
|
||||||
|
static_cast<std::string *>(read_buffer)->append(static_cast<char *>(contents), size * nmemb);
|
||||||
|
return size * nmemb;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string post_request(std::string url, std::string content_type, std::string data, long *response_code, std::string token, std::string ca_location) {
|
||||||
|
CURL *curl;
|
||||||
|
CURLcode res;
|
||||||
|
std::string read_buffer;
|
||||||
|
struct curl_slist *headers = nullptr;
|
||||||
|
|
||||||
|
curl = curl_easy_init();
|
||||||
|
if (curl) {
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||||
|
|
||||||
|
// Now with real HTTPS!
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CAINFO, ca_location.c_str());
|
||||||
|
|
||||||
|
std::string header_arr[3];
|
||||||
|
header_arr[0] = "Content-Type: " + content_type;
|
||||||
|
header_arr[1] = "Authorization: Bot " + token;
|
||||||
|
header_arr[2] = "User-Agent: DiscordBot(http://github.com/jackb-p/triviadiscord, 1.0)";
|
||||||
|
|
||||||
|
for (std::string h : header_arr) {
|
||||||
|
headers = curl_slist_append(headers, h.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &read_buffer);
|
||||||
|
|
||||||
|
res = curl_easy_perform(curl);
|
||||||
|
|
||||||
|
if (res == CURLE_OK) {
|
||||||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, response_code);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
curl_slist_free_all(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return read_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_request(std::string url, long *response_code, std::string token, std::string ca_location) {
|
||||||
|
CURL *curl;
|
||||||
|
CURLcode res;
|
||||||
|
std::string read_buffer;
|
||||||
|
struct curl_slist *headers = nullptr;
|
||||||
|
|
||||||
|
curl = curl_easy_init();
|
||||||
|
if (curl) {
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||||
|
|
||||||
|
// Now with real HTTPS!
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CAINFO, ca_location.c_str());
|
||||||
|
|
||||||
|
std::string header_arr[2];
|
||||||
|
header_arr[0] = "Authorization: Bot " + token;
|
||||||
|
header_arr[1] = "User-Agent: DiscordBot (http://github.com/jackb-p/triviadiscord, 1.0)";
|
||||||
|
|
||||||
|
for (std::string h : header_arr) {
|
||||||
|
headers = curl_slist_append(headers, h.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &HTTP::write_callback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &read_buffer);
|
||||||
|
|
||||||
|
res = curl_easy_perform(curl);
|
||||||
|
|
||||||
|
if (res == CURLE_OK) {
|
||||||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, response_code);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Logger::write("curl error: " + std::string(curl_easy_strerror(res)), Logger::LogLevel::Warning);
|
||||||
|
return read_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
curl_slist_free_all(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return read_buffer;
|
||||||
|
}
|
||||||
|
}
|
15
TriviaBot/bot/http/HTTP.hpp
Normal file
15
TriviaBot/bot/http/HTTP.hpp
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#ifndef BOT_HTTP_HTTP
|
||||||
|
#define BOT_HTTP_HTTP
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
class BotConfig;
|
||||||
|
|
||||||
|
namespace HTTP {
|
||||||
|
std::string post_request(std::string url, std::string content_type, std::string data, long *response_code, std::string token, std::string ca_location);
|
||||||
|
std::string get_request(std::string url, long *response_code, std::string token, std::string ca_location);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -1,57 +0,0 @@
|
|||||||
#include "HTTPHelper.hpp"
|
|
||||||
|
|
||||||
extern std::string bot_token;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Warning: (Awful) C Code
|
|
||||||
*/
|
|
||||||
|
|
||||||
std::string HTTPHelper::post_request(std::string url, std::string content_type, std::string data, long *response_code) {
|
|
||||||
CURL *curl;
|
|
||||||
CURLcode res;
|
|
||||||
std::string read_buffer;
|
|
||||||
struct curl_slist *headers = nullptr;
|
|
||||||
|
|
||||||
curl = curl_easy_init();
|
|
||||||
if (curl) {
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
|
||||||
|
|
||||||
// Now with real HTTPS!
|
|
||||||
curl_easy_setopt(curl, CURLOPT_CAINFO, "bot/http/DiscordCA.crt");
|
|
||||||
|
|
||||||
std::string header_arr[3];
|
|
||||||
header_arr[0] = "Content-Type: " + content_type;
|
|
||||||
header_arr[1] = "Authorization: Bot " + bot_token;
|
|
||||||
header_arr[2] = "User-Agent: DiscordBot(http://github.com/jackb-p/triviadiscord, 1.0)";
|
|
||||||
|
|
||||||
for (std::string h : header_arr) {
|
|
||||||
headers = curl_slist_append(headers, h.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
|
||||||
|
|
||||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
|
|
||||||
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &read_buffer);
|
|
||||||
|
|
||||||
res = curl_easy_perform(curl);
|
|
||||||
|
|
||||||
if (res == CURLE_OK) {
|
|
||||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, response_code);
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* always cleanup */
|
|
||||||
curl_easy_cleanup(curl);
|
|
||||||
curl_slist_free_all(headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
return read_buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t HTTPHelper::write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
|
|
||||||
((std::string *) userp)->append((char *) contents, size * nmemb);
|
|
||||||
return size * nmemb;
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
#ifndef BOT_HTTP_HTTPHELPER
|
|
||||||
#define BOT_HTTP_HTTPHELPER
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include <curl/curl.h>
|
|
||||||
|
|
||||||
class HTTPHelper {
|
|
||||||
public:
|
|
||||||
std::string post_request(std::string url, std::string content_type, std::string data, long *response_code);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
164
TriviaBot/bot/js/CommandHelper.cpp
Normal file
164
TriviaBot/bot/js/CommandHelper.cpp
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
#include "CommandHelper.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
#include "../Logger.hpp"
|
||||||
|
|
||||||
|
namespace CommandHelper {
|
||||||
|
std::vector<Command> commands;
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
sqlite3 *db; int return_code;
|
||||||
|
return_code = sqlite3_open("bot/db/trivia.db", &db);
|
||||||
|
|
||||||
|
std::string sql = "SELECT GuildID, CommandName, Script 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<const char *>(sqlite3_column_text(stmt, 0));
|
||||||
|
std::string command_name = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 1));
|
||||||
|
std::string script = reinterpret_cast<const char *>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::write(std::to_string(commands.size()) + " custom command(s) loaded", Logger::LogLevel::Info);
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
sqlite3_close(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool return_code_ok(int return_code) {
|
||||||
|
// TODO: NotLikeThis
|
||||||
|
if (return_code != SQLITE_OK) {
|
||||||
|
Logger::write("SQLite error", Logger::LogLevel::Severe);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool 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 };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns: 0 error, 1 inserted, 2 updated
|
||||||
|
int 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;";
|
||||||
|
Logger::write("Command already exists, updating.", Logger::LogLevel::Debug);
|
||||||
|
ret_value = 2;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sql = "INSERT INTO CustomJS(Script, GuildID, CommandName) VALUES (?1, ?2, ?3);";
|
||||||
|
Logger::write("Inserting new command.", Logger::LogLevel::Debug);
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
}
|
20
TriviaBot/bot/js/CommandHelper.hpp
Normal file
20
TriviaBot/bot/js/CommandHelper.hpp
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#ifndef BOT_JS_COMMANDHELPER
|
||||||
|
#define BOT_JS_COMMANDHELPER
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace CommandHelper {
|
||||||
|
struct Command {
|
||||||
|
std::string guild_id;
|
||||||
|
std::string command_name;
|
||||||
|
std::string script;
|
||||||
|
};
|
||||||
|
|
||||||
|
void init();
|
||||||
|
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);
|
||||||
|
bool command_in_db(std::string guild_id, std::string command_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
729
TriviaBot/bot/js/V8Instance.cpp
Normal file
729
TriviaBot/bot/js/V8Instance.cpp
Normal file
@ -0,0 +1,729 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <chrono>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "V8Instance.hpp"
|
||||||
|
#include "../DiscordAPI.hpp"
|
||||||
|
#include "../Logger.hpp"
|
||||||
|
#include "../BotConfig.hpp"
|
||||||
|
|
||||||
|
using namespace v8;
|
||||||
|
|
||||||
|
V8Instance::V8Instance(BotConfig &c, std::string guild_id, std::map<std::string, DiscordObjects::Guild> *guilds, std::map<std::string, DiscordObjects::Channel> *channels,
|
||||||
|
std::map<std::string, DiscordObjects::User> *users, std::map<std::string, DiscordObjects::Role> *roles) : config(c) {
|
||||||
|
|
||||||
|
rng = std::mt19937(std::random_device()());
|
||||||
|
this->guild_id = guild_id;
|
||||||
|
this->guilds = guilds;
|
||||||
|
this->channels = channels;
|
||||||
|
this->users = users;
|
||||||
|
this->roles = roles;
|
||||||
|
|
||||||
|
create();
|
||||||
|
}
|
||||||
|
|
||||||
|
void V8Instance::create() {
|
||||||
|
Isolate::CreateParams create_params;
|
||||||
|
create_params.array_buffer_allocator = ArrayBuffer::Allocator::NewDefaultAllocator();
|
||||||
|
|
||||||
|
isolate = Isolate::New(create_params);
|
||||||
|
isolate->Enter();
|
||||||
|
Logger::write("[v8] Created isolate", Logger::LogLevel::Debug);
|
||||||
|
|
||||||
|
Isolate::Scope isolate_scope(isolate);
|
||||||
|
HandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
// set global context
|
||||||
|
Local<Context> context = create_context();
|
||||||
|
context_.Reset(isolate, context);
|
||||||
|
Context::Scope context_scope(context);
|
||||||
|
|
||||||
|
initialise(context);
|
||||||
|
|
||||||
|
Logger::write("[v8] Created context and context scope", Logger::LogLevel::Debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
void V8Instance::initialise(Local<Context> context) {
|
||||||
|
HandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
Local<Object> server_obj = wrap_server(&(*guilds)[guild_id]);
|
||||||
|
|
||||||
|
context->Global()->Set(
|
||||||
|
context,
|
||||||
|
String::NewFromUtf8(isolate, "server", NewStringType::kNormal).ToLocalChecked(),
|
||||||
|
server_obj
|
||||||
|
).FromJust();
|
||||||
|
|
||||||
|
Logger::write("[v8] Bound server template", Logger::LogLevel::Debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
v8::Local<v8::Context> V8Instance::create_context() {
|
||||||
|
Local<ObjectTemplate> global = ObjectTemplate::New(isolate);
|
||||||
|
|
||||||
|
Local<External> self = External::New(isolate, (void *) this);
|
||||||
|
global->Set(
|
||||||
|
String::NewFromUtf8(isolate, "print", NewStringType::kNormal).ToLocalChecked(),
|
||||||
|
FunctionTemplate::New(isolate, V8Instance::js_print, self)
|
||||||
|
);
|
||||||
|
global->Set(
|
||||||
|
String::NewFromUtf8(isolate, "random", NewStringType::kNormal).ToLocalChecked(),
|
||||||
|
FunctionTemplate::New(isolate, V8Instance::js_random, self)
|
||||||
|
);
|
||||||
|
global->Set(
|
||||||
|
String::NewFromUtf8(isolate, "shuffle", NewStringType::kNormal).ToLocalChecked(),
|
||||||
|
FunctionTemplate::New(isolate, V8Instance::js_shuffle, self)
|
||||||
|
);
|
||||||
|
|
||||||
|
Logger::write("[v8] Created global context, added print function", Logger::LogLevel::Debug);
|
||||||
|
|
||||||
|
return Context::New(isolate, NULL, global);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* server */
|
||||||
|
Local<ObjectTemplate> V8Instance::make_server_template() {
|
||||||
|
EscapableHandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
||||||
|
templ->SetInternalFieldCount(1);
|
||||||
|
templ->SetHandler(
|
||||||
|
NamedPropertyHandlerConfiguration(
|
||||||
|
V8Instance::js_get_server,
|
||||||
|
(GenericNamedPropertySetterCallback) 0,
|
||||||
|
(GenericNamedPropertyQueryCallback) 0,
|
||||||
|
(GenericNamedPropertyDeleterCallback) 0,
|
||||||
|
(GenericNamedPropertyEnumeratorCallback) 0,
|
||||||
|
External::New(isolate, (void *) this)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return handle_scope.Escape(templ);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Object> V8Instance::wrap_server(DiscordObjects::Guild *guild) {
|
||||||
|
EscapableHandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
if (server_template.IsEmpty()) {
|
||||||
|
Local<ObjectTemplate> raw_template = make_server_template();
|
||||||
|
server_template.Reset(isolate, raw_template);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<ObjectTemplate> templ = Local<ObjectTemplate>::New(isolate, server_template);
|
||||||
|
Local<Object> result = templ->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
|
||||||
|
|
||||||
|
Local<External> guild_ptr = External::New(isolate, guild);
|
||||||
|
result->SetInternalField(0, guild_ptr);
|
||||||
|
|
||||||
|
return handle_scope.Escape(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void V8Instance::js_get_server(Local<Name> property, const PropertyCallbackInfo<Value> &info) {
|
||||||
|
void *self_v = info.Data().As<External>()->Value();
|
||||||
|
if (!self_v) {
|
||||||
|
Logger::write("[v8] [js_get_server] Class pointer empty", Logger::LogLevel::Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
V8Instance *self = static_cast<V8Instance *>(self_v);
|
||||||
|
|
||||||
|
void *guild_v = info.Holder()->GetInternalField(0).As<External>()->Value();
|
||||||
|
if (!guild_v) {
|
||||||
|
Logger::write("[v8] [js_get_server] Guild pointer empty", Logger::LogLevel::Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DiscordObjects::Guild *guild = static_cast<DiscordObjects::Guild *>(guild_v);
|
||||||
|
|
||||||
|
if (!property->IsString()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string property_s = *String::Utf8Value(property);
|
||||||
|
|
||||||
|
if (property_s == "Id") {
|
||||||
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), guild->id.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
else if (property_s == "Name") {
|
||||||
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), guild->name.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
else if (property_s == "IconUrl") {
|
||||||
|
std::string icon_url = "https://discordapp.com/api/guilds/" + guild->id + "/icons/" + guild->icon + ".jpg";
|
||||||
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), icon_url.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
else if (property_s == "Owner") {
|
||||||
|
std::string owner_id = guild->owner_id;
|
||||||
|
DiscordObjects::GuildMember *owner = *std::find_if(guild->members.begin(), guild->members.end(), [owner_id](DiscordObjects::GuildMember *m) {
|
||||||
|
return owner_id == m->user->id;
|
||||||
|
});
|
||||||
|
Local<Object> owner_obj = self->wrap_user(owner);
|
||||||
|
info.GetReturnValue().Set(owner_obj);
|
||||||
|
}
|
||||||
|
else if (property_s == "Roles") {
|
||||||
|
Local<Object> roles_obj = self->wrap_role_list(&guild->roles);
|
||||||
|
info.GetReturnValue().Set(roles_obj);
|
||||||
|
}
|
||||||
|
else if (property_s == "Channels") {
|
||||||
|
Local<Object> channels_obj = self->wrap_channel_list(&guild->channels);
|
||||||
|
info.GetReturnValue().Set(channels_obj);
|
||||||
|
}
|
||||||
|
else if (property_s == "Users") {
|
||||||
|
Local<Object> users_obj = self->wrap_user_list(&guild->members);
|
||||||
|
info.GetReturnValue().Set(users_obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* channel */
|
||||||
|
Local<ObjectTemplate> V8Instance::make_channel_template() {
|
||||||
|
EscapableHandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
||||||
|
templ->SetInternalFieldCount(1);
|
||||||
|
templ->SetHandler(
|
||||||
|
NamedPropertyHandlerConfiguration(
|
||||||
|
V8Instance::js_get_channel,
|
||||||
|
(GenericNamedPropertySetterCallback) 0,
|
||||||
|
(GenericNamedPropertyQueryCallback) 0,
|
||||||
|
(GenericNamedPropertyDeleterCallback) 0,
|
||||||
|
(GenericNamedPropertyEnumeratorCallback) 0,
|
||||||
|
External::New(isolate, (void *) this)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return handle_scope.Escape(templ);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Object> V8Instance::wrap_channel(DiscordObjects::Channel *channel) {
|
||||||
|
EscapableHandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
if (role_template.IsEmpty()) {
|
||||||
|
Local<ObjectTemplate> raw_template = make_channel_template();
|
||||||
|
channel_template.Reset(isolate, raw_template);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<ObjectTemplate> templ = Local<ObjectTemplate>::New(isolate, channel_template);
|
||||||
|
Local<Object> result = templ->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
|
||||||
|
|
||||||
|
Local<External> channel_ptr = External::New(isolate, channel);
|
||||||
|
result->SetInternalField(0, channel_ptr);
|
||||||
|
|
||||||
|
return handle_scope.Escape(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void V8Instance::js_get_channel(Local<Name> property, const PropertyCallbackInfo<Value> &info) {
|
||||||
|
void *channel_v = info.Holder()->GetInternalField(0).As<External>()->Value();
|
||||||
|
if (!channel_v) {
|
||||||
|
Logger::write("[v8] [js_get_channel] Channel pointer empty", Logger::LogLevel::Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DiscordObjects::Channel *channel = static_cast<DiscordObjects::Channel *>(channel_v);
|
||||||
|
|
||||||
|
if (!property->IsString()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string property_s = *String::Utf8Value(property);
|
||||||
|
|
||||||
|
if (property_s == "Id") {
|
||||||
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), channel->id.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
else if (property_s == "Name") {
|
||||||
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), channel->name.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
else if (property_s == "Topic") {
|
||||||
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), channel->topic.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
else if (property_s == "IsVoice") {
|
||||||
|
info.GetReturnValue().Set(Boolean::New(info.GetIsolate(), channel->type == "voice"));
|
||||||
|
}
|
||||||
|
else if (property_s == "Users") {
|
||||||
|
info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "Channel.Users not implemented.", NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* channel list */
|
||||||
|
Local<ObjectTemplate> V8Instance::make_channel_list_template() {
|
||||||
|
EscapableHandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
||||||
|
templ->SetInternalFieldCount(1);
|
||||||
|
templ->SetHandler(
|
||||||
|
IndexedPropertyHandlerConfiguration(
|
||||||
|
V8Instance::js_get_channel_list,
|
||||||
|
(IndexedPropertySetterCallback) 0,
|
||||||
|
(IndexedPropertyQueryCallback) 0,
|
||||||
|
(IndexedPropertyDeleterCallback) 0,
|
||||||
|
(IndexedPropertyEnumeratorCallback) 0,
|
||||||
|
External::New(isolate, (void *) this)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return handle_scope.Escape(templ);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Object> V8Instance::wrap_channel_list(std::vector<DiscordObjects::Channel *> *channel_list) {
|
||||||
|
EscapableHandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
if (channel_list_template.IsEmpty()) {
|
||||||
|
Local<ObjectTemplate> raw_template = make_channel_list_template();
|
||||||
|
channel_list_template.Reset(isolate, raw_template);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<ObjectTemplate> templ = Local<ObjectTemplate>::New(isolate, channel_list_template);
|
||||||
|
Local<Object> result = templ->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
|
||||||
|
|
||||||
|
// imitate an array
|
||||||
|
result->Set(String::NewFromUtf8(isolate, "length", NewStringType::kNormal).ToLocalChecked(), Integer::New(isolate, (*channel_list).size()));
|
||||||
|
result->SetPrototype(Array::New(isolate)->GetPrototype());
|
||||||
|
|
||||||
|
Local<External> channel_list_ptr = External::New(isolate, channel_list);
|
||||||
|
result->SetInternalField(0, channel_list_ptr);
|
||||||
|
|
||||||
|
return handle_scope.Escape(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void V8Instance::js_get_channel_list(uint32_t index, const PropertyCallbackInfo<Value> &info) {
|
||||||
|
void *self_v = info.Data().As<External>()->Value();
|
||||||
|
if (!self_v) {
|
||||||
|
Logger::write("[v8] [js_get_channel_list] Class pointer empty", Logger::LogLevel::Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
V8Instance *self = static_cast<V8Instance *>(self_v);
|
||||||
|
|
||||||
|
void *channel_list_v = info.Holder()->GetInternalField(0).As<External>()->Value();
|
||||||
|
if (!channel_list_v) {
|
||||||
|
Logger::write("[v8] [js_get_channel_list] Channel List pointer empty", Logger::LogLevel::Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::vector<DiscordObjects::Channel *> *channel_list = static_cast<std::vector<DiscordObjects::Channel *> *>(channel_list_v);
|
||||||
|
|
||||||
|
|
||||||
|
if (index < (*channel_list).size()) {
|
||||||
|
Local<Object> channel_obj = self->wrap_channel((*channel_list)[index]);
|
||||||
|
info.GetReturnValue().Set(channel_obj);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
info.GetReturnValue().SetUndefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* user */
|
||||||
|
Local<ObjectTemplate> V8Instance::make_user_template() {
|
||||||
|
EscapableHandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
||||||
|
templ->SetInternalFieldCount(1);
|
||||||
|
templ->SetHandler(
|
||||||
|
NamedPropertyHandlerConfiguration(
|
||||||
|
V8Instance::js_get_user,
|
||||||
|
(GenericNamedPropertySetterCallback)0,
|
||||||
|
(GenericNamedPropertyQueryCallback)0,
|
||||||
|
(GenericNamedPropertyDeleterCallback)0,
|
||||||
|
(GenericNamedPropertyEnumeratorCallback)0,
|
||||||
|
External::New(isolate, (void *) this)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return handle_scope.Escape(templ);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Object> V8Instance::wrap_user(DiscordObjects::GuildMember *member) {
|
||||||
|
EscapableHandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
if (user_template.IsEmpty()) {
|
||||||
|
Local<ObjectTemplate> raw_template = make_user_template();
|
||||||
|
user_template.Reset(isolate, raw_template);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<ObjectTemplate> templ = Local<ObjectTemplate>::New(isolate, user_template);
|
||||||
|
Local<Object> result = templ->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
|
||||||
|
|
||||||
|
Local<External> member_ptr = External::New(isolate, member);
|
||||||
|
result->SetInternalField(0, member_ptr);
|
||||||
|
|
||||||
|
return handle_scope.Escape(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void V8Instance::js_get_user(Local<Name> property, const PropertyCallbackInfo<Value> &info) {
|
||||||
|
void *self_v = info.Data().As<External>()->Value();
|
||||||
|
if (!self_v) {
|
||||||
|
Logger::write("[v8] [js_get_user] Class pointer empty", Logger::LogLevel::Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
V8Instance *self = static_cast<V8Instance *>(self_v);
|
||||||
|
|
||||||
|
void *member_v = info.Holder()->GetInternalField(0).As<External>()->Value();
|
||||||
|
if (!member_v) {
|
||||||
|
Logger::write("[v8] [js_get_user] GuildMember pointer empty", Logger::LogLevel::Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DiscordObjects::GuildMember *member = static_cast<DiscordObjects::GuildMember *>(member_v);
|
||||||
|
|
||||||
|
if (!property->IsString()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string property_s = *String::Utf8Value(property);
|
||||||
|
|
||||||
|
if (property_s == "Id") {
|
||||||
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), member->user->id.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
else if (property_s == "Name") {
|
||||||
|
std::string name = member->nick == "null" ? member->user->username : member->nick;
|
||||||
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), name.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
else if (property_s == "Mention") {
|
||||||
|
std::string mention = "<@" + member->user->id + ">";
|
||||||
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), mention.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
else if (property_s == "AvatarUrl") {
|
||||||
|
std::string avatar_url = "https://discordapp.com/api/users/" + member->user->id + "/avatars/" + member->user->avatar + ".jpg";
|
||||||
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), avatar_url.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
else if (property_s == "Roles") {
|
||||||
|
Local<Object> roles_obj = self->wrap_role_list(&member->roles);
|
||||||
|
info.GetReturnValue().Set(roles_obj);
|
||||||
|
}
|
||||||
|
else if (property_s == "State") {
|
||||||
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), member->user->status.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
else if (property_s == "CurrentGame") {
|
||||||
|
if (member->user->game == "null") {
|
||||||
|
info.GetReturnValue().SetNull();
|
||||||
|
} else {
|
||||||
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), member->user->game.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* user list */
|
||||||
|
Local<ObjectTemplate> V8Instance::make_user_list_template() {
|
||||||
|
EscapableHandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
||||||
|
templ->SetInternalFieldCount(1);
|
||||||
|
templ->SetHandler(
|
||||||
|
IndexedPropertyHandlerConfiguration(
|
||||||
|
V8Instance::js_get_user_list,
|
||||||
|
(IndexedPropertySetterCallback) 0,
|
||||||
|
(IndexedPropertyQueryCallback) 0,
|
||||||
|
(IndexedPropertyDeleterCallback) 0,
|
||||||
|
(IndexedPropertyEnumeratorCallback) 0,
|
||||||
|
External::New(isolate, (void *) this)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return handle_scope.Escape(templ);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Object> V8Instance::wrap_user_list(std::vector<DiscordObjects::GuildMember *> *user_list) {
|
||||||
|
EscapableHandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
if (user_list_template.IsEmpty()) {
|
||||||
|
Local<ObjectTemplate> raw_template = make_user_list_template();
|
||||||
|
user_list_template.Reset(isolate, raw_template);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<ObjectTemplate> templ = Local<ObjectTemplate>::New(isolate, user_list_template);
|
||||||
|
Local<Object> result = templ->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
|
||||||
|
|
||||||
|
// imitate an array
|
||||||
|
result->Set(String::NewFromUtf8(isolate, "length", NewStringType::kNormal).ToLocalChecked(), Integer::New(isolate, (*user_list).size()));
|
||||||
|
result->SetPrototype(Array::New(isolate)->GetPrototype());
|
||||||
|
|
||||||
|
Local<External> user_list_ptr = External::New(isolate, user_list);
|
||||||
|
result->SetInternalField(0, user_list_ptr);
|
||||||
|
|
||||||
|
return handle_scope.Escape(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void V8Instance::js_get_user_list(uint32_t index, const PropertyCallbackInfo<Value> &info) {
|
||||||
|
void *self_v = info.Data().As<External>()->Value();
|
||||||
|
if (!self_v) {
|
||||||
|
Logger::write("[v8] [js_get_user_list] Class pointer empty", Logger::LogLevel::Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
V8Instance *self = static_cast<V8Instance *>(self_v);
|
||||||
|
|
||||||
|
void *user_list_v = info.Holder()->GetInternalField(0).As<External>()->Value();
|
||||||
|
if (!user_list_v) {
|
||||||
|
Logger::write("[v8] [js_get_user_list] GuildMember List pointer empty", Logger::LogLevel::Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::vector<DiscordObjects::GuildMember *> *user_list = static_cast<std::vector<DiscordObjects::GuildMember *> *>(user_list_v);
|
||||||
|
|
||||||
|
if (index < (*user_list).size()) {
|
||||||
|
Local<Object> role_obj = self->wrap_user((*user_list)[index]);
|
||||||
|
info.GetReturnValue().Set(role_obj);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
info.GetReturnValue().SetUndefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* role */
|
||||||
|
Local<ObjectTemplate> V8Instance::make_role_template() {
|
||||||
|
EscapableHandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
||||||
|
templ->SetInternalFieldCount(1);
|
||||||
|
templ->SetHandler(
|
||||||
|
NamedPropertyHandlerConfiguration(
|
||||||
|
V8Instance::js_get_role,
|
||||||
|
(GenericNamedPropertySetterCallback)0,
|
||||||
|
(GenericNamedPropertyQueryCallback)0,
|
||||||
|
(GenericNamedPropertyDeleterCallback)0,
|
||||||
|
(GenericNamedPropertyEnumeratorCallback)0,
|
||||||
|
External::New(isolate, (void *) this)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return handle_scope.Escape(templ);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Object> V8Instance::wrap_role(DiscordObjects::Role *role) {
|
||||||
|
EscapableHandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
if (role_template.IsEmpty()) {
|
||||||
|
Local<ObjectTemplate> raw_template = make_role_template();
|
||||||
|
role_template.Reset(isolate, raw_template);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<ObjectTemplate> templ = Local<ObjectTemplate>::New(isolate, role_template);
|
||||||
|
Local<Object> result = templ->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
|
||||||
|
|
||||||
|
Local<External> role_ptr = External::New(isolate, role);
|
||||||
|
result->SetInternalField(0, role_ptr);
|
||||||
|
|
||||||
|
return handle_scope.Escape(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void V8Instance::js_get_role(Local<Name> property, const PropertyCallbackInfo<Value> &info) {
|
||||||
|
void *role_v = info.Holder()->GetInternalField(0).As<External>()->Value();
|
||||||
|
if (!role_v) {
|
||||||
|
Logger::write("[v8] [js_get_role] Role pointer empty", Logger::LogLevel::Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DiscordObjects::Role *role = static_cast<DiscordObjects::Role *>(role_v);
|
||||||
|
|
||||||
|
if (!property->IsString()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string property_s = *String::Utf8Value(property);
|
||||||
|
|
||||||
|
if (property_s == "Id") {
|
||||||
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), role->id.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
else if (property_s == "Name") {
|
||||||
|
info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), role->name.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
else if (property_s == "Position") {
|
||||||
|
info.GetReturnValue().Set(Integer::New(info.GetIsolate(), role->position));
|
||||||
|
}
|
||||||
|
else if (property_s == "Red" || property_s == "Green" || property_s == "Blue") {
|
||||||
|
info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "Role.[Colour] not implemented.", NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* role list */
|
||||||
|
Local<ObjectTemplate> V8Instance::make_role_list_template() {
|
||||||
|
EscapableHandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
||||||
|
templ->SetInternalFieldCount(1);
|
||||||
|
templ->SetHandler(
|
||||||
|
IndexedPropertyHandlerConfiguration(
|
||||||
|
V8Instance::js_get_role_list,
|
||||||
|
(IndexedPropertySetterCallback) 0,
|
||||||
|
(IndexedPropertyQueryCallback) 0,
|
||||||
|
(IndexedPropertyDeleterCallback) 0,
|
||||||
|
(IndexedPropertyEnumeratorCallback) 0,
|
||||||
|
External::New(isolate, (void *) this)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return handle_scope.Escape(templ);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Object> V8Instance::wrap_role_list(std::vector<DiscordObjects::Role *> *role_list) {
|
||||||
|
EscapableHandleScope handle_scope(isolate);
|
||||||
|
|
||||||
|
if (role_list_template.IsEmpty()) {
|
||||||
|
Local<ObjectTemplate> raw_template = make_role_list_template();
|
||||||
|
role_list_template.Reset(isolate, raw_template);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<ObjectTemplate> templ = Local<ObjectTemplate>::New(isolate, role_list_template);
|
||||||
|
Local<Object> result = templ->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
|
||||||
|
|
||||||
|
// imitate an array
|
||||||
|
result->Set(String::NewFromUtf8(isolate, "length", NewStringType::kNormal).ToLocalChecked(), Integer::New(isolate, (*role_list).size()));
|
||||||
|
result->SetPrototype(Array::New(isolate)->GetPrototype());
|
||||||
|
|
||||||
|
Local<External> role_list_ptr = External::New(isolate, role_list);
|
||||||
|
result->SetInternalField(0, role_list_ptr);
|
||||||
|
|
||||||
|
return handle_scope.Escape(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void V8Instance::js_get_role_list(uint32_t index, const PropertyCallbackInfo<Value> &info) {
|
||||||
|
void *self_v = info.Data().As<External>()->Value();
|
||||||
|
if (!self_v) {
|
||||||
|
Logger::write("[v8] [js_get_role_list] Class pointer empty", Logger::LogLevel::Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
V8Instance *self = static_cast<V8Instance *>(self_v);
|
||||||
|
|
||||||
|
void *role_list_v = info.Holder()->GetInternalField(0).As<External>()->Value();
|
||||||
|
if (!role_list_v) {
|
||||||
|
Logger::write("[v8] [js_get_role_list] Role List pointer empty", Logger::LogLevel::Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::vector<DiscordObjects::Role *> *role_list = static_cast<std::vector<DiscordObjects::Role *> *>(role_list_v);
|
||||||
|
|
||||||
|
|
||||||
|
if (index < (*role_list).size()) {
|
||||||
|
Local<Object> role_obj = self->wrap_role((*role_list)[index]);
|
||||||
|
info.GetReturnValue().Set(role_obj);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
info.GetReturnValue().SetUndefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* global functions */
|
||||||
|
void V8Instance::js_print(const v8::FunctionCallbackInfo<v8::Value> &args) {
|
||||||
|
auto data = args.Data().As<External>();
|
||||||
|
V8Instance *self = static_cast<V8Instance *>(data->Value());
|
||||||
|
|
||||||
|
std::string output = "";
|
||||||
|
for (int i = 0; i < args.Length(); i++) {
|
||||||
|
v8::String::Utf8Value str(args[i]);
|
||||||
|
self->print_text += *str;
|
||||||
|
}
|
||||||
|
self->print_text += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void V8Instance::js_random(const v8::FunctionCallbackInfo<v8::Value> &args) {
|
||||||
|
auto data = args.Data().As<External>();
|
||||||
|
V8Instance *self = static_cast<V8Instance *>(data->Value());
|
||||||
|
|
||||||
|
int number_args = args.Length();
|
||||||
|
|
||||||
|
if (number_args == 0) {
|
||||||
|
std::uniform_real_distribution<double> dist(0, 1);
|
||||||
|
double random_val = dist(self->rng);
|
||||||
|
args.GetReturnValue().Set(Number::New(args.GetIsolate(), random_val));
|
||||||
|
}
|
||||||
|
else if (number_args == 1) {
|
||||||
|
int64_t max = args[0]->IntegerValue();
|
||||||
|
std::uniform_int_distribution<int> dist(0, max);
|
||||||
|
int random_val = dist(self->rng);
|
||||||
|
args.GetReturnValue().Set(Integer::New(args.GetIsolate(), random_val));
|
||||||
|
}
|
||||||
|
else if (number_args == 2) {
|
||||||
|
int64_t min = args[0]->IntegerValue();
|
||||||
|
int64_t max = args[1]->IntegerValue();
|
||||||
|
std::uniform_int_distribution<int> dist(min, max);
|
||||||
|
int random_val = dist(self->rng);
|
||||||
|
args.GetReturnValue().Set(Integer::New(args.GetIsolate(), random_val));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::string err_msg = "random() requires 0-2 arguments. You gave: " + std::to_string(number_args);
|
||||||
|
args.GetIsolate()->ThrowException(String::NewFromUtf8(args.GetIsolate(), err_msg.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void V8Instance::js_shuffle(const v8::FunctionCallbackInfo<v8::Value> &args) {
|
||||||
|
auto data = args.Data().As<External>();
|
||||||
|
V8Instance *self = static_cast<V8Instance *>(data->Value());
|
||||||
|
|
||||||
|
if (!args[0]->IsArray()) {
|
||||||
|
std::string err_msg = "shuffle() requires an array as it's argument. You gave: " + std::string(*String::Utf8Value(args[0]->TypeOf(args.GetIsolate())));
|
||||||
|
args.GetIsolate()->ThrowException(String::NewFromUtf8(args.GetIsolate(), err_msg.c_str(), NewStringType::kNormal).ToLocalChecked());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Local<Array> given_arr = Local<Array>::Cast(args[0]);
|
||||||
|
const int length = given_arr->Length();
|
||||||
|
Local<Array> return_arr = Array::New(args.GetIsolate(), length);
|
||||||
|
|
||||||
|
std::vector<Local<Value>> cpp_arr;
|
||||||
|
for (uint32_t i = 0; i < given_arr->Length(); i++) {
|
||||||
|
cpp_arr.push_back(given_arr->Get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shuffle(cpp_arr.begin(), cpp_arr.end(), self->rng);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < given_arr->Length(); i++) {
|
||||||
|
return_arr->Set(i, cpp_arr[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.GetReturnValue().Set(return_arr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void V8Instance::exec_js(std::string js, DiscordObjects::Channel *channel, DiscordObjects::GuildMember *sender, std::string args) {
|
||||||
|
HandleScope handle_scope(isolate);
|
||||||
|
Local<Context> context = Local<Context>::New(isolate, context_);
|
||||||
|
Context::Scope context_scope(context);
|
||||||
|
|
||||||
|
context->Global()->Set(
|
||||||
|
String::NewFromUtf8(isolate, "input", NewStringType::kNormal).ToLocalChecked(),
|
||||||
|
String::NewFromUtf8(isolate, args.c_str(), NewStringType::kNormal).ToLocalChecked()
|
||||||
|
);
|
||||||
|
Local<Object> user_obj = wrap_user(sender);
|
||||||
|
context->Global()->Set(
|
||||||
|
String::NewFromUtf8(isolate, "user", NewStringType::kNormal).ToLocalChecked(),
|
||||||
|
user_obj
|
||||||
|
);
|
||||||
|
Local<Object> channel_obj = wrap_channel(channel);
|
||||||
|
context->Global()->Set(
|
||||||
|
String::NewFromUtf8(isolate, "channel", NewStringType::kNormal).ToLocalChecked(),
|
||||||
|
channel_obj
|
||||||
|
);
|
||||||
|
// TODO: 'message' object here too, although it's fairly pointless
|
||||||
|
|
||||||
|
current_sender = sender;
|
||||||
|
current_channel = channel;
|
||||||
|
|
||||||
|
Logger::write("[v8] Preparing JS (guild " + (*guilds)[guild_id].id + ", channel " + channel->id + ")", Logger::LogLevel::Debug);
|
||||||
|
|
||||||
|
Local<String> source = String::NewFromUtf8(isolate, js.c_str(), NewStringType::kNormal).ToLocalChecked();
|
||||||
|
|
||||||
|
// compile
|
||||||
|
Logger::write("[v8] Isolate nullptr? " + std::to_string(isolate == nullptr) + " Context empty? " + std::to_string(context.IsEmpty()), Logger::LogLevel::Debug);
|
||||||
|
|
||||||
|
TryCatch compile_try_catch(isolate);
|
||||||
|
Local<Script> script;
|
||||||
|
|
||||||
|
auto begin = std::chrono::steady_clock::now();
|
||||||
|
if (!Script::Compile(context, source).ToLocal(&script)) {
|
||||||
|
String::Utf8Value error(compile_try_catch.Exception());
|
||||||
|
|
||||||
|
std::string err_msg = *error;
|
||||||
|
Logger::write("[v8] Compilation error: " + err_msg, Logger::LogLevel::Debug);
|
||||||
|
DiscordAPI::send_message(channel->id, ":warning: **Compilation error:** `" + err_msg + "`", config.token, config.cert_location);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TryCatch run_try_catch(isolate);
|
||||||
|
MaybeLocal<Value> v = script->Run(context);
|
||||||
|
if (v.IsEmpty()) {
|
||||||
|
String::Utf8Value error(run_try_catch.Exception());
|
||||||
|
|
||||||
|
std::string err_msg = *error;
|
||||||
|
Logger::write("[v8] Runtime error: " + err_msg, Logger::LogLevel::Debug);
|
||||||
|
DiscordAPI::send_message(channel->id, ":warning: **Runtime error:** `" + err_msg + "`", config.token, config.cert_location);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end = std::chrono::steady_clock::now();
|
||||||
|
long long time_taken = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();
|
||||||
|
Logger::write("[v8] Script compiled and run in " + std::to_string(time_taken) + "ms", Logger::LogLevel::Debug);
|
||||||
|
|
||||||
|
current_sender = nullptr;
|
||||||
|
current_channel = nullptr;
|
||||||
|
|
||||||
|
if (print_text != "") {
|
||||||
|
DiscordAPI::send_message(channel->id, print_text, config.token, config.cert_location);
|
||||||
|
print_text = "";
|
||||||
|
}
|
||||||
|
}
|
99
TriviaBot/bot/js/V8Instance.hpp
Normal file
99
TriviaBot/bot/js/V8Instance.hpp
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
#ifndef BOT_JS_V8INSTANCE
|
||||||
|
#define BOT_JS_V8INSTANCE
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <map>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include <include/v8.h>
|
||||||
|
#include <include/libplatform/libplatform.h>
|
||||||
|
|
||||||
|
#include "../data_structures/Guild.hpp"
|
||||||
|
#include "../data_structures/Channel.hpp"
|
||||||
|
#include "../data_structures/Role.hpp"
|
||||||
|
#include "../data_structures/GuildMember.hpp"
|
||||||
|
#include "../data_structures/User.hpp"
|
||||||
|
|
||||||
|
class BotConfig;
|
||||||
|
|
||||||
|
class V8Instance {
|
||||||
|
public:
|
||||||
|
V8Instance(BotConfig &c, std::string guild_id, std::map<std::string, DiscordObjects::Guild> *guilds,
|
||||||
|
std::map<std::string, DiscordObjects::Channel> *channels, std::map<std::string, DiscordObjects::User> *users, std::map<std::string, DiscordObjects::Role> *roles);
|
||||||
|
void exec_js(std::string js, DiscordObjects::Channel *channel, DiscordObjects::GuildMember *sender, std::string args = "");
|
||||||
|
|
||||||
|
private:
|
||||||
|
BotConfig &config;
|
||||||
|
|
||||||
|
void create();
|
||||||
|
v8::Local<v8::Context> create_context();
|
||||||
|
|
||||||
|
void initialise(v8::Local<v8::Context> context);
|
||||||
|
|
||||||
|
/* server */
|
||||||
|
v8::Global<v8::ObjectTemplate> server_template;
|
||||||
|
v8::Local<v8::ObjectTemplate> make_server_template();
|
||||||
|
v8::Local<v8::Object> wrap_server(DiscordObjects::Guild *guild);
|
||||||
|
static void js_get_server(v8::Local<v8::Name> property, const v8::PropertyCallbackInfo<v8::Value> &info);
|
||||||
|
|
||||||
|
|
||||||
|
/* user */
|
||||||
|
v8::Global<v8::ObjectTemplate> user_template;
|
||||||
|
v8::Local<v8::ObjectTemplate> make_user_template();
|
||||||
|
v8::Local<v8::Object> wrap_user(DiscordObjects::GuildMember *member);
|
||||||
|
static void js_get_user(v8::Local<v8::Name> property, const v8::PropertyCallbackInfo<v8::Value> &info);
|
||||||
|
|
||||||
|
v8::Global<v8::ObjectTemplate> user_list_template;
|
||||||
|
v8::Local<v8::ObjectTemplate> make_user_list_template();
|
||||||
|
v8::Local<v8::Object> wrap_user_list(std::vector<DiscordObjects::GuildMember *> *user_list);
|
||||||
|
static void js_get_user_list(uint32_t index, const v8::PropertyCallbackInfo<v8::Value> &info);
|
||||||
|
|
||||||
|
/* channel */
|
||||||
|
v8::Global<v8::ObjectTemplate> channel_template;
|
||||||
|
v8::Local<v8::ObjectTemplate> make_channel_template();
|
||||||
|
v8::Local<v8::Object> wrap_channel(DiscordObjects::Channel *channel);
|
||||||
|
static void js_get_channel(v8::Local<v8::Name> property, const v8::PropertyCallbackInfo<v8::Value> &info);
|
||||||
|
|
||||||
|
v8::Global<v8::ObjectTemplate> channel_list_template;
|
||||||
|
v8::Local<v8::ObjectTemplate> make_channel_list_template();
|
||||||
|
v8::Local<v8::Object> wrap_channel_list(std::vector<DiscordObjects::Channel *> *channel_list);
|
||||||
|
static void js_get_channel_list(uint32_t index, const v8::PropertyCallbackInfo<v8::Value> &info);
|
||||||
|
|
||||||
|
/* role */
|
||||||
|
v8::Global<v8::ObjectTemplate> role_template;
|
||||||
|
v8::Local<v8::ObjectTemplate> make_role_template();
|
||||||
|
v8::Local<v8::Object> wrap_role(DiscordObjects::Role *role);
|
||||||
|
static void js_get_role(v8::Local<v8::Name> property, const v8::PropertyCallbackInfo<v8::Value> &info);
|
||||||
|
|
||||||
|
v8::Global<v8::ObjectTemplate> role_list_template;
|
||||||
|
v8::Local<v8::ObjectTemplate> make_role_list_template();
|
||||||
|
v8::Local<v8::Object> wrap_role_list(std::vector<DiscordObjects::Role *> *role_list);
|
||||||
|
static void js_get_role_list(uint32_t index, const v8::PropertyCallbackInfo<v8::Value> &info);
|
||||||
|
|
||||||
|
/* print function */
|
||||||
|
static void js_print(const v8::FunctionCallbackInfo<v8::Value> &args);
|
||||||
|
|
||||||
|
/* randomness functions */
|
||||||
|
static void js_random(const v8::FunctionCallbackInfo<v8::Value> &args);
|
||||||
|
static void js_shuffle(const v8::FunctionCallbackInfo<v8::Value> &args);
|
||||||
|
|
||||||
|
std::map<std::string, DiscordObjects::Guild> *guilds;
|
||||||
|
std::map<std::string, DiscordObjects::Channel> *channels;
|
||||||
|
std::map<std::string, DiscordObjects::User> *users;
|
||||||
|
std::map<std::string, DiscordObjects::Role> *roles;
|
||||||
|
|
||||||
|
std::string guild_id;
|
||||||
|
v8::Isolate *isolate;
|
||||||
|
|
||||||
|
v8::Global<v8::Context> context_;
|
||||||
|
|
||||||
|
/* random generating variables */
|
||||||
|
std::mt19937 rng;
|
||||||
|
|
||||||
|
/* variables which change when a new command is executed */
|
||||||
|
std::string print_text;
|
||||||
|
DiscordObjects::Channel *current_channel;
|
||||||
|
DiscordObjects::GuildMember *current_sender;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -16,17 +16,8 @@
|
|||||||
/ Hideous code, but only needs to be run one time.
|
/ Hideous code, but only needs to be run one time.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
static int callback(void *x, int argc, char **argv, char **azColName) {
|
|
||||||
int i;
|
|
||||||
for (i = 0; i<argc; i++) {
|
|
||||||
std::cout << azColName[i] << " = " << (argv[i] ? argv[i] : "NULL") << std::endl;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int load_questions() {
|
int load_questions() {
|
||||||
sqlite3 *db;
|
sqlite3 *db;
|
||||||
char *zErrMsg = 0;
|
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
rc = sqlite3_open("bot/db/trivia.db", &db);
|
rc = sqlite3_open("bot/db/trivia.db", &db);
|
||||||
|
Loading…
Reference in New Issue
Block a user