Bot is now basically functional

Big tidy up, new question source.
This commit is contained in:
Jack Bond-Preston 2016-07-10 01:18:13 +01:00
parent 5168a38702
commit b31eaef6d6
28 changed files with 1510 additions and 16988 deletions

4
.gitignore vendored
View File

@ -245,4 +245,6 @@ ModelManifest.xml
.paket/paket.exe
# FAKE - F# Make
.fake/
.fake/
/TriviaBot/data_management/questions
/TriviaBot/bot/db/trivia.db

View File

@ -1,28 +0,0 @@
Copyright (c) 2015, ben-strasser
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of fast-cpp-csv-parser nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,68 +0,0 @@
#include <stdio.h>
#include <sqlite3.h>
#include <cstdio>
#include <string>
#include <iostream>
#include "csv.h"
/**
/ Takes the questions stored in the CSV downloaded from https://www.reddit.com/r/trivia/comments/3wzpvt/free_database_of_50000_trivia_questions/
/ Questions are stored in a weird format, which makes it a lot harder. To make it easier to process them, I replaced any double commas with single commas,
/ and renamed the repeated headers to Category1, Category2, Question1, etc... There was also one question with incorrect escaping which was fixed manually.
/ 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 loadDB() {
sqlite3 *db;
char *zErrMsg = 0;
int rc;
rc = sqlite3_open("../db/trivia.db", &db);
if (rc) {
std::cerr << "Can't open database: " << sqlite3_errmsg(db) << std::endl;
return 1;
}
else {
std::cout << "Opened database successfully" << std::endl;
}
io::CSVReader<9, io::trim_chars<' ', '\t'>, io::double_quote_escape<',', '"'>> in("trivia.csv");
in.read_header(io::ignore_extra_column, "Category", "Question", "Answer", "Category1", "Question1", "Answer1", "Category2", "Question2", "Answer2");
std::string c0, q0, a0, c1, q1, a1, c2, q2, a2;
int row = 0;
sqlite3_stmt *insertThreeQuestions;
std::string sql;
while (in.read_row(c0, q0, a0, c1, q1, a1, c2, q2, a2)) {
// Process three at a time because why not?
sql = "INSERT INTO Questions (Category, Question, Answer) VALUES (?1, ?2, ?3), (?4, ?5, ?6), (?7, ?8, ?9);";
sqlite3_prepare_v2(db, sql.c_str(), -1, &insertThreeQuestions, NULL);
sqlite3_bind_text(insertThreeQuestions, 1, c0.c_str(), -1, ((sqlite3_destructor_type)-1));
sqlite3_bind_text(insertThreeQuestions, 2, q0.c_str(), -1, ((sqlite3_destructor_type)-1));
sqlite3_bind_text(insertThreeQuestions, 3, a0.c_str(), -1, ((sqlite3_destructor_type)-1));
sqlite3_bind_text(insertThreeQuestions, 4, c1.c_str(), -1, ((sqlite3_destructor_type)-1));
sqlite3_bind_text(insertThreeQuestions, 5, q1.c_str(), -1, ((sqlite3_destructor_type)-1));
sqlite3_bind_text(insertThreeQuestions, 6, a1.c_str(), -1, ((sqlite3_destructor_type)-1));
sqlite3_bind_text(insertThreeQuestions, 7, c2.c_str(), -1, ((sqlite3_destructor_type)-1));
sqlite3_bind_text(insertThreeQuestions, 8, q2.c_str(), -1, ((sqlite3_destructor_type)-1));
sqlite3_bind_text(insertThreeQuestions, 9, a2.c_str(), -1, ((sqlite3_destructor_type)-1));
int result = sqlite3_step(insertThreeQuestions);
std::cout << result << " ";
}
std::cout << std::endl;
sqlite3_close(db);
std::getchar();
return 0;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@
<PropertyGroup Label="Globals">
<ProjectGuid>{E400396F-0C05-413E-B83E-1A4F41D86442}</ProjectGuid>
<RootNamespace>TriviaBot</RootNamespace>
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0.10586.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
@ -91,9 +91,10 @@
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;CURL_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>C:\boost_1_61_0;C:\OpenSSL-Win64\include;C:\Users\Jack\Documents\GitHubVisualStudio\TriviaDiscord\lib\liboauth\include;C:\Users\Jack\Documents\GitHubVisualStudio\TriviaDiscord\lib\curl\include;C:\buildcurl\third-party\libcurl\include;C:\Users\Jack\Documents\GitHubVisualStudio\TriviaDiscord\lib\websocketpp;C:\Users\Jack\Documents\GitHubVisualStudio\TriviaDiscord\lib\beast\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>-D_SCL_SECURE_NO_WARNINGS %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;CURL_STATICLIB;WIN32_WINNT=0x0600;URDL_NO_LIB=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>C:\boost_1_61_0;C:\OpenSSL-Win64\include;C:\Users\Jack\Documents\GitHubVisualStudio\TriviaDiscord\lib\cpr\include;C:\Users\Jack\Documents\GitHubVisualStudio\TriviaDiscord\lib\websocketpp;C:\Users\Jack\Documents\GitHubVisualStudio\TriviaDiscord\lib\beast\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>-D_SCL_SECURE_NO_WARNINGS -DUSE_SYSTEM_CURL=OFF -D_WIN32_WINNT=0x0A00 %(AdditionalOptions)</AdditionalOptions>
<ObjectFileName>$(IntDir)/%(RelativeDir)/</ObjectFileName>
</ClCompile>
<Link>
<AdditionalDependencies>libcurl.lib;sqlite3.lib;libeay32.lib;ssleay32.lib;%(AdditionalDependencies)</AdditionalDependencies>
@ -106,9 +107,10 @@
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;CURL_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>C:\boost_1_61_0;C:\OpenSSL-Win64\include;C:\Users\Jack\Documents\GitHubVisualStudio\TriviaDiscord\lib\liboauth\include;C:\Users\Jack\Documents\GitHubVisualStudio\TriviaDiscord\lib\curl\include;C:\buildcurl\third-party\libcurl\include;C:\Users\Jack\Documents\GitHubVisualStudio\TriviaDiscord\lib\websocketpp;C:\Users\Jack\Documents\GitHubVisualStudio\TriviaDiscord\lib\sqlite3;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>-D_SCL_SECURE_NO_WARNINGS %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;CURL_STATICLIB;WIN32_WINNT=0x0600;URDL_NO_LIB=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>C:\boost_1_61_0;C:\OpenSSL-Win64\include;C:\Users\Jack\Documents\GitHubVisualStudio\TriviaDiscord\lib\cpr\include;C:\Users\Jack\Documents\GitHubVisualStudio\TriviaDiscord\lib\websocketpp;C:\Users\Jack\Documents\GitHubVisualStudio\TriviaDiscord\lib\sqlite3;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>-D_SCL_SECURE_NO_WARNINGS -DUSE_SYSTEM_CURL=OFF -D_WIN32_WINNT=0x0A00 %(AdditionalOptions)</AdditionalOptions>
<ObjectFileName>$(IntDir)/%(RelativeDir)/</ObjectFileName>
</ClCompile>
<Link>
<AdditionalDependencies>libcurl.lib;sqlite3.lib;libeay32.lib;ssleay32.lib;%(AdditionalDependencies)</AdditionalDependencies>
@ -153,27 +155,38 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="bot\ClientConnection.hpp" />
<ClCompile Include="bot\HTTP\HTTPHelper.cpp" />
<ClCompile Include="bot\Source.cpp">
<AdditionalOptions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">-D_SCL_SECURE_NO_WARNINGS
%(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">-D_SCL_SECURE_NO_WARNINGS
%(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<ClCompile Include="bot\TriviaBot.cpp">
<AdditionalOptions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">-D_SCL_SECURE_NO_WARNINGS %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<ClCompile Include="CSV\LoadDB.cpp" />
<ClCompile Include="..\lib\cpr\cpr\auth.cpp" />
<ClCompile Include="..\lib\cpr\cpr\cookies.cpp" />
<ClCompile Include="..\lib\cpr\cpr\cprtypes.cpp" />
<ClCompile Include="..\lib\cpr\cpr\digest.cpp" />
<ClCompile Include="..\lib\cpr\cpr\error.cpp" />
<ClCompile Include="..\lib\cpr\cpr\multipart.cpp" />
<ClCompile Include="..\lib\cpr\cpr\parameters.cpp" />
<ClCompile Include="..\lib\cpr\cpr\payload.cpp" />
<ClCompile Include="..\lib\cpr\cpr\proxies.cpp" />
<ClCompile Include="..\lib\cpr\cpr\session.cpp" />
<ClCompile Include="..\lib\cpr\cpr\util.cpp" />
<ClCompile Include="bot\APIHelper.cpp" />
<ClCompile Include="bot\ClientConnection.cpp" />
<ClInclude Include="bot\ClientConnection.hpp" />
<ClCompile Include="bot\GatewayHandler.cpp" />
<ClCompile Include="bot\http\HTTPHelper.cpp" />
<ClCompile Include="bot\TriviaBot.cpp" />
<ClCompile Include="bot\TriviaGame.cpp" />
<ClCompile Include="data_management\LoadDB.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\..\..\..\AppData\Local\Temp\Rar$DRa0.635\fast-cpp-csv-parser-master\LICENSE" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="bot\json\json.hpp" />
<ClInclude Include="bot\ProtocolHandler.h" />
<ClInclude Include="bot\APIHelper.hpp" />
<ClInclude Include="bot\data_structures\Channel.hpp" />
<ClInclude Include="bot\data_structures\Guild.hpp" />
<ClInclude Include="bot\data_structures\User.hpp" />
<ClInclude Include="bot\GatewayHandler.hpp" />
<ClInclude Include="bot\HTTP\HTTPHelper.hpp" />
<ClInclude Include="CSV\csv.h" />
<ClInclude Include="bot\json\json.hpp" />
<ClInclude Include="bot\TriviaGame.hpp" />
</ItemGroup>
<ItemGroup>
<Text Include="bot\data_structures\Text.txt" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@ -15,37 +15,91 @@
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="..\..\..\..\..\AppData\Local\Temp\Rar$DRa0.635\fast-cpp-csv-parser-master\LICENSE" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="CSV\csv.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="bot\HTTPHelper.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="bot\ProtocolHandler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="bot\json\json.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="bot\GatewayHandler.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="bot\HTTP\HTTPHelper.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="bot\APIHelper.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="bot\ClientConnection.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="bot\TriviaGame.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="bot\data_structures\User.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="bot\data_structures\Channel.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="bot\data_structures\Guild.hpp">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="CSV\LoadDB.cpp">
<ClCompile Include="bot\ClientConnection.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\cpr\cpr\auth.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\cpr\cpr\cookies.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\cpr\cpr\cprtypes.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\cpr\cpr\digest.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\cpr\cpr\error.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\cpr\cpr\multipart.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\cpr\cpr\parameters.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\cpr\cpr\payload.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\cpr\cpr\proxies.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\cpr\cpr\session.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\cpr\cpr\util.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="bot\GatewayHandler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="bot\APIHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="bot\http\HTTPHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="bot\TriviaGame.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="data_management\LoadDB.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="bot\TriviaBot.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="bot\Source.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="bot\HTTPHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="bot\ClientConnection.hpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Text Include="bot\data_structures\Text.txt" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,19 @@
#include "http/HTTPHelper.hpp"
#include <cstdio>
#include "APIHelper.hpp"
APIHelper::APIHelper() {
http = new HTTPHelper();
}
void APIHelper::send_message(std::string channel_id, std::string message) {
const std::string url = CHANNELS_URL + "/" + channel_id + "/messages?" + TOKEN_PARAM;
json data = {
{ "content", message }
};
const std::string response = http->post_request(url, JSON_CTYPE, data.dump());
std::cout << response << std::endl;
}

View File

@ -0,0 +1,28 @@
#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 = "https://discordapp.com/api";
const std::string CHANNELS_URL = BASE_URL + "/channels";
const std::string TOKEN = "MTk5NjU3MDk1MjU4MTc3NTM5.ClyBNQ.15qTa-XBKRtGNMMYeXCrU50GhWE";
const std::string TOKEN_PARAM = "token=" + TOKEN;
const std::string JSON_CTYPE = "application/json";
HTTPHelper *http;
};
#endif

View File

@ -0,0 +1,120 @@
#include "ClientConnection.hpp"
#include <cstdio>
#include <iostream>
#include "GatewayHandler.hpp"
ClientConnection::ClientConnection() {
// Reset the log channels
c.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);
// Initialize ASIO
c.init_asio();
// Bind handlers
c.set_socket_init_handler(bind(
&ClientConnection::on_socket_init,
this,
websocketpp::lib::placeholders::_1
));
c.set_tls_init_handler(bind<context_ptr>(
&ClientConnection::on_tls_init,
this,
websocketpp::lib::placeholders::_1
));
c.set_message_handler(bind(
&ClientConnection::on_message,
this,
websocketpp::lib::placeholders::_1,
websocketpp::lib::placeholders::_2
));
c.set_open_handler(bind(
&ClientConnection::on_open,
this,
websocketpp::lib::placeholders::_1
));
c.set_close_handler(bind(
&ClientConnection::on_close,
this,
websocketpp::lib::placeholders::_1
));
c.set_fail_handler(bind(
&ClientConnection::on_fail,
this,
websocketpp::lib::placeholders::_1
));
gHandler = new 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);
if (ec) { // failed to create connection
c.get_alog().write(websocketpp::log::alevel::app, ec.message());
return;
}
// Open the connection
c.connect(con);
c.run();
}
// Event handlers
void ClientConnection::on_socket_init(websocketpp::connection_hdl) {
c.get_alog().write(websocketpp::log::alevel::app, "Socket intialised.");
}
context_ptr ClientConnection::on_tls_init(websocketpp::connection_hdl) {
context_ptr ctx = std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23);
try {
ctx->set_options(boost::asio::ssl::context::default_workarounds |
boost::asio::ssl::context::no_sslv2 |
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;
}
return ctx;
}
void ClientConnection::on_fail(websocketpp::connection_hdl hdl) {
client::connection_ptr con = c.get_con_from_hdl(hdl);
// Print as much information as possible
c.get_elog().write(websocketpp::log::elevel::warn,
"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");
}
void ClientConnection::on_open(websocketpp::connection_hdl hdl) {
}
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;
return;
}
// Pass the message to the gateway handler
gHandler->handle_data(message->get_payload(), c, hdl);
}
void ClientConnection::on_close(websocketpp::connection_hdl) {
std::cout << "Closed." << std::endl;
}

View File

@ -1,12 +1,9 @@
#include <websocketpp/config/asio_client.hpp>
#include "json/json.hpp"
#ifndef BOT_CLIENTCONNECTION
#define BOT_CLIENTCONNECTION
#include <websocketpp/client.hpp>
#include <iostream>
#include "ProtocolHandler.h"
#include <websocketpp/config/asio_client.hpp>
#include "json/json.hpp"
typedef websocketpp::client<websocketpp::config::asio_tls_client> client;
@ -17,118 +14,26 @@ 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;
class GatewayHandler;
class ClientConnection {
public:
typedef ClientConnection type;
ClientConnection();
ClientConnection() {
m_endpoint.clear_access_channels(websocketpp::log::alevel::all);
// Open a connection to the URI provided
void start(std::string uri);
m_endpoint.set_access_channels(websocketpp::log::alevel::app | websocketpp::log::alevel::connect);
m_endpoint.set_error_channels(websocketpp::log::elevel::all);
// Event handlers
void on_socket_init(websocketpp::connection_hdl);
context_ptr on_tls_init(websocketpp::connection_hdl);
void on_fail(websocketpp::connection_hdl hdl);
void on_open(websocketpp::connection_hdl hdl);
void on_message(websocketpp::connection_hdl &hdl, message_ptr message);
void on_close(websocketpp::connection_hdl);
// Initialize ASIO
m_endpoint.init_asio();
// Register our handlers
m_endpoint.set_socket_init_handler(bind(
&type::on_socket_init,
this,
websocketpp::lib::placeholders::_1
));
m_endpoint.set_tls_init_handler(bind<context_ptr>(
&type::on_tls_init,
this,
websocketpp::lib::placeholders::_1
));
m_endpoint.set_message_handler(bind(
&type::on_message,
this,
websocketpp::lib::placeholders::_1,
websocketpp::lib::placeholders::_2
));
m_endpoint.set_open_handler(bind(
&type::on_open,
this,
websocketpp::lib::placeholders::_1
));
m_endpoint.set_close_handler(bind(
&type::on_close,
this,
websocketpp::lib::placeholders::_1
));
m_endpoint.set_fail_handler(bind(
&type::on_fail,
this,
websocketpp::lib::placeholders::_1
));
}
void start(std::string uri) {
websocketpp::lib::error_code ec;
client::connection_ptr con = m_endpoint.get_connection(uri, ec);
if (ec) {
m_endpoint.get_alog().write(websocketpp::log::alevel::app, ec.message());
return;
}
m_endpoint.connect(con);
m_endpoint.run();
}
void on_socket_init(websocketpp::connection_hdl) {
m_endpoint.get_alog().write(websocketpp::log::alevel::app,
"Socket intialised.");
}
context_ptr on_tls_init(websocketpp::connection_hdl) {
context_ptr ctx = std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23);
try {
ctx->set_options(boost::asio::ssl::context::default_workarounds |
boost::asio::ssl::context::no_sslv2 |
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;
}
return ctx;
}
void on_fail(websocketpp::connection_hdl hdl) {
client::connection_ptr con = m_endpoint.get_con_from_hdl(hdl);
m_endpoint.get_elog().write(websocketpp::log::elevel::warn,
"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");
}
void on_open(websocketpp::connection_hdl &hdl) {
//ProtocolHandler::handle_data();
//pHandler.handle_data("jjj", m_endpoint, hdl);
}
void on_message(websocketpp::connection_hdl &hdl, message_ptr message) {
if (message->get_opcode() != websocketpp::frame::opcode::text) {
std::cout << "<< " << websocketpp::utility::to_hex(message->get_payload()) << std::endl;
return;
}
pHandler.handle_data(message->get_payload(), m_endpoint, hdl);
}
void on_close(websocketpp::connection_hdl) {
std::cout << "Closed." << std::endl;
}
private:
client m_endpoint;
ProtocolHandler pHandler;
};
client c;
GatewayHandler *gHandler;
};
#endif

View File

@ -0,0 +1,175 @@
#include "GatewayHandler.hpp"
#include <boost/algorithm/string.hpp>
#include "APIHelper.hpp"
#include "data_structures/User.hpp"
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;
default:
std::cout << data << std::endl;
}
}
void GatewayHandler::heartbeat(websocketpp::lib::error_code const & ec, client *c, websocketpp::connection_hdl *hdl) {
json heartbeat = {
{ "op", 1 },
{ "d", last_seq }
};
c->send(*hdl, heartbeat.dump(), websocketpp::frame::opcode::text);
c->set_timer(heartbeat_interval, websocketpp::lib::bind(
&GatewayHandler::heartbeat,
this,
websocketpp::lib::placeholders::_1,
c,
hdl
));
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((float)heartbeat_interval / 1000) + " seconds");
c.set_timer(heartbeat_interval, websocketpp::lib::bind(
&GatewayHandler::heartbeat,
this,
websocketpp::lib::placeholders::_1,
&c,
&hdl
));
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"];
c.get_alog().write(websocketpp::log::alevel::app, "Received event: " + event_name + " (new seq value: " + std::to_string(last_seq) + ")");
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 + ")");
c.get_alog().write(websocketpp::log::alevel::app, data.dump(4));
}
else if (event_name == "GUILD_CREATE") {
std::string guild_id = data["id"];
guilds[guild_id] = std::make_unique<DiscordObjects::Guild>(data);
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_unique<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]));
}
c.get_alog().write(websocketpp::log::alevel::app, data.dump(4));
}
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"]);
c.get_alog().write(websocketpp::log::alevel::app, "Message received: " + message + " $" + channel->name + " ^" + channel->id);
std::vector<std::string> words;
boost::split(words, message, boost::is_any_of(" "));
if (games.find(channel->id) != games.end()) { // message received in channel with ongoing game
games[channel->id]->handle_answer(message, sender);
} else if (words[0] == "`trivia" || words[0] == "`t") {
int questions = 10;
if (words.size() == 2) {
try {
questions = std::stoi(words[1]);
} catch (std::invalid_argument e) {
ah->send_message(channel->id, ":exclamation: Invalid arguments!");
}
} else if (words.size() > 2) {
ah->send_message(channel->id, ":exclamation: Invalid arguments!");
}
games[channel->id] = std::make_unique<TriviaGame>(this, ah, channel->id, questions);
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);
}
c.get_alog().write(websocketpp::log::alevel::app, data.dump(2));
}
//c.get_alog().write(websocketpp::log::alevel::app, decoded.dump(2));
}
void GatewayHandler::identify(client &c, websocketpp::connection_hdl &hdl) {
json identify = {
{ "op", 2 },
{ "d",{
{ "token", 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()) {
// remove from map
games.erase(it);
} else {
std::cerr << "Tried to delete a game that didn't exist.";
}
}

View File

@ -0,0 +1,76 @@
#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(websocketpp::lib::error_code const & ec, client *c, websocketpp::connection_hdl *hdl);
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;
const std::string TOKEN = "MTk5NjU3MDk1MjU4MTc3NTM5.ClyBNQ.15qTa-XBKRtGNMMYeXCrU50GhWE";
// 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;
APIHelper *ah;
};
#endif

View File

@ -1,168 +0,0 @@
#include "json/json.hpp"
#include <websocketpp/client.hpp>
#include <iostream>
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 ProtocolHandler {
public:
ProtocolHandler() {
last_seq = 0;
}
void 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;
default:
std::cout << data << std::endl;
}
}
void heartbeat(websocketpp::lib::error_code const & ec, client *c, websocketpp::connection_hdl *hdl) {
json heartbeat = {
{ "op", 1 },
{ "d", last_seq }
};
c->send(*hdl, heartbeat.dump(), websocketpp::frame::opcode::text);
c->set_timer(heartbeat_interval, websocketpp::lib::bind(
&ProtocolHandler::heartbeat,
this,
websocketpp::lib::placeholders::_1,
c,
hdl
));
c->get_alog().write(websocketpp::log::alevel::app, "Sent heartbeat. (seq: " + std::to_string(last_seq) + ")");
}
void 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((float)heartbeat_interval / 1000) + " seconds");
c.set_timer(heartbeat_interval, websocketpp::lib::bind(
&ProtocolHandler::heartbeat,
this,
websocketpp::lib::placeholders::_1,
&c,
&hdl
));
identify(c, hdl);
}
void on_dispatch(json decoded, client &c, websocketpp::connection_hdl &hdl) {
last_seq = decoded["s"];
std::string event_name = decoded["t"];
json data = decoded["d"];
c.get_alog().write(websocketpp::log::alevel::app, "Received event: " + event_name + " (new seq value: " + std::to_string(last_seq) + ")");
if (event_name == "READY") {
user_object = data["user"];
std::string username = user_object["username"];
std::string discriminator = user_object["discriminator"];
c.get_alog().write(websocketpp::log::alevel::app, "Sign-on confirmed. (@" + username + "#" + discriminator + ")");
c.get_alog().write(websocketpp::log::alevel::app, data.dump(4));
}
else if (event_name == "GUILD_CREATE") {
std::string guild_name = data["name"];
std::string guild_id = data["id"];
guilds[guild_id] = data;
for (json &element : data["channels"]) {
if (element["type"] == "text"){
channels[element["id"]] = element;
}
}
c.get_alog().write(websocketpp::log::alevel::app, "Guild joined: " + guild_name);
}
else if (event_name == "TYPING_START") {}
else if (event_name == "MESSAGE_CREATE") {
std::string message = data["content"];
json channel = channels[data["channel_id"]];
std::string channel_name = channel["name"];
std::string channel_id = data["channel_id"];
c.get_alog().write(websocketpp::log::alevel::app, "Message received: " + message + " $" + channel_name + " ^" + channel_id);
if (message == "`trivia" || message == "`t") {
}
}
//c.get_alog().write(websocketpp::log::alevel::app, decoded.dump(2));
}
json create_message(json user, std::string channel_id) {
}
void 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.");
}
private:
int last_seq;
int heartbeat_interval;
const int protocol_version = 5;
const std::string bot_token = "MTk5NjU3MDk1MjU4MTc3NTM5.ClyBNQ.15qTa-XBKRtGNMMYeXCrU50GhWE";
std::map<std::string, json> guilds;
std::map<std::string, json> channels;
json user_object;
};

View File

@ -1,22 +0,0 @@
#include "ClientConnection.hpp"
int main(int argc, char* argv[]) {
std::string uri = "wss://gateway.discord.gg/?v=5&encoding=json";
//std::string uri = "wss://echo.websocket.org/";
try {
ClientConnection endpoint;
endpoint.start(uri);
}
catch (const std::exception & e) {
std::cout << e.what() << std::endl;
}
catch (websocketpp::lib::error_code e) {
std::cout << e.message() << std::endl;
}
catch (...) {
std::cout << "other exception" << std::endl;
}
std::getchar();
}

View File

@ -0,0 +1,29 @@
#include <curl/curl.h>
#include "ClientConnection.hpp"
int main() {
curl_global_init(CURL_GLOBAL_DEFAULT);
std::string uri = "wss://gateway.discord.gg/?v=5&encoding=json";
try {
ClientConnection endpoint;
endpoint.start(uri);
}
catch (const std::exception & e) {
std::cout << e.what() << std::endl;
}
catch (websocketpp::lib::error_code e) {
std::cout << e.message() << std::endl;
}
catch (...) {
std::cout << "other exception" << std::endl;
}
std::getchar();
curl_global_cleanup();
return 0;
}

View File

@ -0,0 +1,346 @@
#include "TriviaGame.hpp"
#include <cstdio>
#include <sstream>
#include <random>
#include <boost/algorithm/string.hpp>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/regex.hpp>
#include "GatewayHandler.hpp"
#include "APIHelper.hpp"
#include "data_structures/User.hpp"
TriviaGame::TriviaGame(GatewayHandler *gh, APIHelper *ah, std::string channel_id, int total_questions) {
this->gh = gh;
this->ah = ah;
this->channel_id = channel_id;
this->total_questions = total_questions;
questions_asked = 0;
}
TriviaGame::~TriviaGame() {
std::string message = ":red_circle: **(" + std::to_string(questions_asked) + "/" + std::to_string(total_questions) +
")** Game over! **Scores:**\n";
// convert map to pair vector
std::vector<std::pair<std::string, int>> pairs;
for (auto &s : scores) {
pairs.push_back(s);
}
// sort by score, highest->lowest
std::sort(pairs.begin(), pairs.end(), [=](std::pair<std::string, int>& a, std::pair<std::string, int>& b) {
return a.second > b.second;
});
for (auto &p : pairs) {
std::string average_time;
average_time = std::to_string(average_times[p.first] / 1000.0f);
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);
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;
}
std::string sql_in_list;
for (int i = 1; i <= scores.size(); i++) {
sql_in_list += "?,";
}
sql_in_list.pop_back(); // remove final comma
// prepare statement
sql = "SELECT * FROM TotalScores WHERE User IN(" + sql_in_list + ");";
sqlite3_stmt *stmt;
rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
if (rc != SQLITE_OK) {
std::cerr << "SQL error." << std::endl;
}
// insert arguments
for (int i = 0; i < scores.size(); i++) {
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;
break;
}
}
std::map<std::string, std::pair<int, int>> data;
rc = 0; // just in case
while (rc != SQLITE_DONE) {
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
std::string id = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
int total_score = sqlite3_column_int(stmt, 1);
int average_time = sqlite3_column_int(stmt, 2);
data[id] = std::pair<int, int>(total_score, average_time);
} else if (rc != SQLITE_DONE) {
sqlite3_finalize(stmt);
std::cerr << "SQLite error." << std::endl;
break;
}
}
sqlite3_finalize(stmt);
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 ";
for (auto &i : scores) {
if (data.find(i.first) == data.end()) {
sql += "(?, ?, ?),";
}
}
sql.pop_back(); // remove final comma
sql += ";";
rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
if (rc != SQLITE_OK) {
std::cerr << "SQL error." << std::endl;
}
int count = 1;
for (auto &i : scores) {
if (data.find(i.first) == data.end()) {
sqlite3_bind_text(stmt, count, i.first.c_str(), -1, (sqlite3_destructor_type) -1);
sqlite3_bind_int(stmt, count + 1, scores[i.first]);
sqlite3_bind_int(stmt, count + 2, average_times[i.first]);
count += 3;
}
}
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
for (int i = 0; i < data.size(); i++) {
update_sql += "UPDATE TotalScores SET TotalScore=?, AverageTime=? WHERE User=?;";
}
if (update_sql != "") {
std::cout << update_sql << std::endl;
rc = sqlite3_prepare_v2(db, update_sql.c_str(), -1, &stmt, 0);
if (rc != SQLITE_OK) {
std::cerr << "SQL error." << std::endl;
}
int index = 1;
for (auto &i : data) {
int total_answered = i.second.first + scores[i.first];
// (correct answers [t] * average time [t]) + (correct answers [c] * average time [c]) -- [c] is current game, [t] total
long total = (i.second.first * i.second.second) + (scores[i.first] * average_times[i.first]);
// total / correct answers [t+c] = new avg
int new_avg = total / total_answered;
rc = sqlite3_bind_int(stmt, index, total_answered);
rc = sqlite3_bind_int(stmt, index + 1, new_avg);
rc = sqlite3_bind_text(stmt, index + 2, i.first.c_str(), -1, (sqlite3_destructor_type) -1);
index += 3;
}
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
sqlite3_close(db);
}
void TriviaGame::end_game() {
gh->delete_game(channel_id);
}
void TriviaGame::start() {
question();
}
void TriviaGame::question() {
sqlite3 *db; int rc; char *sql;
/// open db
rc = sqlite3_open("bot/db/trivia.db", &db);
if (rc) {
std::cerr << "Cant't open database: " << sqlite3_errmsg(db) << std::endl;
}
// prepare statement
sqlite3_stmt *stmt;
sql = "SELECT * FROM Questions ORDER BY RANDOM() LIMIT 1;";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
if (rc != SQLITE_OK) {
std::cerr << "SQL error." << std::endl;
}
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
// result received
std::string id = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)); // converts id to string for us
std::string category = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
std::string question = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
std::string answer = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
current_question = "#" + id + " [" + category + "] **" + question + "**";
boost::split(current_answers, boost::algorithm::to_lower_copy(answer), boost::is_any_of("*"));
} else if (rc != SQLITE_DONE) {
sqlite3_finalize(stmt);
std::cerr << "SQLite error." << std::endl;
}
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);
question_start = boost::posix_time::microsec_clock::universal_time();
current_thread = boost::shared_ptr<boost::thread>(new boost::thread(boost::bind(&TriviaGame::give_hint, this, 0, "")));
}
void TriviaGame::give_hint(int hints_given, std::string hint) {
boost::this_thread::sleep(boost::posix_time::seconds(10));
std::string answer = *current_answers.begin();
bool print = false;
if (hints_given == 0) {
hint = answer;
// probably shouldn't use regex here
boost::regex regexp("[a-zA-Z0-9]+?");
hint = boost::regex_replace(hint, regexp, std::string(1, hide_char));
print = true;
}
else {
std::stringstream hint_stream(hint);
std::random_device rd;
std::mt19937 rng(rd());
std::vector<std::string> hint_words, answer_words;
boost::split(hint_words, hint, boost::is_any_of(" "));
boost::split(answer_words, answer, boost::is_any_of(" "));
hint = "";
for (int i = 0; i < hint_words.size(); i++) {
std::string word = hint_words[i];
// count number of *s
int length = 0;
for (int i = 0; i < word.length(); i++) {
if (word[i] == hide_char) {
length++;
}
}
if (length > 1) {
std::uniform_int_distribution<int> uni(0, word.length() - 1);
bool replaced = false;
while (!replaced) {
int replace_index = uni(rng);
if (word[replace_index] == hide_char) {
word[replace_index] = answer_words[i][replace_index];
print = true;
replaced = true;
}
}
}
hint += word + " ";
}
}
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 + "`**");
}
if (hints_given < 4) {
current_thread = boost::shared_ptr<boost::thread>(new boost::thread(boost::bind(&TriviaGame::give_hint, this, hints_given, hint)));
} else {
current_thread = boost::shared_ptr<boost::thread>(new boost::thread(boost::bind(&TriviaGame::question_failed, this)));
}
}
void TriviaGame::question_failed() {
boost::this_thread::sleep(boost::posix_time::seconds(10));
ah->send_message(channel_id, ":exclamation: Question failed. Answer: ** `" + *current_answers.begin() + "` **");
if (questions_asked < 10) {
question();
} else {
end_game();
}
}
void TriviaGame::handle_answer(std::string answer, DiscordObjects::User sender) {
boost::algorithm::to_lower(answer);
if (current_answers.find(answer) != current_answers.end()) {
current_thread->interrupt();
boost::posix_time::time_duration diff = boost::posix_time::microsec_clock::universal_time() - question_start;
std::string time_taken = std::to_string(diff.total_milliseconds() / 1000.0f);
// 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)");
increase_score(sender.id);
update_average_time(sender.id, diff.total_milliseconds());
if (questions_asked < 10) {
question();
} else {
end_game();
}
} else if (answer == "`s" || answer == "`stop") {
current_thread->interrupt();
end_game();
}
}
void TriviaGame::increase_score(std::string user_id) {
if (scores.find(user_id) == scores.end()) {
// user entry not found, add one
scores[user_id] = 1;
}
else {
scores[user_id]++;
}
}
void TriviaGame::update_average_time(std::string user_id, int time) {
if (average_times.find(user_id) == average_times.end()) {
// user entry not found, add one
average_times[user_id] = time;
}
else {
int questions_answered = scores[user_id];
// questions_answered was updated before this, -1 to get to previous value for avg calc
int total = average_times[user_id] * (questions_answered - 1);
total += time;
// yeah it probably loses accuracy here, doesn't really matter
average_times[user_id] = (int) (total / questions_answered);
}
}

View File

@ -0,0 +1,58 @@
#ifndef BOT_QUESTIONHELPER
#define BOT_QUESTIONHELPER
#include <iostream>
#include <map>
#include <string>
#include <set>
#include <sqlite3.h>
#include <boost/thread.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
class GatewayHandler;
class APIHelper;
namespace DiscordObjects {
class User;
}
class TriviaGame {
public:
TriviaGame(GatewayHandler *gh, APIHelper *ah, std::string channel_id, int total_questions);
~TriviaGame();
void start();
void handle_answer(std::string answer, DiscordObjects::User sender);
private:
int questions_asked;
int total_questions;
void end_game();
void question();
void give_hint(int hints_given, std::string hint);
void question_failed();
void increase_score(std::string user_id);
void update_average_time(std::string user_id, int time);
std::string channel_id;
GatewayHandler *gh;
APIHelper *ah;
const char hide_char = '#';
std::string current_question;
std::set<std::string> current_answers;
// <user_id, score>
std::map<std::string, int> scores;
// <user_id, average_time>
std::map<std::string, int> average_times;
boost::shared_ptr<boost::thread> current_thread;
boost::posix_time::ptime question_start;
};
#endif

View File

@ -0,0 +1,81 @@
#ifndef BOT_DATA__STRUCTURES_CHANNEL
#define BOT_DATA__STRUCTURES_CHANNEL
#include <string>
#include "../json/json.hpp"
using json = nlohmann::json;
namespace DiscordObjects {
/*
=============================================================== CHANNEL OBJECT ==============================================================
|Field |Type |Description |Present |
|-----------------------|---------------|---------------------------------------------------------------------------------------|-----------|
|id |snowflake |the id of this channel (will be equal to the guild if it's the "general" channel) |Always |
|guild_id |snowflake |the id of the guild |Always |
|name |string |the name of the channel (2-100 characters) |Always |
|type |string |"text" or "voice" |Always |
|position |integer |the ordering position of the channel |Always |
|is_private |bool |should always be false for guild channels |Always |
|permission_overwrites |array |an array of overwrite objects |Always |
|topic |string |the channel topic (0-1024 characters) |Text only |
|last_message_id |snowflake |the id of the last message sent in this channel |Text only |
|bitrate |integer |the bitrate (in bits) of the voice channel |Voice only |
|user_limit |integer |the user limit of the voice channel |Voice only |
---------------------------------------------------------------------------------------------------------------------------------------------
*/
class Channel {
public:
Channel();
Channel(json data);
void load_from_json(json data);
bool operator==(Channel rhs);
std::string id;
std::string guild_id;
std::string name;
std::string type;
int position;
bool is_private;
// TODO: Implement permission overwrites
// std::vector<Permission_Overwrite> permission_overwrites;
std::string topic;
std::string last_message_id;
int bitrate;
int user_limit;
};
inline Channel::Channel() {
id = guild_id = name = topic = last_message_id = "null";
position = bitrate = user_limit = -1;
is_private = false;
type = "text";
}
inline Channel::Channel(json data) {
load_from_json(data);
}
inline void Channel::load_from_json(json data) {
id = data.value("id", "null");
guild_id = data.value("guild_id", "null");
name = data.value("name", "null");
type = data.value("type", "text");
position = data.value("position", -1);
is_private = data.value("is_private", false);
topic = data.value("topic", "null");
last_message_id = data.value("last_message_id", "null");
bitrate = data.value("bitrate", -1);
user_limit = data.value("user_limit", -1);
}
inline bool Channel::operator==(Channel rhs) {
return id == rhs.id && id != "null";
}
}
#endif

View File

@ -0,0 +1,105 @@
#ifndef BOT_DATA__STRUCTURES_Guild
#define BOT_DATA__STRUCTURES_Guild
#include <string>
#include <memory>
#include "../json/json.hpp"
#include "Channel.hpp"
#include "User.hpp"
using json = nlohmann::json;
namespace DiscordObjects {
/*
=================================== GUILD OBJECT ====================================
|Field |Type |Description |
|-------------------|---------------|-----------------------------------------------|
|id |snowflake |guild id |
|name |string |guild name (2-100 characters) |
|icon |string |icon hash |
|splash |string |splash hash |
|owner_id |snowflake |id of owner |
|region |string |{voice_region.id} |
|afk_channel_id |snowflake |id of afk channel |
|afk_timeout |integer |afk timeout in seconds |
|embed_enabled |bool |is this guild embeddable (e.g. widget) |
|embed_channel_id |snowflake |id of embedded channel |
|verification_level |integer |level of verification |
|voice_states |array |array of voice state objects (w/o guild_id) |
|roles |array |array of role objects |
|emojis |array |array of emoji objects |
|features |array |array of guild features |
-------------------------------------------------------------------------------------
Custom fields:
------------------------------------------------------------------------------------
|Field |Type |Description |
|-------------------|---------------|-----------------------------------------------|
|channels |array |array of channel object ptrs |
|users |array |array of user objects ptrs |
-------------------------------------------------------------------------------------
*/
class Guild {
public:
Guild();
Guild(json data);
void load_from_json(json data);
bool operator==(Guild rhs);
std::string id;
std::string name;
std::string icon;
std::string splash;
std::string owner_id;
std::string region;
std::string afk_channel_id;
int afk_timeout;
// bool embed_enabled;
// std::string embed_channel_id;
int verification_level;
// TODO: Implement all guil fields
// std::vector<?> voice_states
// std::vector<?> roles
// std::vector<?> emojis
// std::vector<?> features
std::vector<std::shared_ptr<Channel>> channels;
//std::vector<std::unique_ptr<DiscordObjects::User>> users;
};
inline Guild::Guild() {
id = name = icon = splash = owner_id = region = afk_channel_id = "null";
afk_timeout = verification_level = -1;
}
inline Guild::Guild(json data) {
load_from_json(data);
}
inline void Guild::load_from_json(json data) {
Guild();
std::cout << data.dump(4) << std::endl;
id = data.value("id", "null");
name = data.value("name", "null");
icon = data.value("icon", "null");
splash = data.value("spash", "null");
owner_id = data.value("owner_id", "null");
region = data.value("region", "null");
afk_channel_id = data.value("afk_channel_id", "null");
afk_timeout = data.value("afk_timeout", -1);
verification_level = data.value("verification_level", -1);
}
inline bool Guild::operator==(Guild rhs) {
return id == rhs.id && id != "null";
}
}
#endif

View File

@ -0,0 +1,125 @@
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": []
}

View File

@ -0,0 +1,65 @@
#ifndef BOT_DATA__STRUCTURES_USER
#define BOT_DATA__STRUCTURES_USER
#include <string>
#include "../json/json.hpp"
using json = nlohmann::json;
namespace DiscordObjects {
/*
==================================================== USER OBJECT =======================================================
Field Type Description Required OAuth2 Scope
------------------------------------------------------------------------------------------------------------------------
id snowflake the users id identify
username string the users username, not unique across the platform identify
discriminator string the users 4-digit discord-tag identify
avatar string the users avatar hash identify
bot bool whether the user belongs to a OAuth2 application identify
mfa_enabled bool whether the user has two factor enabled on their account identify
verified bool whether the email on this account has been verified email
email string the users email email
*/
class User {
public:
User();
User(json data);
void load_from_json(json data);
bool operator==(User rhs);
std::string id;
std::string username;
std::string discriminator;
std::string avatar;
bool bot;
bool mfa_enabled;
};
inline User::User() {
id = username = discriminator = avatar = "null";
bot = mfa_enabled = false;
}
inline User::User(json data) {
load_from_json(data);
}
inline void User::load_from_json(json data) {
id = data.value("id", "null");
username = data.value("username", "null");
discriminator = data.value("discriminator", "null");
avatar = data.value("avatar", "null");
bot = data.value("bot", false);
mfa_enabled = data.value("mfa_enabled", false);
}
inline bool User::operator==(User rhs) {
return id == rhs.id && id != "null";
}
}
#endif

View File

@ -1,7 +1,8 @@
BEGIN TRANSACTION;
CREATE TABLE `TotalScores` (
CREATE TABLE "TotalScores" (
`User` TEXT UNIQUE,
`TotalScore` INTEGER,
`AverageTime` INTEGER,
PRIMARY KEY(User)
);
CREATE TABLE "Questions" (
@ -10,4 +11,4 @@ CREATE TABLE "Questions" (
`Question` TEXT,
`Answer` TEXT
);
COMMIT;
COMMIT;

View File

@ -1,44 +1,47 @@
#include "HTTPHelper.hpp"
size_t HTTPHelper::data_write(void* buf, size_t size, size_t nmemb, void* userp) {
if (userp) {
std::ostream& os = *static_cast<std::ostream*>(userp);
std::streamsize len = size * nmemb;
if (os.write(static_cast<char*>(buf), len)) {
return len;
}
}
/*
* Warning: (Awful) C Code
*/
return 0;
}
CURLcode HTTPHelper::curl_read(const std::string& url, std::ostream& os, long timeout = 30) {
CURLcode code(CURLE_FAILED_INIT);
CURL* curl = curl_easy_init();
std::string HTTPHelper::post_request(std::string url, std::string content_type, std::string data) {
CURL *curl;
CURLcode res;
std::string read_buffer;
struct curl_slist *headers = nullptr;
curl = curl_easy_init();
if (curl) {
if (CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &data_write))
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L))
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L))
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_FILE, &os))
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout))
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_URL, url.c_str()))) {
code = curl_easy_perform(curl);
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
// I wonder what the S in HTTPS stands for
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
std::string content_header = "Content-Type: " + content_type;
headers = curl_slist_append(headers, content_header.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) {
return "ERROR";
}
/* always cleanup */
curl_easy_cleanup(curl);
curl_slist_free_all(headers);
}
return code;
return read_buffer;
}
std::string HTTPHelper::read_url(std::string url) {
std::ostringstream oss;
std::string html = "ERROR";
if (CURLE_OK == curl_read("http://www.google.co.uk/", oss)) {
html = oss.str();
}
else {
std::cout << "CURL error" << std::endl;
}
return html;
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;
}

View File

@ -1,14 +1,16 @@
#include <curl/curl.h>
#include <fstream>
#include <sstream>
#ifndef BOT_HTTP_HTTPHELPER
#define BOT_HTTP_HTTPHELPER
#include <iostream>
// callback function writes data to a std::ostream
#include <curl/curl.h>
class HTTPHelper {
public:
static std::string read_url(std::string url);
std::string post_request(std::string url, std::string content_type, std::string data);
private:
static size_t data_write(void* buf, size_t size, size_t nmemb, void* userp);
static CURLcode curl_read(const std::string& url, std::ostream& os, long timeout);
};
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp);
};
#endif

View File

@ -3650,7 +3650,15 @@ class basic_json
const auto it = find(key);
if (it != end())
{
return *it;
try
{
return *it;
}
catch (...)
{
// if some kind of exception occurred, return default value
return default_value;
}
}
else
{

View File

@ -0,0 +1,87 @@
#include <sqlite3.h>
#include <cstdio>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
/**
/ Questions obtained from https://sourceforge.net/projects/triviadb/
/ Questions are split into 14 files - b[01-14].txt
/
/ Format:
/ [CATEGORY: ]QUESTION*ANSWER1[*ANSWER2 ...]
/ where things in [] are not always present
/
/ 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);
if (rc) {
std::cerr << "Can't open database: " << sqlite3_errmsg(db) << std::endl;
return 1;
}
else {
std::cout << "Opened database successfully" << std::endl;
}
for (int i = 1; i <= 14; i++) {
std::stringstream ss;
ss.fill('0');
ss.width(2);
ss << i;
std::ifstream file("data_management/questions/b" + ss.str() + ".txt");
std::string str;
std::string file_contents;
while (std::getline(file, str)) {
std::string category, question, answer;
bool strange_question = true;
category = "Uncategorised";
if (str.find(':') != std::string::npos) {
category = str.substr(0, str.find(':'));
str = str.substr(str.find(':') + 2); // account for space after :
strange_question = false;
}
question = str.substr(0, str.find('*'));
answer = str.substr(str.find('*') + 1);
sqlite3_stmt *insert_question;
std::string sql = "INSERT INTO Questions (Category, Question, Answer) VALUES (?1, ?2, ?3);";
sqlite3_prepare_v2(db, sql.c_str(), -1, &insert_question, NULL);
sqlite3_bind_text(insert_question, 1, category.c_str(), -1, ((sqlite3_destructor_type)-1));
sqlite3_bind_text(insert_question, 2, question.c_str(), -1, ((sqlite3_destructor_type)-1));
sqlite3_bind_text(insert_question, 3, answer.c_str(), -1, ((sqlite3_destructor_type)-1));
int result = sqlite3_step(insert_question);
std::cout << (result == 101 ? "OK" : std::to_string(result)) << " ";
}
file.close();
}
std::cout << std::endl;
sqlite3_close(db);
std::getchar();
return 0;
}

Binary file not shown.