Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
e5a9b6557d | |||
09830a6736 | |||
bc63dabb81 | |||
a802e6ae38 | |||
301d99ca2f | |||
b68ac1d3b2 | |||
a365456dd8 | |||
0498b22c8c | |||
c566e7f04f | |||
04565187a2 | |||
c9af4566c6 | |||
9c7e4f5a6d | |||
4b2d0ee50a | |||
da93eda0be | |||
8ebe6c2c4e | |||
7739404646 | |||
ec19b784b3 | |||
e4cc023055 | |||
f47d8adc36 | |||
47de861f45 | |||
1b2ea3c6bf | |||
a1c50b253d | |||
893a6cbbe1 | |||
3656080a43 | |||
005dad899b | |||
d49c502869 |
24
.gitignore
vendored
24
.gitignore
vendored
@ -1,11 +1,23 @@
|
||||
# VS files
|
||||
/.vs/
|
||||
/TriviaBot.sln
|
||||
/TriviaBot.VC*
|
||||
/TriviaBot/TriviaBot.vcxproj*
|
||||
/TriviaBot/x64/
|
||||
/Toast.sln
|
||||
/Toast.VC*
|
||||
/Toast/Toast.vcxproj*
|
||||
/Toast/x64/
|
||||
/x64/
|
||||
|
||||
# Data files
|
||||
/TriviaBot/data_management/questions
|
||||
/TriviaBot/bot/db/trivia.db
|
||||
/Toast/data_management/questions
|
||||
/Toast/bot/db/trivia.db
|
||||
|
||||
# Config file
|
||||
config.json
|
||||
|
||||
# Compiled sqlite file
|
||||
sqlite3.obj
|
||||
|
||||
# V8 lib
|
||||
lib/v8
|
||||
|
||||
# Built files
|
||||
build/
|
||||
|
28
.travis.yml
28
.travis.yml
@ -1,28 +0,0 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
language: cpp
|
||||
compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- boost-latest
|
||||
packages:
|
||||
- g++-5
|
||||
- gcc-5
|
||||
- libboost1.55-all-dev
|
||||
- libcurl4-openssl-dev
|
||||
- cmake
|
||||
install: export CXX="g++-5" CC="gcc-5";
|
||||
script:
|
||||
- cd TriviaBot
|
||||
- cmake .
|
||||
- make
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: sbZntceUqjyFZ13TlwuU0Jdtfma/jXHprv4z+pYrmuO3/YarKMtwlvM6UniagF2wVcHTFtiBmlbxfSyRfpy+P6XGkpvIJtJFrsMaSAoZpLI6mbSPsPxUCvc1VW3EESGIlWTJ97TZmU3opLjLSQIgYK1NB1+1KtLedVnQy6KCtTFthjupNwDIeSIFyPR31BgL0yQ7owPYZ63koS4U32ABPFxTYUPDbkI+Xq02nrzbn3OGKQY1cXmLJtvaTi+QQARfCFGYNTB4Ngt04LtpzBP5eeyj2P3YslZj9Xyr9PgqDt0uS2I5m/hLWsSLf/ssJ06EIBt2mYIORVV/XcgBw13VUBHawbd6hLfxSmSwhYpTewcjVbU1gE09kYmyVJ+KKKcbJjhWknIRd6z9+rLGwGb+zlgFnR9KFa4VqBSxGzfujwtBJVntaz0QVWb8vNL5U1xz1FyOBUT+6jWbr0L9d8QyK0ivPUbiYXwMQAoAEjjq7VbG985eyrTjxB6oxiWg9W8RveS1Mbb++/NWRSvsIfA1oEPrOgJONiOUP1+1XQ0rFLWpVHw5n4yuWFo6XpbfauCujIA4bNPGPqR0cfHj8QbUK9JDoA1SiOWsburQqUNqo0WHTUvrgut3OaP8jysDKsmoh8/tWeKlc6l5IqKQ4qu28sOQLd8RHcKOToKSfIOwRdA=
|
||||
file: TriviaBot
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
68
README.md
68
README.md
@ -1,4 +1,4 @@
|
||||
#trivia-bot <img src="https://cdn.discordapp.com/attachments/164732409919569920/205700949304541184/emoji.png" width="30" height="30" /> <img src="https://travis-ci.org/jackb-p/trivia-bot.svg?branch=release" />
|
||||
# Toast <img src="https://www.ahealthiermichigan.org/wp-content/uploads/2014/09/Transform-toast-into-breakfast.jpg" width="30" height="30" />
|
||||
|
||||
A bot which provides a Trivia game for [Discord](https://discordapp.com/).
|
||||
|
||||
@ -10,15 +10,27 @@ It requires no special permissions at this time (only read/write to channels).
|
||||
|
||||
|
||||
### Installation
|
||||
[Releases](https://github.com/jackb-p/trivia-bot/releases) are available for tagged versions. These are compiled on Ubuntu by Travis CI. To run on other systems you will need to compile yourself - a CMake configuration is included. Windows releases will happen one day.
|
||||
[Releases](https://github.com/jackb-p/trivia-bot/releases) are available for tagged versions. These are compiled on Debian Jessie by Jenkins. Note that you still require the dependency library files, so you will still have to build V8 and add it to your `LD_LIBRARY_PATH`. To run on other systems you will need to compile yourself - a CMake configuration is included. Windows releases will happen one day.
|
||||
|
||||
If you want to install a version for which a release does not exist, you will also have to compile manually. Compilation instructions are available for Linux below.
|
||||
|
||||
|
||||
### Running
|
||||
To run simply execute the program: `./TriviaBot`
|
||||
To run simply execute the program: `./Toast`
|
||||
|
||||
If you do not want to be prompted for your token every launch, provide it as an argument: `./TriviaBot {TOKEN}`
|
||||
#### Configuration
|
||||
The config file is automatically generated if it is not present. The JSON format is used. You must edit the config file for the bot to work correctly, the bot token is required.
|
||||
|
||||
The current configuration options are as follows:
|
||||
|
||||
1. **General**
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `api_cert_file` | The path to the Discord API .crt file for HTTPS. |
|
||||
| `bot_token` | Your Discord bot token. |
|
||||
| `owner_id` | The user ID of the owner of the bot. This allows owner-only (maintenance) commands, such as `shutdown`. |
|
||||
| `js_allowed_roles` | List of role names which are allowed to use the `createjs` ands `js` commands. |
|
||||
|
||||
### Trivia Questions
|
||||
Questions are obtained from [trivia-db on Sourceforge](https://sourceforge.net/projects/triviadb/).
|
||||
@ -30,32 +42,40 @@ LoadDB.cpp takes some time to execute.
|
||||
|
||||
|
||||
### Commands
|
||||
#### Trivia Game
|
||||
`` `trivia`` is the base command.
|
||||
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
|
||||
#### Javascript Commands
|
||||
The Javascript system is designed to mirror the old [Boobot implementation](https://www.boobot.party/). For now there are some exceptions:
|
||||
|
||||
1. Message objects aren't implemented.
|
||||
2. Properties *are* case sensitive. You must use `server.Name`, not `server.name`. This will not be changed.
|
||||
|
||||
### Compiling
|
||||
#### Dependencies
|
||||
| Name | Website | Notes |
|
||||
| ------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| boost | [boost.org](http://www.boost.org/) | |
|
||||
| websocketpp | [zaphoyd/websocketpp](https://github.com/zaphoyd/websocketpp) | Included as submodule. |
|
||||
| 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/). |
|
||||
| nlohmann/json | [nlohmann/json](https://github.com/nlohmann/json) | (Slightly modified) source file included in repo. |
|
||||
| Name | Website | Notes |
|
||||
| --- | --- | --- |
|
||||
| boost | [boost.org](http://www.boost.org/) | |
|
||||
| websocketpp | [zaphoyd/websocketpp](https://github.com/zaphoyd/websocketpp) | Included as submodule. |
|
||||
| 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/). |
|
||||
| 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. Must be built manually. |
|
||||
|
||||
#### Linux (debian)
|
||||
c++14 support is required. gcc 5 and above recommended.
|
||||
#### Linux (Debian)
|
||||
c++14 support is required. gcc 5 and above recommended, however it compiles on 4.9.2 (and possibly some versions below.)
|
||||
|
||||
1. Clone the github repo: `git clone https://github.com/jackb-p/TriviaDiscord.git TriviaDiscord`
|
||||
2. Navigate to repository directory: `cd TriviaDiscord`
|
||||
1. Clone the github repo: `git clone https://github.com/jackb-p/Toast.git ToastBot`
|
||||
2. Navigate to repository directory: `cd ToastBot`
|
||||
3. Clone the submodules: `git submodule init` and `git submodule update`
|
||||
4. Install other dependencies: `sudo apt-get install cmake libboost-all-dev libcurl-dev` (Package managers and names may vary, but all of these should be easy to find through a simple Google search.)
|
||||
5. `cd TriviaBot`
|
||||
6. `cmake .`
|
||||
7. `make`
|
||||
4. Install other dependencies: `sudo apt-get install build-essential cmake libboost-all-dev libcurl4-openssl-dev libssl-dev` (Package managers and names may vary, but all of these should be easy to find through a simple Google search.) V8 may require other dependencies.
|
||||
5. Build V8. Put the library files into lib/v8/lib/ and the include files into lib/v8/include. More instructions will be added at some point for this step.
|
||||
6. `cd Toast`
|
||||
7. `cmake .`
|
||||
8. `make`
|
||||
|
@ -1,5 +1,5 @@
|
||||
cmake_minimum_required(VERSION 2.8.7)
|
||||
project(TriviaBot)
|
||||
project(Toast)
|
||||
|
||||
###############################################################################
|
||||
## get source ## ##############################################################
|
||||
@ -7,12 +7,14 @@ project(TriviaBot)
|
||||
|
||||
file(GLOB_RECURSE sources bot/*.cpp bot/*.hpp ../lib/sqlite3/sqlite3.c)
|
||||
|
||||
link_directories(../lib/v8/lib)
|
||||
|
||||
###############################################################################
|
||||
## target definitions #########################################################
|
||||
###############################################################################
|
||||
|
||||
# add the data to the target, so it becomes visible in some IDE
|
||||
add_executable(TriviaBot ${sources})
|
||||
add_executable(Toast ${sources})
|
||||
|
||||
# add some compiler flags
|
||||
set (CMAKE_CXX_FLAGS "-std=c++14 -Wall ${CMAKE_CXX_FLAGS}")
|
||||
@ -25,12 +27,18 @@ find_package(Boost COMPONENTS system thread regex REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
find_package(CURL REQUIRED)
|
||||
|
||||
target_link_libraries(TriviaBot PUBLIC
|
||||
target_link_libraries(Toast PUBLIC
|
||||
${Boost_LIBRARIES}
|
||||
${OPENSSL_LIBRARIES}
|
||||
${CURL_LIBRARIES}
|
||||
pthread
|
||||
v8
|
||||
v8_libplatform
|
||||
v8_libbase
|
||||
icui18n
|
||||
icuuc
|
||||
rt
|
||||
dl
|
||||
pthread
|
||||
)
|
||||
|
||||
include_directories(
|
||||
@ -39,7 +47,10 @@ include_directories(
|
||||
${CURL_INCLUDE_DIR}
|
||||
../lib/websocketpp
|
||||
../lib/sqlite3
|
||||
../lib/v8
|
||||
)
|
||||
|
||||
# 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)
|
60
Toast/bot/BotConfig.cpp
Normal file
60
Toast/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");
|
||||
|
||||
js_allowed_roles = parsed["v8"].value("js_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", {
|
||||
{ "js_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
Toast/bot/BotConfig.hpp
Normal file
23
Toast/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> js_allowed_roles;
|
||||
|
||||
private:
|
||||
void load_from_json(std::string data);
|
||||
void create_new_file();
|
||||
};
|
||||
|
||||
#endif
|
@ -3,73 +3,74 @@
|
||||
#include <cstdio>
|
||||
#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
|
||||
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
|
||||
c.set_access_channels(websocketpp::log::alevel::app | websocketpp::log::alevel::connect);
|
||||
c.set_error_channels(websocketpp::log::elevel::all);
|
||||
cli.set_access_channels(websocketpp::log::alevel::app | websocketpp::log::alevel::connect);
|
||||
cli.set_error_channels(websocketpp::log::elevel::all);
|
||||
|
||||
// Initialize ASIO
|
||||
c.init_asio();
|
||||
cli.init_asio();
|
||||
|
||||
// Bind handlers
|
||||
c.set_socket_init_handler(bind(
|
||||
cli.set_socket_init_handler(bind(
|
||||
&ClientConnection::on_socket_init,
|
||||
this,
|
||||
websocketpp::lib::placeholders::_1
|
||||
));
|
||||
c.set_tls_init_handler(bind<context_ptr>(
|
||||
cli.set_tls_init_handler(bind<context_ptr>(
|
||||
&ClientConnection::on_tls_init,
|
||||
this,
|
||||
websocketpp::lib::placeholders::_1
|
||||
));
|
||||
c.set_message_handler(bind(
|
||||
cli.set_message_handler(bind(
|
||||
&ClientConnection::on_message,
|
||||
this,
|
||||
websocketpp::lib::placeholders::_1,
|
||||
websocketpp::lib::placeholders::_2
|
||||
));
|
||||
c.set_open_handler(bind(
|
||||
cli.set_open_handler(bind(
|
||||
&ClientConnection::on_open,
|
||||
this,
|
||||
websocketpp::lib::placeholders::_1
|
||||
));
|
||||
c.set_close_handler(bind(
|
||||
cli.set_close_handler(bind(
|
||||
&ClientConnection::on_close,
|
||||
this,
|
||||
websocketpp::lib::placeholders::_1
|
||||
));
|
||||
c.set_fail_handler(bind(
|
||||
cli.set_fail_handler(bind(
|
||||
&ClientConnection::on_fail,
|
||||
this,
|
||||
websocketpp::lib::placeholders::_1
|
||||
));
|
||||
|
||||
gh = std::make_unique<GatewayHandler>();
|
||||
}
|
||||
|
||||
// Open a connection to the URI provided
|
||||
void ClientConnection::start(std::string uri) {
|
||||
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
|
||||
c.get_alog().write(websocketpp::log::alevel::app, ec.message());
|
||||
Logger::write("Failed to create connection: " + ec.message(), Logger::LogLevel::Severe);
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the connection
|
||||
c.connect(con);
|
||||
c.run();
|
||||
cli.connect(con);
|
||||
cli.run();
|
||||
|
||||
Logger::write("Finished running", Logger::LogLevel::Debug);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
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) {
|
||||
@ -81,41 +82,43 @@ context_ptr ClientConnection::on_tls_init(websocketpp::connection_hdl) {
|
||||
boost::asio::ssl::context::no_sslv3 |
|
||||
boost::asio::ssl::context::single_dh_use);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
std::cout << "Error in context pointer: " << e.what() << std::endl;
|
||||
catch (std::exception &e) {
|
||||
Logger::write("[tls] Error in context pointer: " + std::string(e.what()), Logger::LogLevel::Severe);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
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
|
||||
c.get_elog().write(websocketpp::log::elevel::warn,
|
||||
"Fail handler: \n" +
|
||||
Logger::write("Fail handler: \n" +
|
||||
std::to_string(con->get_state()) + "\n" +
|
||||
std::to_string(con->get_local_close_code()) + "\n" +
|
||||
con->get_local_close_reason() + "\n" +
|
||||
std::to_string(con->get_remote_close_code()) + "\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) {
|
||||
Logger::write("Connection opened", Logger::LogLevel::Debug);
|
||||
}
|
||||
|
||||
void ClientConnection::on_message(websocketpp::connection_hdl hdl, message_ptr message) {
|
||||
if (message->get_opcode() != websocketpp::frame::opcode::text) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
// 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) {
|
||||
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 "json/json.hpp"
|
||||
|
||||
#include "GatewayHandler.hpp"
|
||||
|
||||
typedef websocketpp::client<websocketpp::config::asio_tls_client> client;
|
||||
|
||||
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 client::connection_ptr connection_ptr;
|
||||
|
||||
#include "GatewayHandler.hpp"
|
||||
class BotConfig;
|
||||
|
||||
class ClientConnection {
|
||||
public:
|
||||
ClientConnection();
|
||||
ClientConnection(BotConfig &c);
|
||||
|
||||
// Open a connection to the URI provided
|
||||
void start(std::string uri);
|
||||
|
||||
private:
|
||||
client cli;
|
||||
BotConfig &config;
|
||||
GatewayHandler gh;
|
||||
|
||||
// Event handlers
|
||||
void on_socket_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_message(websocketpp::connection_hdl hdl, message_ptr message);
|
||||
void on_close(websocketpp::connection_hdl);
|
||||
|
||||
private:
|
||||
client c;
|
||||
std::unique_ptr<GatewayHandler> gh;
|
||||
};
|
||||
|
||||
#endif
|
84
Toast/bot/DiscordAPI.cpp
Normal file
84
Toast/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
Toast/bot/DiscordAPI.hpp
Normal file
17
Toast/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
|
678
Toast/bot/GatewayHandler.cpp
Normal file
678
Toast/bot/GatewayHandler.cpp
Normal file
@ -0,0 +1,678 @@
|
||||
#include "GatewayHandler.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "DiscordAPI.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include "data_structures/GuildMember.hpp"
|
||||
#include "BotConfig.hpp"
|
||||
|
||||
GatewayHandler::GatewayHandler(BotConfig &c) : config(c) {
|
||||
last_seq = 0;
|
||||
|
||||
CommandHelper::init();
|
||||
}
|
||||
|
||||
void GatewayHandler::handle_data(std::string data, client &c, websocketpp::connection_hdl &hdl) {
|
||||
json decoded = json::parse(data);
|
||||
|
||||
int op = decoded["op"];
|
||||
|
||||
switch (op) {
|
||||
case 0: // Event dispatch
|
||||
on_dispatch(decoded, c, hdl);
|
||||
break;
|
||||
case 10: // Hello
|
||||
on_hello(decoded, c, hdl);
|
||||
break;
|
||||
case 11:
|
||||
Logger::write("Heartbeat acknowledged", Logger::LogLevel::Debug);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayHandler::send_heartbeat(client *c, websocketpp::connection_hdl hdl, int interval) {
|
||||
while (true) {
|
||||
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 = {
|
||||
{ "op", 1 },
|
||||
{ "d", last_seq }
|
||||
};
|
||||
|
||||
c->send(hdl, heartbeat.dump(), websocketpp::frame::opcode::text);
|
||||
|
||||
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) {
|
||||
heartbeat_interval = decoded["d"]["heartbeat_interval"];
|
||||
|
||||
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::send_heartbeat, this, &c, hdl, heartbeat_interval));
|
||||
|
||||
send_identify(c, hdl);
|
||||
}
|
||||
|
||||
void GatewayHandler::on_dispatch(json decoded, client &c, websocketpp::connection_hdl &hdl) {
|
||||
last_seq = decoded["s"];
|
||||
std::string event_name = decoded["t"];
|
||||
json data = decoded["d"];
|
||||
|
||||
if (event_name == "READY") {
|
||||
on_event_ready(data);
|
||||
}
|
||||
else if (event_name == "GUILD_CREATE") {
|
||||
on_event_guild_create(data);
|
||||
}
|
||||
else if (event_name == "GUILD_UPDATE") {
|
||||
on_event_guild_update(data);
|
||||
}
|
||||
else if (event_name == "GUILD_DELETE") {
|
||||
on_event_guild_delete(data);
|
||||
}
|
||||
else if (event_name == "GUILD_MEMBER_ADD") {
|
||||
on_event_guild_member_add(data);
|
||||
}
|
||||
else if (event_name == "GUILD_MEMBER_UPDATE") {
|
||||
on_event_guild_member_update(data);
|
||||
}
|
||||
else if (event_name == "GUILD_MEMBER_REMOVE") {
|
||||
on_event_guild_member_remove(data);
|
||||
}
|
||||
else if (event_name == "GUILD_ROLE_CREATE") {
|
||||
on_event_guild_role_create(data);
|
||||
}
|
||||
else if (event_name == "GUILD_ROLE_UPDATE") {
|
||||
on_event_guild_role_update(data);
|
||||
}
|
||||
else if (event_name == "GUILD_ROLE_DELETE") {
|
||||
on_event_guild_role_delete(data);
|
||||
}
|
||||
else if (event_name == "CHANNEL_CREATE") {
|
||||
on_event_channel_create(data);
|
||||
}
|
||||
else if (event_name == "CHANNEL_UPDATE") {
|
||||
on_event_channel_update(data);
|
||||
}
|
||||
else if (event_name == "CHANNEL_DELETE") {
|
||||
on_event_channel_delete(data);
|
||||
}
|
||||
else if (event_name == "MESSAGE_CREATE") {
|
||||
on_event_message_create(data, c, hdl);
|
||||
}
|
||||
else if (event_name == "PRESENCE_UPDATE") {
|
||||
on_event_presence_update(data);
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayHandler::on_event_ready(json data) {
|
||||
user_object.load_from_json(data["user"]);
|
||||
|
||||
Logger::write("Sign-on confirmed. (@" + user_object.username + "#" + user_object.discriminator + ")", Logger::LogLevel::Info);
|
||||
}
|
||||
|
||||
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: **toast** by Jack. <http://github.com/jackb-p/Toast>", 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;
|
||||
});
|
||||
BotConfig &conf = config;
|
||||
bool disallowed = std::find_if(member->roles.begin(), member->roles.end(), [conf](DiscordObjects::Role *r) -> bool {
|
||||
return conf.js_allowed_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 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.js_allowed_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) {
|
||||
auto it = games.find(channel_id);
|
||||
|
||||
if (it != games.end()) {
|
||||
it->second->interrupt();
|
||||
// remove from map
|
||||
games.erase(it);
|
||||
} else {
|
||||
Logger::write("Tried to delete a game that didn't exist (channel_id: " + channel_id + ")", Logger::LogLevel::Warning);
|
||||
}
|
||||
}
|
107
Toast/bot/GatewayHandler.hpp
Normal file
107
Toast/bot/GatewayHandler.hpp
Normal file
@ -0,0 +1,107 @@
|
||||
#ifndef BOT_GATEWAYHANDLER
|
||||
#define BOT_GATEWAYHANDLER
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <websocketpp/client.hpp>
|
||||
#include <websocketpp/config/asio_client.hpp>
|
||||
#include "json/json.hpp"
|
||||
|
||||
#include "TriviaGame.hpp"
|
||||
#include "js/CommandHelper.hpp"
|
||||
#include "js/V8Instance.hpp"
|
||||
#include "data_structures/User.hpp"
|
||||
#include "data_structures/Guild.hpp"
|
||||
#include "data_structures/Channel.hpp"
|
||||
#include "data_structures/Role.hpp"
|
||||
|
||||
typedef websocketpp::client<websocketpp::config::asio_tls_client> client;
|
||||
using json = nlohmann::json;
|
||||
|
||||
/************ Opcodes **************************************************************************************************
|
||||
* Code | Name | Description *
|
||||
* --------------------------------------------------------------------------------------------------------------------------*
|
||||
* 0 | Dispatch | dispatches an event *
|
||||
* 1 | Heartbeat | used for ping checking *
|
||||
* 2 | Identify | used for client handshake *
|
||||
* 3 | Status Update | used to update the client status *
|
||||
* 4 | Voice State Update | used to join/move/leave voice channels *
|
||||
* 5 | Voice Server Ping | used for voice ping checking *
|
||||
* 6 | Resume | used to resume a closed connection *
|
||||
* 7 | Reconnect | used to tell clients to reconnect to the gateway *
|
||||
* 8 | Request Guild Members | used to request guild members *
|
||||
* 9 | Invalid Session | used to notify client they have an invalid session id *
|
||||
* 10 | Hello | sent immediately after connecting, contains heartbeat and server debug information *
|
||||
* 11 | Heartback ACK | sent immediately following a client heartbeat that was received *
|
||||
*****************************************************************************************************************************/
|
||||
|
||||
class TriviaGame;
|
||||
class BotConfig;
|
||||
|
||||
class GatewayHandler {
|
||||
public:
|
||||
GatewayHandler(BotConfig &c);
|
||||
|
||||
void handle_data(std::string data, client &c, websocketpp::connection_hdl &hdl);
|
||||
|
||||
void delete_game(std::string channel_id);
|
||||
|
||||
private:
|
||||
BotConfig &config;
|
||||
|
||||
int last_seq;
|
||||
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;
|
||||
|
||||
// bot's user obj
|
||||
DiscordObjects::User user_object;
|
||||
|
||||
/* <id, obj> */
|
||||
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;
|
||||
|
||||
// <channel_id, game obj>
|
||||
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;
|
||||
};
|
||||
|
||||
#endif
|
47
Toast/bot/Logger.cpp
Normal file
47
Toast/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
Toast/bot/Logger.hpp
Normal file
14
Toast/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
|
66
Toast/bot/Toast.cpp
Normal file
66
Toast/bot/Toast.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <include/libplatform/libplatform.h>
|
||||
#include <include/v8.h>
|
||||
|
||||
#include "ClientConnection.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include "DiscordAPI.hpp"
|
||||
#include "BotConfig.hpp"
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
v8::V8::Dispose();
|
||||
v8::V8::ShutdownPlatform();
|
||||
delete platform;
|
||||
|
||||
curl_global_cleanup();
|
||||
|
||||
Logger::write("Cleaned up", Logger::LogLevel::Info);
|
||||
|
||||
return exit_code;
|
||||
}
|
@ -10,12 +10,13 @@
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
#include "GatewayHandler.hpp"
|
||||
#include "APIHelper.hpp"
|
||||
#include "DiscordAPI.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->ah = ah;
|
||||
this->channel_id = channel_id;
|
||||
|
||||
this->total_questions = total_questions;
|
||||
@ -26,7 +27,7 @@ TriviaGame::~TriviaGame() {
|
||||
current_thread.reset();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -50,13 +51,13 @@ TriviaGame::~TriviaGame() {
|
||||
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";
|
||||
}
|
||||
ah->send_message(channel_id, message);
|
||||
DiscordAPI::send_message(channel_id, message, config.token, config.cert_location);
|
||||
|
||||
sqlite3 *db; int rc; std::string sql;
|
||||
|
||||
rc = sqlite3_open("bot/db/trivia.db", &db);
|
||||
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;
|
||||
@ -71,7 +72,7 @@ TriviaGame::~TriviaGame() {
|
||||
|
||||
rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
|
||||
if (rc != SQLITE_OK) {
|
||||
std::cerr << "SQL error." << std::endl;
|
||||
Logger::write("Error creating prepared statement: " + *sqlite3_errmsg(db), Logger::LogLevel::Severe);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
if (rc != SQLITE_OK) {
|
||||
std::cerr << "SQL error." << std::endl;
|
||||
Logger::write("Error binding prepared statement argument: " + *sqlite3_errmsg(db), Logger::LogLevel::Severe);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -97,7 +98,7 @@ TriviaGame::~TriviaGame() {
|
||||
data[id] = std::pair<int, int>(total_score, average_time);
|
||||
} else if (rc != SQLITE_DONE) {
|
||||
sqlite3_finalize(stmt);
|
||||
std::cerr << "SQLite error." << std::endl;
|
||||
Logger::write("Error fetching results: " + *sqlite3_errmsg(db), Logger::LogLevel::Severe);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -105,7 +106,7 @@ TriviaGame::~TriviaGame() {
|
||||
|
||||
std::string update_sql;
|
||||
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) {
|
||||
if (data.find(i.first) == data.end()) {
|
||||
sql += "(?, ?, ?),";
|
||||
@ -116,7 +117,7 @@ TriviaGame::~TriviaGame() {
|
||||
|
||||
rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
|
||||
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;
|
||||
@ -140,7 +141,7 @@ TriviaGame::~TriviaGame() {
|
||||
if (update_sql != "") {
|
||||
rc = sqlite3_prepare_v2(db, update_sql.c_str(), -1, &stmt, 0);
|
||||
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;
|
||||
@ -180,16 +181,15 @@ void TriviaGame::question() {
|
||||
/// open db
|
||||
rc = sqlite3_open("bot/db/trivia.db", &db);
|
||||
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
|
||||
sqlite3_stmt *stmt;
|
||||
sql = "SELECT * FROM Questions ORDER BY RANDOM() LIMIT 1;";
|
||||
rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
|
||||
|
||||
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);
|
||||
@ -205,16 +205,17 @@ void TriviaGame::question() {
|
||||
boost::split(current_answers, answer, boost::is_any_of("*"));
|
||||
|
||||
}
|
||||
else if (rc != SQLITE_DONE) {
|
||||
else {
|
||||
sqlite3_finalize(stmt);
|
||||
std::cerr << "SQLite error." << std::endl;
|
||||
Logger::write("Error fetching question: " + *sqlite3_errmsg(db), Logger::LogLevel::Severe);
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close(db);
|
||||
|
||||
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();
|
||||
|
||||
give_hint(0, "");
|
||||
@ -253,8 +254,8 @@ void TriviaGame::give_hint(int hints_given, std::string hint) {
|
||||
|
||||
// count number of *s
|
||||
int length = 0;
|
||||
for (unsigned int i = 0; i < word.length(); i++) {
|
||||
if (word[i] == hide_char) {
|
||||
for (unsigned int j = 0; j < word.length(); j++) {
|
||||
if (word[j] == hide_char) {
|
||||
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
|
||||
|
||||
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);
|
||||
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) {
|
||||
@ -300,7 +301,7 @@ void TriviaGame::handle_answer(std::string answer, DiscordObjects::User sender)
|
||||
// remove the last three 0s
|
||||
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);
|
||||
update_average_time(sender.id, diff.total_milliseconds());
|
||||
@ -338,4 +339,4 @@ void TriviaGame::update_average_time(std::string user_id, int time) {
|
||||
// yeah it probably loses accuracy here, doesn't really matter
|
||||
average_times[user_id] = (int) (total / questions_answered);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,15 +11,14 @@
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
class GatewayHandler;
|
||||
class APIHelper;
|
||||
class BotConfig;
|
||||
namespace DiscordObjects {
|
||||
class User;
|
||||
}
|
||||
|
||||
|
||||
class TriviaGame {
|
||||
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();
|
||||
|
||||
void start();
|
||||
@ -27,6 +26,8 @@ public:
|
||||
void handle_answer(std::string answer, DiscordObjects::User sender);
|
||||
|
||||
private:
|
||||
BotConfig &config;
|
||||
|
||||
int questions_asked;
|
||||
int total_questions;
|
||||
boost::posix_time::seconds interval;
|
||||
@ -38,7 +39,6 @@ private:
|
||||
|
||||
std::string channel_id;
|
||||
GatewayHandler *gh;
|
||||
APIHelper *ah;
|
||||
|
||||
const char hide_char = '#';
|
||||
|
@ -32,6 +32,7 @@ namespace DiscordObjects {
|
||||
Channel(json data);
|
||||
|
||||
void load_from_json(json data);
|
||||
std::string to_debug_string();
|
||||
|
||||
bool operator==(Channel rhs);
|
||||
|
||||
@ -56,7 +57,7 @@ namespace DiscordObjects {
|
||||
type = "text";
|
||||
}
|
||||
|
||||
inline Channel::Channel(json data) {
|
||||
inline Channel::Channel(json data) : Channel() {
|
||||
load_from_json(data);
|
||||
}
|
||||
|
||||
@ -73,6 +74,19 @@ namespace DiscordObjects {
|
||||
user_limit = data.value("user_limit", -1);
|
||||
}
|
||||
|
||||
inline std::string Channel::to_debug_string() {
|
||||
return "**__Channel " + id + "__**"
|
||||
+ "\n**guild_id:** " + guild_id
|
||||
+ "\n**name:** " + name
|
||||
+ "\n**type:** " + type
|
||||
+ "\n**position:** " + std::to_string(position)
|
||||
+ "\n**is_private:** " + std::to_string(is_private)
|
||||
+ "\n**topic:** " + (topic == "" ? "[empty]" : topic)
|
||||
+ "\n**last_message_id:** " + last_message_id
|
||||
+ "\n**bitrate:** " + std::to_string(bitrate)
|
||||
+ "\n**user_limit:** " + std::to_string(user_limit);
|
||||
}
|
||||
|
||||
inline bool Channel::operator==(Channel rhs) {
|
||||
return id == rhs.id && id != "null";
|
||||
}
|
@ -1,13 +1,16 @@
|
||||
#ifndef BOT_DATA__STRUCTURES_Guild
|
||||
#define BOT_DATA__STRUCTURES_Guild
|
||||
#ifndef BOT_DATA__STRUCTURES_GUILD
|
||||
#define BOT_DATA__STRUCTURES_GUILD
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "../json/json.hpp"
|
||||
|
||||
#include "Channel.hpp"
|
||||
#include "User.hpp"
|
||||
#include "Role.hpp"
|
||||
#include "GuildMember.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
@ -48,6 +51,7 @@ namespace DiscordObjects {
|
||||
Guild(json data);
|
||||
|
||||
void load_from_json(json data);
|
||||
std::string to_debug_string();
|
||||
|
||||
bool operator==(Guild rhs);
|
||||
|
||||
@ -62,13 +66,15 @@ namespace DiscordObjects {
|
||||
// bool embed_enabled;
|
||||
// std::string embed_channel_id;
|
||||
int verification_level;
|
||||
// TODO: Implement all guil fields
|
||||
// TODO: Implement all guild fields
|
||||
// std::vector<?> voice_states
|
||||
// std::vector<?> roles
|
||||
// std::vector<?> emojis
|
||||
// std::vector<?> features
|
||||
bool unavailable;
|
||||
|
||||
std::vector<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;
|
||||
};
|
||||
|
||||
@ -77,13 +83,11 @@ namespace DiscordObjects {
|
||||
afk_timeout = verification_level = -1;
|
||||
}
|
||||
|
||||
inline Guild::Guild(json data) {
|
||||
inline Guild::Guild(json data) : Guild() {
|
||||
load_from_json(data);
|
||||
}
|
||||
|
||||
inline void Guild::load_from_json(json data) {
|
||||
Guild();
|
||||
|
||||
id = data.value("id", "null");
|
||||
name = data.value("name", "null");
|
||||
icon = data.value("icon", "null");
|
||||
@ -93,6 +97,23 @@ namespace DiscordObjects {
|
||||
afk_channel_id = data.value("afk_channel_id", "null");
|
||||
afk_timeout = data.value("afk_timeout", -1);
|
||||
verification_level = data.value("verification_level", -1);
|
||||
unavailable = data.value("unavailable", false);
|
||||
}
|
||||
|
||||
inline std::string Guild::to_debug_string() {
|
||||
return "**__Guild " + id + "__**"
|
||||
+ "\n**name:** " + name
|
||||
+ "\n**icon:** " + icon
|
||||
+ "\n**splash:** " + splash
|
||||
+ "\n**owner_id:** " + owner_id
|
||||
+ "\n**region:** " + region
|
||||
+ "\n**afk_channel_id:** " + afk_channel_id
|
||||
+ "\n**afk_timeout:** " + std::to_string(afk_timeout)
|
||||
+ "\n**verification_level:** " + std::to_string(verification_level)
|
||||
+ "\n**unavailable:** " + std::to_string(unavailable)
|
||||
+ "\n**channels:** " + std::to_string(channels.size())
|
||||
+ "\n**roles:** " + std::to_string(roles.size())
|
||||
+ "\n**members:** " + std::to_string(members.size());
|
||||
}
|
||||
|
||||
inline bool Guild::operator==(Guild rhs) {
|
69
Toast/bot/data_structures/GuildMember.hpp
Normal file
69
Toast/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
Toast/bot/data_structures/Role.hpp
Normal file
114
Toast/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
|
@ -2,6 +2,7 @@
|
||||
#define BOT_DATA__STRUCTURES_USER
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../json/json.hpp"
|
||||
|
||||
@ -37,14 +38,21 @@ namespace DiscordObjects {
|
||||
std::string avatar;
|
||||
bool bot;
|
||||
bool mfa_enabled;
|
||||
|
||||
// presence
|
||||
std::string game;
|
||||
std::string status;
|
||||
|
||||
std::vector<std::string> guilds;
|
||||
};
|
||||
|
||||
inline User::User() {
|
||||
id = username = discriminator = avatar = "null";
|
||||
id = username = discriminator = avatar = game = "null";
|
||||
status = "offline";
|
||||
bot = mfa_enabled = false;
|
||||
}
|
||||
|
||||
inline User::User(json data) {
|
||||
inline User::User(json data) : User() {
|
||||
load_from_json(data);
|
||||
}
|
||||
|
20
Toast/bot/db/schema.sqlite
Normal file
20
Toast/bot/db/schema.sqlite
Normal file
@ -0,0 +1,20 @@
|
||||
BEGIN TRANSACTION;
|
||||
CREATE TABLE "TotalScores" (
|
||||
`User` TEXT NOT NULL,
|
||||
`TotalScore` INTEGER NOT NULL,
|
||||
`AverageTime` INTEGER NOT NULL,
|
||||
PRIMARY KEY(User)
|
||||
);
|
||||
CREATE TABLE "Questions" (
|
||||
`ID` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
`Category` TEXT NOT NULL,
|
||||
`Question` TEXT NOT NULL,
|
||||
`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;
|
102
Toast/bot/http/HTTP.cpp
Normal file
102
Toast/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
Toast/bot/http/HTTP.hpp
Normal file
15
Toast/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
|
164
Toast/bot/js/CommandHelper.cpp
Normal file
164
Toast/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
Toast/bot/js/CommandHelper.hpp
Normal file
20
Toast/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
|
733
Toast/bot/js/V8Instance.cpp
Normal file
733
Toast/bot/js/V8Instance.cpp
Normal file
@ -0,0 +1,733 @@
|
||||
#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 == "TrueName") { // ignores nick
|
||||
std::string name = member->user->username;
|
||||
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
Toast/bot/js/V8Instance.hpp
Normal file
99
Toast/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.
|
||||
**/
|
||||
|
||||
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() {
|
||||
sqlite3 *db;
|
||||
char *zErrMsg = 0;
|
||||
int rc;
|
||||
|
||||
rc = sqlite3_open("bot/db/trivia.db", &db);
|
@ -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
|
@ -1,196 +0,0 @@
|
||||
#include "GatewayHandler.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "APIHelper.hpp"
|
||||
#include "data_structures/User.hpp"
|
||||
|
||||
extern std::string bot_token;
|
||||
|
||||
GatewayHandler::GatewayHandler() {
|
||||
last_seq = 0;
|
||||
|
||||
ah = new APIHelper();
|
||||
}
|
||||
|
||||
void GatewayHandler::handle_data(std::string data, client &c, websocketpp::connection_hdl &hdl) {
|
||||
json decoded = json::parse(data);
|
||||
|
||||
int op = decoded["op"];
|
||||
|
||||
switch (op) {
|
||||
case 0: // Event dispatch
|
||||
on_dispatch(decoded, c, hdl);
|
||||
break;
|
||||
case 10: // Hello
|
||||
on_hello(decoded, c, hdl);
|
||||
break;
|
||||
case 11:
|
||||
c.get_alog().write(websocketpp::log::alevel::app, "Heartbeat acknowledged.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayHandler::heartbeat(client *c, websocketpp::connection_hdl hdl, int interval) {
|
||||
while (true) {
|
||||
boost::this_thread::sleep(boost::posix_time::milliseconds(interval));
|
||||
|
||||
json heartbeat = {
|
||||
{ "op", 1 },
|
||||
{ "d", last_seq }
|
||||
};
|
||||
|
||||
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) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayHandler::on_hello(json decoded, client &c, websocketpp::connection_hdl &hdl) {
|
||||
heartbeat_interval = decoded["d"]["heartbeat_interval"];
|
||||
|
||||
c.get_alog().write(websocketpp::log::alevel::app, "Heartbeat interval: " + std::to_string(heartbeat_interval / 1000.0f) + " seconds");
|
||||
|
||||
heartbeat_thread = std::make_unique<boost::thread>(boost::bind(&GatewayHandler::heartbeat, this, &c, hdl, heartbeat_interval));
|
||||
|
||||
identify(c, hdl);
|
||||
}
|
||||
|
||||
void GatewayHandler::on_dispatch(json decoded, client &c, websocketpp::connection_hdl &hdl) {
|
||||
last_seq = decoded["s"];
|
||||
std::string event_name = decoded["t"];
|
||||
json data = decoded["d"];
|
||||
|
||||
if (event_name == "READY") {
|
||||
user_object.load_from_json(data["user"]);
|
||||
|
||||
c.get_alog().write(websocketpp::log::alevel::app, "Sign-on confirmed. (@" + user_object.username + "#" + user_object.discriminator + ")");
|
||||
}
|
||||
else if (event_name == "GUILD_CREATE") {
|
||||
std::string guild_id = data["id"];
|
||||
try {
|
||||
guilds[guild_id] = std::make_unique<DiscordObjects::Guild>(data);
|
||||
}
|
||||
catch (std::domain_error err) {
|
||||
// this doesn't even work
|
||||
c.get_alog().write(websocketpp::log::elevel::rerror, "Domain error");
|
||||
}
|
||||
|
||||
|
||||
c.get_alog().write(websocketpp::log::alevel::app, "Loaded guild: " + guilds[guild_id]->name);
|
||||
|
||||
for (json channel : data["channels"]) {
|
||||
std::string channel_id = channel["id"];
|
||||
channel["guild_id"] = guild_id;
|
||||
// create channel obj, add to overall channel list
|
||||
channels[channel_id] = std::make_shared<DiscordObjects::Channel>(channel);
|
||||
// add ptr to said channel list to guild's channel list
|
||||
guilds[guild_id]->channels.push_back(std::shared_ptr<DiscordObjects::Channel>(channels[channel_id]));
|
||||
}
|
||||
}
|
||||
else if (event_name == "TYPING_START") {}
|
||||
else if (event_name == "MESSAGE_CREATE") {
|
||||
std::string message = data["content"];
|
||||
auto channel = channels[data["channel_id"]];
|
||||
|
||||
DiscordObjects::User sender(data["author"]);
|
||||
|
||||
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) {
|
||||
json identify = {
|
||||
{ "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);
|
||||
c.get_alog().write(websocketpp::log::alevel::app, "Sent identify payload.");
|
||||
}
|
||||
|
||||
void GatewayHandler::delete_game(std::string channel_id) {
|
||||
auto it = games.find(channel_id);
|
||||
|
||||
if (it != games.end()) {
|
||||
it->second->interrupt();
|
||||
// remove from map
|
||||
games.erase(it);
|
||||
} else {
|
||||
std::cerr << "Tried to delete a game that didn't exist.";
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
#ifndef BOT_GATEWAYHANDLER
|
||||
#define BOT_GATEWAYHANDLER
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <websocketpp/client.hpp>
|
||||
#include <websocketpp/config/asio_client.hpp>
|
||||
#include "json/json.hpp"
|
||||
|
||||
#include "TriviaGame.hpp"
|
||||
#include "data_structures/User.hpp"
|
||||
#include "data_structures/Guild.hpp"
|
||||
#include "data_structures/Channel.hpp"
|
||||
|
||||
typedef websocketpp::client<websocketpp::config::asio_tls_client> client;
|
||||
using json = nlohmann::json;
|
||||
|
||||
/************ Opcodes **************************************************************************************************
|
||||
* Code | Name | Description *
|
||||
* --------------------------------------------------------------------------------------------------------------------------*
|
||||
* 0 | Dispatch | dispatches an event *
|
||||
* 1 | Heartbeat | used for ping checking *
|
||||
* 2 | Identify | used for client handshake *
|
||||
* 3 | Status Update | used to update the client status *
|
||||
* 4 | Voice State Update | used to join/move/leave voice channels *
|
||||
* 5 | Voice Server Ping | used for voice ping checking *
|
||||
* 6 | Resume | used to resume a closed connection *
|
||||
* 7 | Reconnect | used to tell clients to reconnect to the gateway *
|
||||
* 8 | Request Guild Members | used to request guild members *
|
||||
* 9 | Invalid Session | used to notify client they have an invalid session id *
|
||||
* 10 | Hello | sent immediately after connecting, contains heartbeat and server debug information *
|
||||
* 11 | Heartback ACK | sent immediately following a client heartbeat that was received *
|
||||
*****************************************************************************************************************************/
|
||||
|
||||
class TriviaGame;
|
||||
class APIHelper;
|
||||
|
||||
class GatewayHandler {
|
||||
public:
|
||||
GatewayHandler();
|
||||
|
||||
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);
|
||||
|
||||
private:
|
||||
int last_seq;
|
||||
int heartbeat_interval;
|
||||
|
||||
const int protocol_version = 5;
|
||||
|
||||
// bot's user obj
|
||||
DiscordObjects::User user_object;
|
||||
|
||||
// <id, ptr to data>
|
||||
std::map<std::string, std::unique_ptr<DiscordObjects::Guild>> guilds;
|
||||
// channels pointers are shared pointers, held here but also in guild objects
|
||||
std::map<std::string, std::shared_ptr<DiscordObjects::Channel>> channels;
|
||||
|
||||
// <channel_id, game obj>
|
||||
std::map<std::string, std::unique_ptr<TriviaGame>> games;
|
||||
|
||||
std::unique_ptr<boost::thread> heartbeat_thread;
|
||||
|
||||
APIHelper *ah;
|
||||
};
|
||||
|
||||
#endif
|
@ -1,40 +0,0 @@
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "ClientConnection.hpp"
|
||||
|
||||
std::string bot_token;
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
|
||||
if (argc == 2) {
|
||||
bot_token = argv[1];
|
||||
}
|
||||
else {
|
||||
std::cout << "Please enter your bot token: " << std::endl;
|
||||
std::cin >> bot_token;
|
||||
}
|
||||
|
||||
// todo: get this using API
|
||||
std::string uri = "wss://gateway.discord.gg/?v=5&encoding=json";
|
||||
|
||||
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();
|
||||
|
||||
return 0;
|
||||
}
|
@ -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": []
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
BEGIN TRANSACTION;
|
||||
CREATE TABLE "TotalScores" (
|
||||
`User` TEXT UNIQUE,
|
||||
`TotalScore` INTEGER,
|
||||
`AverageTime` INTEGER,
|
||||
PRIMARY KEY(User)
|
||||
);
|
||||
CREATE TABLE "Questions" (
|
||||
`ID` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
`Category` TEXT,
|
||||
`Question` TEXT,
|
||||
`Answer` TEXT
|
||||
);
|
||||
COMMIT;
|
@ -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
|
Loading…
Reference in New Issue
Block a user