Make bot token an argument, add trivia commands

This commit is contained in:
Jack Bond-Preston 2016-07-10 19:17:35 +01:00
parent 54de981fc6
commit c189265435
8 changed files with 103 additions and 73 deletions

View File

@ -4,7 +4,10 @@
#include "APIHelper.hpp" #include "APIHelper.hpp"
APIHelper::APIHelper() { extern std::string bot_token;
APIHelper::APIHelper() : BASE_URL("https://discordapp.com/api"), CHANNELS_URL(BASE_URL + "/channels"),
TOKEN_PARAM("token=" + bot_token), JSON_CTYPE("application/json") {
http = new HTTPHelper(); http = new HTTPHelper();
} }
@ -15,5 +18,5 @@ void APIHelper::send_message(std::string channel_id, std::string message) {
}; };
const std::string response = http->post_request(url, JSON_CTYPE, data.dump()); const std::string response = http->post_request(url, JSON_CTYPE, data.dump());
std::cout << response << std::endl; // TODO: verify success
} }

View File

@ -16,11 +16,11 @@ public:
void send_message(std::string channel_id, std::string message); void send_message(std::string channel_id, std::string message);
private: private:
const std::string BASE_URL = "https://discordapp.com/api"; const std::string BASE_URL;
const std::string CHANNELS_URL = BASE_URL + "/channels"; const std::string CHANNELS_URL;
const std::string TOKEN = "MTk5NjU3MDk1MjU4MTc3NTM5.ClyBNQ.15qTa-XBKRtGNMMYeXCrU50GhWE"; const std::string TOKEN;
const std::string TOKEN_PARAM = "token=" + TOKEN; const std::string TOKEN_PARAM;
const std::string JSON_CTYPE = "application/json"; const std::string JSON_CTYPE;
HTTPHelper *http; HTTPHelper *http;
}; };

View File

@ -5,6 +5,8 @@
#include "APIHelper.hpp" #include "APIHelper.hpp"
#include "data_structures/User.hpp" #include "data_structures/User.hpp"
extern std::string bot_token;
GatewayHandler::GatewayHandler() { GatewayHandler::GatewayHandler() {
last_seq = 0; last_seq = 0;
@ -26,8 +28,6 @@ void GatewayHandler::handle_data(std::string data, client &c, websocketpp::conne
case 11: case 11:
c.get_alog().write(websocketpp::log::alevel::app, "Heartbeat acknowledged."); c.get_alog().write(websocketpp::log::alevel::app, "Heartbeat acknowledged.");
break; break;
default:
std::cout << data << std::endl;
} }
} }
@ -53,7 +53,7 @@ void GatewayHandler::heartbeat(websocketpp::lib::error_code const & ec, client *
void GatewayHandler::on_hello(json decoded, client &c, websocketpp::connection_hdl &hdl) { void GatewayHandler::on_hello(json decoded, client &c, websocketpp::connection_hdl &hdl) {
heartbeat_interval = decoded["d"]["heartbeat_interval"]; heartbeat_interval = decoded["d"]["heartbeat_interval"];
c.get_alog().write(websocketpp::log::alevel::app, "Heartbeat interval: " + std::to_string((float)heartbeat_interval / 1000) + " seconds"); c.get_alog().write(websocketpp::log::alevel::app, "Heartbeat interval: " + std::to_string(heartbeat_interval / 1000.0f) + " seconds");
c.set_timer(heartbeat_interval, websocketpp::lib::bind( c.set_timer(heartbeat_interval, websocketpp::lib::bind(
&GatewayHandler::heartbeat, &GatewayHandler::heartbeat,
@ -71,14 +71,10 @@ void GatewayHandler::on_dispatch(json decoded, client &c, websocketpp::connectio
std::string event_name = decoded["t"]; std::string event_name = decoded["t"];
json data = decoded["d"]; 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") { if (event_name == "READY") {
user_object.load_from_json(data["user"]); 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, "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") { else if (event_name == "GUILD_CREATE") {
std::string guild_id = data["id"]; std::string guild_id = data["id"];
@ -92,8 +88,6 @@ void GatewayHandler::on_dispatch(json decoded, client &c, websocketpp::connectio
// add ptr to said channel list to guild's channel list // 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])); 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 == "TYPING_START") {}
else if (event_name == "MESSAGE_CREATE") { else if (event_name == "MESSAGE_CREATE") {
@ -102,25 +96,47 @@ void GatewayHandler::on_dispatch(json decoded, client &c, websocketpp::connectio
DiscordObjects::User sender(data["author"]); 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; std::vector<std::string> words;
boost::split(words, message, boost::is_any_of(" ")); boost::split(words, message, boost::is_any_of(" "));
if (games.find(channel->id) != games.end()) { // message received in channel with ongoing game if (words[0] == "`trivia" || words[0] == "`t") {
games[channel->id]->handle_answer(message, sender);
} else if (words[0] == "`trivia" || words[0] == "`t") {
int questions = 10; int questions = 10;
if (words.size() == 2) { int delay = 8;
if (words.size() > 3) {
ah->send_message(channel->id, ":exclamation: Invalid arguments!");
return;
}
else if(words.size() > 1) {
if (words[1] == "help" || words[1] == "h") {
std::string help = "**Base command \\`t[rivia]**. Arguments:\n";
help += "\\`trivia **{x}** **{y}**: Makes the game last **x** number of questions, optionally sets the time interval between hints to **y** seconds\n";
help += "\\`trivia **stop**: stops the ongoing game.\n";
help += "\\`trivia **help**: prints this message\n";
ah->send_message(channel->id, help);
return;
}
else if (words[1] == "stop" || words[1] == "s") {
if (games.find(channel->id) != games.end()) {
delete_game(channel->id);
return;
}
}
else {
try { try {
questions = std::stoi(words[1]); questions = std::stoi(words[1]);
} catch (std::invalid_argument e) { if (words.size() == 3) {
ah->send_message(channel->id, ":exclamation: Invalid arguments!"); delay = std::stoi(words[2]);
} }
} else if (words.size() > 2) { }
catch (std::invalid_argument e) {
ah->send_message(channel->id, ":exclamation: Invalid arguments!"); ah->send_message(channel->id, ":exclamation: Invalid arguments!");
return;
}
}
} }
games[channel->id] = std::make_unique<TriviaGame>(this, ah, channel->id, questions); games[channel->id] = std::make_unique<TriviaGame>(this, ah, channel->id, questions, delay);
games[channel->id]->start(); games[channel->id]->start();
} }
else if (words[0] == "`channels") { else if (words[0] == "`channels") {
@ -130,24 +146,26 @@ void GatewayHandler::on_dispatch(json decoded, client &c, websocketpp::connectio
+ guilds[ch.second->guild_id]->name + " (" + ch.second->guild_id + ")\n"; + guilds[ch.second->guild_id]->name + " (" + ch.second->guild_id + ")\n";
} }
ah->send_message(channel->id, m); ah->send_message(channel->id, m);
} else if (words[0] == "`guilds") { }
else if (words[0] == "`guilds") {
std::string m = "Guild List:\n"; std::string m = "Guild List:\n";
for (auto &gu : guilds) { for (auto &gu : guilds) {
m += "> " + gu.second->name + " (" + gu.second->id + ") Channels: " + std::to_string(gu.second->channels.size()) + "\n"; m += "> " + gu.second->name + " (" + gu.second->id + ") Channels: " + std::to_string(gu.second->channels.size()) + "\n";
} }
ah->send_message(channel->id, m); ah->send_message(channel->id, m);
} }
c.get_alog().write(websocketpp::log::alevel::app, data.dump(2)); else if (games.find(channel->id) != games.end()) { // message received in channel with ongoing game
games[channel->id]->handle_answer(message, sender);
}
} }
//c.get_alog().write(websocketpp::log::alevel::app, decoded.dump(2));
} }
void GatewayHandler::identify(client &c, websocketpp::connection_hdl &hdl) { void GatewayHandler::identify(client &c, websocketpp::connection_hdl &hdl) {
json identify = { json identify = {
{ "op", 2 }, { "op", 2 },
{ "d",{ { "d", {
{ "token", TOKEN }, { "token", bot_token },
{ "properties",{ { "properties", {
{ "$browser", "Microsoft Windows 10" }, { "$browser", "Microsoft Windows 10" },
{ "$device", "TriviaBot-0.0" }, { "$device", "TriviaBot-0.0" },
{ "$referrer", "" }, { "$referrer", "" },
@ -155,18 +173,19 @@ void GatewayHandler::identify(client &c, websocketpp::connection_hdl &hdl) {
} }, } },
{ "compress", false }, { "compress", false },
{ "large_threshold", 250 }, { "large_threshold", 250 },
{ "shard",{ 0, 1 } } { "shard", { 0, 1 } }
} } } }
}; };
c.send(hdl, identify.dump(), websocketpp::frame::opcode::text); c.send(hdl, identify.dump(), websocketpp::frame::opcode::text);
c.get_alog().write(websocketpp::log::alevel::app, "Sent identify payload."); c.get_alog().write(websocketpp::log::alevel::app, "Sent identify payload. Token: " + bot_token);
} }
void GatewayHandler::delete_game(std::string channel_id) { void GatewayHandler::delete_game(std::string channel_id) {
auto it = games.find(channel_id); auto it = games.find(channel_id);
if (it != games.end()) { if (it != games.end()) {
it->second->interrupt();
// remove from map // remove from map
games.erase(it); games.erase(it);
} else { } else {

View File

@ -57,7 +57,6 @@ private:
int heartbeat_interval; int heartbeat_interval;
const int protocol_version = 5; const int protocol_version = 5;
const std::string TOKEN = "MTk5NjU3MDk1MjU4MTc3NTM5.ClyBNQ.15qTa-XBKRtGNMMYeXCrU50GhWE";
// bot's user obj // bot's user obj
DiscordObjects::User user_object; DiscordObjects::User user_object;

View File

@ -2,23 +2,34 @@
#include "ClientConnection.hpp" #include "ClientConnection.hpp"
int main() { std::string bot_token;
int main(int argc, char *argv[]) {
curl_global_init(CURL_GLOBAL_DEFAULT); curl_global_init(CURL_GLOBAL_DEFAULT);
if (argc == 2) {
bot_token = argv[1];
}
else {
std::cout << "Please enter your bot token: " << std::endl;
std::cin >> bot_token;
}
// todo: get this using API
std::string uri = "wss://gateway.discord.gg/?v=5&encoding=json"; std::string uri = "wss://gateway.discord.gg/?v=5&encoding=json";
try { try {
ClientConnection endpoint; ClientConnection conn;
endpoint.start(uri); conn.start(uri);
} }
catch (const std::exception & e) { catch (const std::exception & e) {
std::cout << e.what() << std::endl; std::cerr << e.what() << std::endl;
} }
catch (websocketpp::lib::error_code e) { catch (websocketpp::lib::error_code e) {
std::cout << e.message() << std::endl; std::cerr << e.message() << std::endl;
} }
catch (...) { catch (...) {
std::cout << "other exception" << std::endl; std::cerr << "other exception" << std::endl;
} }
std::getchar(); std::getchar();

View File

@ -13,7 +13,7 @@
#include "APIHelper.hpp" #include "APIHelper.hpp"
#include "data_structures/User.hpp" #include "data_structures/User.hpp"
TriviaGame::TriviaGame(GatewayHandler *gh, APIHelper *ah, std::string channel_id, int total_questions) { TriviaGame::TriviaGame(GatewayHandler *gh, APIHelper *ah, std::string channel_id, int total_questions, int delay) : interval(delay) {
this->gh = gh; this->gh = gh;
this->ah = ah; this->ah = ah;
this->channel_id = channel_id; this->channel_id = channel_id;
@ -23,6 +23,11 @@ TriviaGame::TriviaGame(GatewayHandler *gh, APIHelper *ah, std::string channel_id
} }
TriviaGame::~TriviaGame() { TriviaGame::~TriviaGame() {
if (scores.size() == 0) {
ah->send_message(channel_id, ":red_circle: Game cancelled!");
return;
}
std::string message = ":red_circle: **(" + std::to_string(questions_asked) + "/" + std::to_string(total_questions) + std::string message = ":red_circle: **(" + std::to_string(questions_asked) + "/" + std::to_string(total_questions) +
")** Game over! **Scores:**\n"; ")** Game over! **Scores:**\n";
@ -33,7 +38,7 @@ TriviaGame::~TriviaGame() {
} }
// sort by score, highest->lowest // sort by score, highest->lowest
std::sort(pairs.begin(), pairs.end(), [=](std::pair<std::string, int>& a, std::pair<std::string, int>& b) { std::sort(pairs.begin(), pairs.end(), [=](std::pair<std::string, int> &a, std::pair<std::string, int> &b) {
return a.second > b.second; return a.second > b.second;
}); });
@ -83,7 +88,7 @@ TriviaGame::~TriviaGame() {
rc = sqlite3_step(stmt); rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) { if (rc == SQLITE_ROW) {
std::string id = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)); std::string id = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 0));
int total_score = sqlite3_column_int(stmt, 1); int total_score = sqlite3_column_int(stmt, 1);
int average_time = sqlite3_column_int(stmt, 2); int average_time = sqlite3_column_int(stmt, 2);
@ -131,7 +136,6 @@ TriviaGame::~TriviaGame() {
} }
if (update_sql != "") { if (update_sql != "") {
std::cout << update_sql << std::endl;
rc = sqlite3_prepare_v2(db, update_sql.c_str(), -1, &stmt, 0); rc = sqlite3_prepare_v2(db, update_sql.c_str(), -1, &stmt, 0);
if (rc != SQLITE_OK) { if (rc != SQLITE_OK) {
std::cerr << "SQL error." << std::endl; std::cerr << "SQL error." << std::endl;
@ -159,14 +163,14 @@ TriviaGame::~TriviaGame() {
sqlite3_close(db); sqlite3_close(db);
} }
void TriviaGame::end_game() {
gh->delete_game(channel_id);
}
void TriviaGame::start() { void TriviaGame::start() {
question(); question();
} }
void TriviaGame::interrupt() {
current_thread->interrupt();
}
void TriviaGame::question() { void TriviaGame::question() {
sqlite3 *db; int rc; char *sql; sqlite3 *db; int rc; char *sql;
@ -188,10 +192,10 @@ void TriviaGame::question() {
rc = sqlite3_step(stmt); rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) { if (rc == SQLITE_ROW) {
// result received // result received
std::string id = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)); // converts id to string for us 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 category = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 1));
std::string question = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2)); std::string question = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 2));
std::string answer = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3)); std::string answer = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 3));
current_question = "#" + id + " [" + category + "] **" + question + "**"; current_question = "#" + id + " [" + category + "] **" + question + "**";
boost::split(current_answers, boost::algorithm::to_lower_copy(answer), boost::is_any_of("*")); boost::split(current_answers, boost::algorithm::to_lower_copy(answer), boost::is_any_of("*"));
@ -212,7 +216,7 @@ void TriviaGame::question() {
} }
void TriviaGame::give_hint(int hints_given, std::string hint) { void TriviaGame::give_hint(int hints_given, std::string hint) {
boost::this_thread::sleep(boost::posix_time::seconds(10)); boost::this_thread::sleep(interval);
std::string answer = *current_answers.begin(); std::string answer = *current_answers.begin();
@ -225,8 +229,7 @@ void TriviaGame::give_hint(int hints_given, std::string hint) {
hint = boost::regex_replace(hint, regexp, std::string(1, hide_char)); hint = boost::regex_replace(hint, regexp, std::string(1, hide_char));
print = true; print = true;
} } else {
else {
std::stringstream hint_stream(hint); std::stringstream hint_stream(hint);
std::random_device rd; std::random_device rd;
@ -281,13 +284,13 @@ void TriviaGame::give_hint(int hints_given, std::string hint) {
} }
void TriviaGame::question_failed() { void TriviaGame::question_failed() {
boost::this_thread::sleep(boost::posix_time::seconds(10)); boost::this_thread::sleep(interval);
ah->send_message(channel_id, ":exclamation: Question failed. Answer: ** `" + *current_answers.begin() + "` **"); ah->send_message(channel_id, ":exclamation: Question failed. Answer: ** `" + *current_answers.begin() + "` **");
if (questions_asked < 10) { if (questions_asked < total_questions) {
question(); question();
} else { } else {
end_game(); gh->delete_game(channel_id);
} }
} }
@ -307,15 +310,11 @@ void TriviaGame::handle_answer(std::string answer, DiscordObjects::User sender)
increase_score(sender.id); increase_score(sender.id);
update_average_time(sender.id, diff.total_milliseconds()); update_average_time(sender.id, diff.total_milliseconds());
if (questions_asked < 10) { if (questions_asked < total_questions) {
question(); question();
} else { } else {
end_game(); gh->delete_game(channel_id);
} }
} else if (answer == "`s" || answer == "`stop") {
current_thread->interrupt();
end_game();
} }
} }

View File

@ -19,17 +19,18 @@ namespace DiscordObjects {
class TriviaGame { class TriviaGame {
public: public:
TriviaGame(GatewayHandler *gh, APIHelper *ah, std::string channel_id, int total_questions); TriviaGame(GatewayHandler *gh, APIHelper *ah, std::string channel_id, int total_questions, int delay);
~TriviaGame(); ~TriviaGame();
void start(); void start();
void interrupt();
void handle_answer(std::string answer, DiscordObjects::User sender); void handle_answer(std::string answer, DiscordObjects::User sender);
private: private:
int questions_asked; int questions_asked;
int total_questions; int total_questions;
boost::posix_time::seconds interval;
void end_game();
void question(); void question();
void give_hint(int hints_given, std::string hint); void give_hint(int hints_given, std::string hint);
void question_failed(); void question_failed();

View File

@ -83,8 +83,6 @@ namespace DiscordObjects {
inline void Guild::load_from_json(json data) { inline void Guild::load_from_json(json data) {
Guild(); Guild();
std::cout << data.dump(4) << std::endl;
id = data.value("id", "null"); id = data.value("id", "null");
name = data.value("name", "null"); name = data.value("name", "null");