1
0
Fork 0
mirror of https://gitlab.com/niansa/discordlistforbots.git synced 2025-03-06 20:49:22 +01:00
discordlistforbots/controllers/views.cc
2021-01-11 20:53:05 +01:00

323 lines
13 KiB
C++

#include <string>
#include <unordered_map>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <exception>
#include <functional>
#include <fmt/core.h>
#include <drogon/drogon.h>
#include "../config.h"
#include "views.h"
static unordered_map <std::string, trantor::Date> last_votes;
#define isAuthed() getOptional<bool>("discord_authed").has_value()
#define authenticate(cb) cb(HttpResponse::newRedirectionResponse(OAUTH_URL)); return
#define toStart(cb) cb(HttpResponse::newRedirectionResponse("/")); return
#define voteID(uid, bid) std::to_string(uid)+'-'+std::to_string(bid)
#define cantVote(vid, ...) {auto _cantvoteres = last_votes.find(vid); auto now = trantor::Date::date(); if (_cantvoteres != last_votes.end() and now < _cantvoteres->second.after(43200)) __VA_ARGS__}
Bot deserializeBot(orm::Row row) {
Bot thisbot;
# define sf(f) thisbot.f = row[#f]
sf(name).as<std::string>();
sf(short_description).as<std::string>();
sf(long_description).as<std::string>();
sf(avatar_url).as<std::string>();
sf(owner).as<std::string>();
sf(support_server).as<std::string>();
sf(prefix).as<std::string>();
sf(votes).as<uint32_t>();
sf(approved).as<bool>();
thisbot.owner_id = std::stoul(row["owner_id"].as<std::string>());
thisbot.app_id = std::stoul(row["app_id"].as<std::string>());
return thisbot;
}
std::string dbEsc(const std::string& src) {
std::ostringstream fres;
for (const auto &character : src) {
if (character == '\'') {
fres << "''";
} else {
fres << character;
}
}
return fres.str();
}
std::string htmlEsc(const std::string& src) {
std::ostringstream fres;
for (const auto &character : src) {
switch (character) {
case '<': fres << "&lt;"; break;
case '>': fres << "&gt;"; break;
case '&': fres << "&amp;"; break;
case '"': fres << "&quot;"; break;
default: fres << character;
}
}
return fres.str();
}
auto errPage(const std::exception& e) {
HttpViewData data;
data.insert("message", e.what());
auto resp = HttpResponse::newHttpViewResponse("exception.csp", data);
resp->setStatusCode(HttpStatusCode::k500InternalServerError);
return resp;
}
void getUser(uint64_t user_id, std::function<void (const Json::Value&)> callback) {
auto discordapi = HttpClient::newHttpClient("https://discord.com");
auto req = HttpRequest::newHttpRequest();
req->setPath("/api/v8/users/"+std::to_string(user_id));
req->setMethod(HttpMethod::Get);
req->addHeader("Authorization", "Bot " BOT_TOKEN);
discordapi->sendRequest(req, [callback, user_id] (ReqResult, const HttpResponsePtr &response) {
if (response->getStatusCode() == HttpStatusCode::k200OK) {
auto &json = *response->getJsonObject().get();
auto avatar_hash = json["avatar"].asString();
if (avatar_hash.empty()) {
json["avatar_url"] = "https://cdn.discordapp.com/embed/avatars/"+std::to_string(std::stoi(json["discriminator"].asString()) % 5)+".png";
} else {
json["avatar_url"] = "https://cdn.discordapp.com/avatars/"+std::to_string(user_id)+"/"+avatar_hash+".png";
}
callback(json);
} else {
callback({});
}
});
}
auto dbErr = [](const orm::DrogonDbException &) {};
views::views() {
db = drogon::app().getDbClient();
}
void views::start(
const HttpRequestPtr&, std::function<void (const HttpResponsePtr &)> &&callback
)
{
callback(HttpResponse::newRedirectionResponse("/bots/@all", HttpStatusCode::k301MovedPermanently));
}
void LoginFilter::doFilter(const HttpRequestPtr &req, FilterCallback &&fcb, FilterChainCallback &&fccb) {
if (req->session()->isAuthed()) {
fccb();
} else {
authenticate(fcb);
}
}
void views::botlist(
const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback
)
{
auto session = req->session();
auto authed = session->isAuthed();
auto justMine = req->getPath()=="/bots/@me";
auto modView = req->getPath()=="/bots/@unapproved";
std::string q = "SELECT * FROM bots WHERE ";
if (justMine) {
q.append("owner_id = '"+std::to_string(session->get<uint64_t>("discord_user_id"))+"'");
} else {
q.append("approved = "+std::string(modView?"false":"true"));
}
q.append(" ORDER BY votes");
db->execSqlAsync(q,
[justMine, authed, callback] (const orm::Result &rows) {
std::map<uint64_t, Bot> bot_list;
for (const auto& r : rows) {
Bot bot = deserializeBot(r);
bot_list[bot.app_id] = bot;
}
HttpViewData data;
data.insert("modView", false);
data.insert("justMine", justMine);
data.insert("authed", authed);
data.insert("bots", bot_list);
callback(HttpResponse::newHttpViewResponse("botlist.csp", data));
}, dbErr);
}
void views::botdetail(
const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback,
uint64_t bot_id)
{
db->execSqlAsync("SELECT * FROM bots WHERE app_id = '"+std::to_string(bot_id)+"'",
[req, callback, bot_id] (const orm::Result &rows) {
if (rows.empty()) {
// Bot not found
callback(HttpResponse::newNotFoundResponse());
} else {
// Bot found
auto session = req->session();
auto bot = deserializeBot(rows[0]);
HttpViewData data;
data.insert("modView", false);
data.insert("bot_id", bot.app_id);
data.insert("bot", bot);
data.insert("owner", session->isAuthed() and session->get<uint64_t>("discord_user_id") == bot.owner_id);
cantVote(voteID(req->session()->get<uint64_t>("discord_user_id"), bot_id), {
data.insert("canVote", false);
} else {
data.insert("canVote", bot.approved);
})
callback(HttpResponse::newHttpViewResponse("botdetail.csp", data));
}
}, dbErr);
}
void views::botvote(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback,
uint64_t bot_id) {
auto session = req->session();
// Check if user is able to vote again
auto user_id = session->get<uint64_t>("discord_user_id");
auto vote_id = voteID(user_id, bot_id);
cantVote(vote_id, {
callback(HttpResponse::newRedirectionResponse("detail"));
return;
})
// Register vote
db->execSqlAsync("UPDATE bots SET votes = votes + 1 WHERE app_id = '"+std::to_string(bot_id)+"'",
[vote_id, callback] (const orm::Result &rows) {
if (rows.affectedRows() == 0) {
// Bot not found
callback(HttpResponse::newNotFoundResponse());
} else {
last_votes[vote_id] = trantor::Date::date();
// Redirect back
callback(HttpResponse::newRedirectionResponse("detail"));
}
}, [callback] (const orm::DrogonDbException &) {
callback(HttpResponse::newNotFoundResponse());
});
}
void views::botregister_view(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback,
const std::string& error) {
auto session = req->session();
// Display page
HttpViewData data;
data.insert("owner", session->get<std::string>("discord_user_fullname"));
data.insert("error", error);
callback(HttpResponse::newHttpViewResponse("botregister.csp", data));
}
void views::botregister_submit(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback
) {
auto session = req->session();
// Set error handler
auto onError = [callback] (const std::string& e) {
callback(HttpResponse::newRedirectionResponse("register?error="+e));
};
// Get and check parameters
string short_description, long_description, support_server, prefix;
uint64_t app_id;
try {
app_id = std::stoul(req->getParameter("app_id"));
short_description = htmlEsc(req->getParameter("short_description"));
long_description = htmlEsc(req->getParameter("long_description"));
support_server = htmlEsc(req->getParameter("support_server"));
prefix = htmlEsc(req->getParameter("prefix"));
} catch (std::exception& e) {
onError(e.what());
}
// Check if bot already exists
db->execSqlAsync("select 1 from bots where app_id ='"+std::to_string(app_id)+"'",
[=] (const orm::Result &r) {
if (not r.empty()) {
onError("Bot%20has%20already%20been%20registered");
return;
}
// Get bots avatar
getUser(app_id, [=] (const Json::Value& botuser) {
// Check result
if (botuser.empty() or not botuser["bot"].asBool()) {
onError("Invalid%20client%20ID");
return;
}
// Perform database operation
db->execSqlAsync(fmt::format("INSERT INTO bots (name, short_description, long_description, avatar_url, owner, support_server, prefix, owner_id, app_id, votes, approved) "
"VALUES('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', 0, 'f')",
dbEsc(botuser["username"].asString()), dbEsc(short_description), dbEsc(long_description), dbEsc(botuser["avatar_url"].asString()), session->get<std::string>("discord_user_fullname"), dbEsc(support_server), dbEsc(prefix), session->get<uint64_t>("discord_user_id"), app_id),
[app_id, callback] (const orm::Result &) {
callback(HttpResponse::newRedirectionResponse(std::to_string(app_id)+"/detail"));
}, dbErr);
});
}, dbErr);
}
void views::discorddeauth(
const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback
) {
req->session()->clear();
toStart(callback);
}
void views::discordauth(
const HttpRequestPtr& client_req, std::function<void (const HttpResponsePtr &)> &&callback,
const std::string& code)
{
if (code.empty()) {
authenticate(callback);
} else {
// Get token from API
auto discordapi = HttpClient::newHttpClient("https://discord.com");
auto req = HttpRequest::newHttpRequest();
req->setPath("/api/v8/oauth2/token");
req->setContentTypeCode(ContentType::CT_APPLICATION_X_FORM);
req->setMethod(HttpMethod::Post);
{
req->setParameter("client_id", CLIENT_ID);
req->setParameter("client_secret", CLIENT_SECRET);
req->setParameter("redirect_uri", REDIRECT_URI);
req->setParameter("grant_type", "authorization_code");
req->setParameter("scope", "identify");
req->setParameter("code", code);
}
discordapi->sendRequest(req, [discordapi, client_req, callback] (ReqResult, const HttpResponsePtr &response) {
// Check for success
if (response->getStatusCode() == HttpStatusCode::k200OK) {
// Auth success
auto &data = *response->getJsonObject().get();
auto session = client_req->session();
auto access_token = data["access_token"].asString();
session->clear();
session->insert("discord_access_token", access_token);
// Get user data
auto req = HttpRequest::newHttpRequest();
req->setPath("/api/v8/users/@me");
req->setMethod(HttpMethod::Get);
req->addHeader("Authorization", "Bearer "+access_token);
discordapi->sendRequest(req, [client_req, callback, session] (ReqResult, const HttpResponsePtr &response) {
if (response->getStatusCode() == HttpStatusCode::k200OK) {
// Getting user data success
auto &userdata = *response->getJsonObject().get();
auto fullname = userdata["username"].asString()+'#'+userdata["discriminator"].asString();
session->insert("discord_authed", true);
session->insert("discord_user_id", std::stoul(userdata["id"].asString()));
session->insert("discord_user_fullname", fullname);
// Show success page
HttpViewData data;
data.insert("fullname", fullname);
callback(HttpResponse::newHttpViewResponse("authsuccess.csp", data));
} else {
toStart(callback);
}
});
} else {
toStart(callback);
}
});
}
}