Bot is now basically functional
Big tidy up, new question source.
This commit is contained in:
parent
5168a38702
commit
b31eaef6d6
2
.gitignore
vendored
2
.gitignore
vendored
@ -246,3 +246,5 @@ ModelManifest.xml
|
|||||||
|
|
||||||
# FAKE - F# Make
|
# FAKE - F# Make
|
||||||
.fake/
|
.fake/
|
||||||
|
/TriviaBot/data_management/questions
|
||||||
|
/TriviaBot/bot/db/trivia.db
|
||||||
|
@ -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.
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
1258
TriviaBot/CSV/csv.h
1258
TriviaBot/CSV/csv.h
File diff suppressed because it is too large
Load Diff
15236
TriviaBot/CSV/trivia.csv
15236
TriviaBot/CSV/trivia.csv
File diff suppressed because it is too large
Load Diff
@ -21,7 +21,7 @@
|
|||||||
<PropertyGroup Label="Globals">
|
<PropertyGroup Label="Globals">
|
||||||
<ProjectGuid>{E400396F-0C05-413E-B83E-1A4F41D86442}</ProjectGuid>
|
<ProjectGuid>{E400396F-0C05-413E-B83E-1A4F41D86442}</ProjectGuid>
|
||||||
<RootNamespace>TriviaBot</RootNamespace>
|
<RootNamespace>TriviaBot</RootNamespace>
|
||||||
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
|
<WindowsTargetPlatformVersion>10.0.10586.0</WindowsTargetPlatformVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||||
@ -91,9 +91,10 @@
|
|||||||
<WarningLevel>Level3</WarningLevel>
|
<WarningLevel>Level3</WarningLevel>
|
||||||
<Optimization>Disabled</Optimization>
|
<Optimization>Disabled</Optimization>
|
||||||
<SDLCheck>true</SDLCheck>
|
<SDLCheck>true</SDLCheck>
|
||||||
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;CURL_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<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\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>
|
<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 %(AdditionalOptions)</AdditionalOptions>
|
<AdditionalOptions>-D_SCL_SECURE_NO_WARNINGS -DUSE_SYSTEM_CURL=OFF -D_WIN32_WINNT=0x0A00 %(AdditionalOptions)</AdditionalOptions>
|
||||||
|
<ObjectFileName>$(IntDir)/%(RelativeDir)/</ObjectFileName>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<AdditionalDependencies>libcurl.lib;sqlite3.lib;libeay32.lib;ssleay32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
<AdditionalDependencies>libcurl.lib;sqlite3.lib;libeay32.lib;ssleay32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
@ -106,9 +107,10 @@
|
|||||||
<WarningLevel>Level4</WarningLevel>
|
<WarningLevel>Level4</WarningLevel>
|
||||||
<Optimization>Disabled</Optimization>
|
<Optimization>Disabled</Optimization>
|
||||||
<SDLCheck>true</SDLCheck>
|
<SDLCheck>true</SDLCheck>
|
||||||
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;CURL_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<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\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>
|
<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 %(AdditionalOptions)</AdditionalOptions>
|
<AdditionalOptions>-D_SCL_SECURE_NO_WARNINGS -DUSE_SYSTEM_CURL=OFF -D_WIN32_WINNT=0x0A00 %(AdditionalOptions)</AdditionalOptions>
|
||||||
|
<ObjectFileName>$(IntDir)/%(RelativeDir)/</ObjectFileName>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<AdditionalDependencies>libcurl.lib;sqlite3.lib;libeay32.lib;ssleay32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
<AdditionalDependencies>libcurl.lib;sqlite3.lib;libeay32.lib;ssleay32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
@ -153,27 +155,38 @@
|
|||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="bot\ClientConnection.hpp" />
|
<ClCompile Include="..\lib\cpr\cpr\auth.cpp" />
|
||||||
<ClCompile Include="bot\HTTP\HTTPHelper.cpp" />
|
<ClCompile Include="..\lib\cpr\cpr\cookies.cpp" />
|
||||||
<ClCompile Include="bot\Source.cpp">
|
<ClCompile Include="..\lib\cpr\cpr\cprtypes.cpp" />
|
||||||
<AdditionalOptions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">-D_SCL_SECURE_NO_WARNINGS
|
<ClCompile Include="..\lib\cpr\cpr\digest.cpp" />
|
||||||
%(AdditionalOptions)</AdditionalOptions>
|
<ClCompile Include="..\lib\cpr\cpr\error.cpp" />
|
||||||
<AdditionalOptions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">-D_SCL_SECURE_NO_WARNINGS
|
<ClCompile Include="..\lib\cpr\cpr\multipart.cpp" />
|
||||||
%(AdditionalOptions)</AdditionalOptions>
|
<ClCompile Include="..\lib\cpr\cpr\parameters.cpp" />
|
||||||
</ClCompile>
|
<ClCompile Include="..\lib\cpr\cpr\payload.cpp" />
|
||||||
<ClCompile Include="bot\TriviaBot.cpp">
|
<ClCompile Include="..\lib\cpr\cpr\proxies.cpp" />
|
||||||
<AdditionalOptions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">-D_SCL_SECURE_NO_WARNINGS %(AdditionalOptions)</AdditionalOptions>
|
<ClCompile Include="..\lib\cpr\cpr\session.cpp" />
|
||||||
</ClCompile>
|
<ClCompile Include="..\lib\cpr\cpr\util.cpp" />
|
||||||
<ClCompile Include="CSV\LoadDB.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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\..\..\..\..\AppData\Local\Temp\Rar$DRa0.635\fast-cpp-csv-parser-master\LICENSE" />
|
<ClInclude Include="bot\APIHelper.hpp" />
|
||||||
</ItemGroup>
|
<ClInclude Include="bot\data_structures\Channel.hpp" />
|
||||||
<ItemGroup>
|
<ClInclude Include="bot\data_structures\Guild.hpp" />
|
||||||
<ClInclude Include="bot\json\json.hpp" />
|
<ClInclude Include="bot\data_structures\User.hpp" />
|
||||||
<ClInclude Include="bot\ProtocolHandler.h" />
|
<ClInclude Include="bot\GatewayHandler.hpp" />
|
||||||
<ClInclude Include="bot\HTTP\HTTPHelper.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>
|
</ItemGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
@ -15,37 +15,91 @@
|
|||||||
</Filter>
|
</Filter>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<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">
|
<ClInclude Include="bot\json\json.hpp">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</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>
|
||||||
<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>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="bot\TriviaBot.cpp">
|
<ClCompile Include="bot\TriviaBot.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="bot\Source.cpp">
|
</ItemGroup>
|
||||||
<Filter>Source Files</Filter>
|
<ItemGroup>
|
||||||
</ClCompile>
|
<Text Include="bot\data_structures\Text.txt" />
|
||||||
<ClCompile Include="bot\HTTPHelper.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="bot\ClientConnection.hpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
19
TriviaBot/bot/APIHelper.cpp
Normal file
19
TriviaBot/bot/APIHelper.cpp
Normal 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;
|
||||||
|
}
|
28
TriviaBot/bot/APIHelper.hpp
Normal file
28
TriviaBot/bot/APIHelper.hpp
Normal 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
|
120
TriviaBot/bot/ClientConnection.cpp
Normal file
120
TriviaBot/bot/ClientConnection.cpp
Normal 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;
|
||||||
|
}
|
@ -1,12 +1,9 @@
|
|||||||
#include <websocketpp/config/asio_client.hpp>
|
#ifndef BOT_CLIENTCONNECTION
|
||||||
|
#define BOT_CLIENTCONNECTION
|
||||||
#include "json/json.hpp"
|
|
||||||
|
|
||||||
#include <websocketpp/client.hpp>
|
#include <websocketpp/client.hpp>
|
||||||
|
#include <websocketpp/config/asio_client.hpp>
|
||||||
#include <iostream>
|
#include "json/json.hpp"
|
||||||
|
|
||||||
#include "ProtocolHandler.h"
|
|
||||||
|
|
||||||
typedef websocketpp::client<websocketpp::config::asio_tls_client> client;
|
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 websocketpp::lib::shared_ptr<boost::asio::ssl::context> context_ptr;
|
||||||
typedef client::connection_ptr connection_ptr;
|
typedef client::connection_ptr connection_ptr;
|
||||||
|
|
||||||
|
class GatewayHandler;
|
||||||
|
|
||||||
class ClientConnection {
|
class ClientConnection {
|
||||||
public:
|
public:
|
||||||
typedef ClientConnection type;
|
ClientConnection();
|
||||||
|
|
||||||
ClientConnection() {
|
// Open a connection to the URI provided
|
||||||
m_endpoint.clear_access_channels(websocketpp::log::alevel::all);
|
void start(std::string uri);
|
||||||
|
|
||||||
m_endpoint.set_access_channels(websocketpp::log::alevel::app | websocketpp::log::alevel::connect);
|
// Event handlers
|
||||||
m_endpoint.set_error_channels(websocketpp::log::elevel::all);
|
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:
|
private:
|
||||||
client m_endpoint;
|
client c;
|
||||||
ProtocolHandler pHandler;
|
GatewayHandler *gHandler;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif
|
175
TriviaBot/bot/GatewayHandler.cpp
Normal file
175
TriviaBot/bot/GatewayHandler.cpp
Normal 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.";
|
||||||
|
}
|
||||||
|
}
|
76
TriviaBot/bot/GatewayHandler.hpp
Normal file
76
TriviaBot/bot/GatewayHandler.hpp
Normal 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
|
@ -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;
|
|
||||||
};
|
|
@ -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();
|
|
||||||
}
|
|
@ -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;
|
||||||
|
}
|
346
TriviaBot/bot/TriviaGame.cpp
Normal file
346
TriviaBot/bot/TriviaGame.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
58
TriviaBot/bot/TriviaGame.hpp
Normal file
58
TriviaBot/bot/TriviaGame.hpp
Normal 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
|
81
TriviaBot/bot/data_structures/Channel.hpp
Normal file
81
TriviaBot/bot/data_structures/Channel.hpp
Normal 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
|
105
TriviaBot/bot/data_structures/Guild.hpp
Normal file
105
TriviaBot/bot/data_structures/Guild.hpp
Normal 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
|
125
TriviaBot/bot/data_structures/Text.txt
Normal file
125
TriviaBot/bot/data_structures/Text.txt
Normal 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": []
|
||||||
|
}
|
||||||
|
|
65
TriviaBot/bot/data_structures/User.hpp
Normal file
65
TriviaBot/bot/data_structures/User.hpp
Normal 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
|
@ -1,7 +1,8 @@
|
|||||||
BEGIN TRANSACTION;
|
BEGIN TRANSACTION;
|
||||||
CREATE TABLE `TotalScores` (
|
CREATE TABLE "TotalScores" (
|
||||||
`User` TEXT UNIQUE,
|
`User` TEXT UNIQUE,
|
||||||
`TotalScore` INTEGER,
|
`TotalScore` INTEGER,
|
||||||
|
`AverageTime` INTEGER,
|
||||||
PRIMARY KEY(User)
|
PRIMARY KEY(User)
|
||||||
);
|
);
|
||||||
CREATE TABLE "Questions" (
|
CREATE TABLE "Questions" (
|
@ -1,44 +1,47 @@
|
|||||||
#include "HTTPHelper.hpp"
|
#include "HTTPHelper.hpp"
|
||||||
|
|
||||||
size_t HTTPHelper::data_write(void* buf, size_t size, size_t nmemb, void* userp) {
|
/*
|
||||||
if (userp) {
|
* Warning: (Awful) C Code
|
||||||
std::ostream& os = *static_cast<std::ostream*>(userp);
|
*/
|
||||||
std::streamsize len = size * nmemb;
|
|
||||||
if (os.write(static_cast<char*>(buf), len)) {
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
std::string HTTPHelper::post_request(std::string url, std::string content_type, std::string data) {
|
||||||
}
|
CURL *curl;
|
||||||
|
CURLcode res;
|
||||||
CURLcode HTTPHelper::curl_read(const std::string& url, std::ostream& os, long timeout = 30) {
|
std::string read_buffer;
|
||||||
CURLcode code(CURLE_FAILED_INIT);
|
struct curl_slist *headers = nullptr;
|
||||||
CURL* curl = curl_easy_init();
|
|
||||||
|
|
||||||
|
curl = curl_easy_init();
|
||||||
if (curl) {
|
if (curl) {
|
||||||
if (CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &data_write))
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||||
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L))
|
|
||||||
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L))
|
// I wonder what the S in HTTPS stands for
|
||||||
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_FILE, &os))
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||||
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout))
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||||
&& CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_URL, url.c_str()))) {
|
|
||||||
code = curl_easy_perform(curl);
|
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_easy_cleanup(curl);
|
||||||
}
|
curl_slist_free_all(headers);
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
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,14 +1,16 @@
|
|||||||
#include <curl/curl.h>
|
#ifndef BOT_HTTP_HTTPHELPER
|
||||||
#include <fstream>
|
#define BOT_HTTP_HTTPHELPER
|
||||||
#include <sstream>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
// callback function writes data to a std::ostream
|
#include <curl/curl.h>
|
||||||
|
|
||||||
class HTTPHelper {
|
class HTTPHelper {
|
||||||
public:
|
public:
|
||||||
static std::string read_url(std::string url);
|
std::string post_request(std::string url, std::string content_type, std::string data);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static size_t data_write(void* buf, size_t size, size_t nmemb, void* userp);
|
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp);
|
||||||
static CURLcode curl_read(const std::string& url, std::ostream& os, long timeout);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -3650,7 +3650,15 @@ class basic_json
|
|||||||
const auto it = find(key);
|
const auto it = find(key);
|
||||||
if (it != end())
|
if (it != end())
|
||||||
{
|
{
|
||||||
return *it;
|
try
|
||||||
|
{
|
||||||
|
return *it;
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
// if some kind of exception occurred, return default value
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
87
TriviaBot/data_management/LoadDB.cpp
Normal file
87
TriviaBot/data_management/LoadDB.cpp
Normal 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.
Loading…
Reference in New Issue
Block a user