1
0
Fork 0
mirror of https://gitlab.com/niansa/SomeBot.git synced 2025-03-06 20:48:26 +01:00
SomeBot/modules/levels.cpp
2023-12-07 01:50:33 +01:00

358 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "../bot.hpp"
#include "../util.hpp"
#include <tuple>
#include <sstream>
class Levels {
Bot *bot;
void db_add_user(dpp::snowflake guild_id, dpp::snowflake user_id) {
bot->db << "INSERT OR IGNORE INTO levels (guild_id, user_id, level, xp) VALUES (?, ?, 0, 0);"
<< std::to_string(guild_id)
<< std::to_string(user_id);
}
void db_add_guild(dpp::snowflake guild_id) {
bot->db << "INSERT OR IGNORE INTO levels_guild_settings (id, enabled, embed, speed, color) VALUES (?, FALSE, TRUE, 1, 16711680);"
<< std::to_string(guild_id);
}
std::pair<unsigned, double> get_user_data(dpp::snowflake guild_id, dpp::snowflake user_id) {
std::pair<unsigned, double> fres = {0, 0.0};
bot->db << "SELECT level, xp FROM levels "
"WHERE guild_id = ? AND user_id = ?;"
<< std::to_string(guild_id)
<< std::to_string(user_id)
>> std::tie(fres.first, fres.second);
return fres;
}
void set_user_data(dpp::snowflake guild_id, dpp::snowflake user_id, unsigned level, double xp) {
bot->db << "UPDATE levels "
"SET level = ?, "
" xp = ? "
"WHERE guild_id = ? AND user_id = ?;"
<< level
<< xp
<< std::to_string(guild_id)
<< std::to_string(user_id);
}
std::tuple<bool, bool, double, unsigned> get_guild_data(dpp::snowflake guild_id) {
std::tuple<unsigned, unsigned, double, unsigned> fres;
bot->db << "SELECT enabled, embed, speed, color FROM levels_guild_settings "
"WHERE id = ?;"
<< std::to_string(guild_id)
>> std::tie(std::get<0>(fres), std::get<1>(fres), std::get<2>(fres), std::get<3>(fres));
return fres;
}
void set_guild_data(dpp::snowflake guild_id, bool enabled, double speed) {
bot->db << "UPDATE levels_guild_settings "
"SET enabled = ?, "
" speed = ? "
"WHERE id = ?;"
<< enabled
<< speed
<< std::to_string(guild_id);
}
dpp::message get_level_info_message(dpp::snowflake guild_id, dpp::snowflake target_id, bool target_is_sender, bool embed_enabled, unsigned color = 0xff0000) {
// Get level and XP
db_add_user(guild_id, target_id);
auto [level, xp] = get_user_data(guild_id, target_id);
// Get XP percentage (and solve rounding issue)
unsigned xp_percentage = xp*100.0;
if (xp_percentage == 100) {
xp_percentage = 99;
}
// Create message string
auto msg_str = "**<@"+std::to_string(target_id)+">**"+(target_is_sender?", you are":" is")+" level **"+std::to_string(level)+"** and **"+std::to_string(xp_percentage)+"%** to the next.";
// Create base message
dpp::message base_msg;
if (!target_is_sender) {
base_msg.set_flags(dpp::message_flags::m_ephemeral);
}
// Send current level
if (embed_enabled) {
dpp::embed embed;
embed.set_title("Your level")
.set_description(msg_str)
.set_color(color);
return base_msg.add_embed(embed);
} else {
return base_msg.set_content(msg_str).set_allowed_mentions(false, false, false, false, {}, {});
}
}
public:
Levels(Bot *_bot) : bot(_bot) {
bot->cluster.intents |= dpp::intents::i_guild_messages;
bot->db << "CREATE TABLE IF NOT EXISTS levels_guild_settings ("
" id TEXT PRIMARY KEY NOT NULL,"
" enabled INTEGER,"
" embed INTEGER,"
" speed REAL,"
" color INTEGER,"
" UNIQUE(id)"
");";
bot->db << "CREATE TABLE IF NOT EXISTS levels_guild_roles ("
" guild_id TEXT NOT NULL,"
" role_id TEXT NOT NULL,"
" threshold INTEGER,"
" UNIQUE(guild_id, role_id)"
");";
bot->db << "CREATE TABLE IF NOT EXISTS levels ("
" guild_id TEXT NOT NULL,"
" user_id TEXT NOT NULL,"
" level INTEGER,"
" xp REAL,"
" UNIQUE(guild_id, user_id)"
");";
bot->add_chatcommand(Bot::ChatCommand({"current_level", "level"}, "Shows your level (or someone else's)", dpp::slashcommand().add_option(dpp::command_option(dpp::command_option_type::co_user, "target", "Benutzer dessen Level du sehen möchtest", false))), [&](const dpp::slashcommand_t& event) {
// Get guild settings
db_add_guild(event.command.guild_id);
auto [enabled, embed_enabled, speed, color] = get_guild_data(event.command.guild_id);
// Make sure levels are enabled here
if (!enabled) {
event.reply(dpp::message("This server doesn't have levels enabled.").set_flags(dpp::message_flags::m_ephemeral));
return;
}
// Get target
dpp::snowflake target_id;
bool target_is_sender;
{
auto target_option = event.get_parameter("target");
if (target_option.index() == 0) {
target_id = event.command.usr.id;
target_is_sender = true;
} else {
target_id = std::get<dpp::snowflake>(target_option);
target_is_sender = target_id == event.command.usr.id;
}
}
// Get level info message and reply with it
event.reply(get_level_info_message(event.command.guild_id, target_id, target_is_sender, embed_enabled));
});
bot->add_chatcommand(Bot::ChatCommand({"levels_leaderboard", "leaderboard"}, "Shows the level leaderboard of this server"), [&](const dpp::slashcommand_t& event) {
// Get guild settings
db_add_guild(event.command.guild_id);
auto [enabled, embed_enabled, speed, color] = get_guild_data(event.command.guild_id);
// Make sure levels are enabled here
if (!enabled) {
event.reply(dpp::message("This server doesn't have levels enabled.").set_flags(dpp::message_flags::m_ephemeral));
return;
}
// Get all levels on this server sorted by highest-first
std::ostringstream leaderboard_str;
unsigned placement = 0;
bot->db << "SELECT level, user_id FROM levels "
"WHERE guild_id = ? "
"ORDER BY level DESC "
"LIMIT 10;"
<< std::to_string(event.command.guild_id)
>> [&](unsigned level, std::string user_id_str) {
if (level) {
leaderboard_str << "**" << ++placement << ".** <@" << user_id_str << ">" << " (Level *" << level << "*)" << (placement==1?" :crown:":"") << "\n";
}
};
// Create embed
dpp::embed embed;
embed.set_title("Leaderboard")
.set_description(std::move(leaderboard_str).str())
.set_color(color);
// Send result
event.reply(dpp::message().add_embed(embed));
});
bot->add_chatcommand(Bot::ChatCommand({"levels_settings"}, "Configure levels", dpp::slashcommand().add_option(dpp::command_option(dpp::command_option_type::co_boolean, "enabled", "Whether levels should be enabled", false)).add_option(dpp::command_option(dpp::command_option_type::co_boolean, "embed", "Whether levels should be shown in embeds", false)).add_option(dpp::command_option(dpp::command_option_type::co_string, "color", "Embed color of levels", false)).add_option(dpp::command_option(dpp::command_option_type::co_number, "speed", "Level speed: 0.5=half, 1.0=normal, 2.0=double, ...", false))), [&](const dpp::slashcommand_t& event) {
// Check that user has enough permissions
if (!event.command.get_guild().base_permissions(event.command.member).has(dpp::permissions::p_administrator)) {
event.reply(dpp::message("You need administration rights to use this command.").set_flags(dpp::message_flags::m_ephemeral));
return;
}
// Go on
db_add_guild(event.command.guild_id);
// Get parameters
auto opt_enabled = event.get_parameter("enabled"),
opt_embed = event.get_parameter("embed"),
opt_color = event.get_parameter("color"),
opt_speed = event.get_parameter("speed");
bool bad_color = false,
bad_speed = false;
// Update database
if (opt_enabled.index() != 0) {
bot->db << "UPDATE levels_guild_settings "
"SET enabled = ? "
"WHERE id = ?;"
<< std::get<bool>(opt_enabled)
<< std::to_string(event.command.guild_id);
}
if (opt_embed.index() != 0) {
bot->db << "UPDATE levels_guild_settings "
"SET embed = ? "
"WHERE id = ?;"
<< std::get<bool>(opt_embed)
<< std::to_string(event.command.guild_id);
}
if (opt_color.index() != 0) {
auto color = Util::str_to_color(std::get<std::string>(opt_color));
if (!(bad_color = color == -1U)) {
bot->db << "UPDATE levels_guild_settings "
"SET color = ? "
"WHERE id = ?;"
<< color
<< std::to_string(event.command.guild_id);
}
}
if (opt_speed.index() != 0) {
auto speed = std::get<double>(opt_speed);
if (!(bad_speed = speed <= 0.0 || speed > 7.0)) {
bot->db << "UPDATE levels_guild_settings "
"SET speed = ? "
"WHERE id = ?;"
<< speed
<< std::to_string(event.command.guild_id);
}
}
// Report success
event.reply(dpp::message(std::string("Done!")+
(bad_color?"\n**Warning:** I could not identify this color. Try again with a color code!":"")+
(bad_speed?"\n**Warning:** The specified speed is not allowed!":"")).set_flags(dpp::message_flags::m_ephemeral));
});
bot->add_chatcommand(Bot::ChatCommand({"levels_add_role", "level_role"}, "Add a level role", dpp::slashcommand().add_option(dpp::command_option(dpp::command_option_type::co_integer, "threshold", "Minimum level to have this role", true)).add_option(dpp::command_option(dpp::command_option_type::co_role, "role", "Role to assign", true))), [&](const dpp::slashcommand_t& event) {
// Check that user has enough permissions
if (!event.command.get_guild().base_permissions(event.command.member).has(dpp::permissions::p_manage_roles)) {
event.reply(dpp::message("You need role management rights to use this command.").set_flags(dpp::message_flags::m_ephemeral));
return;
}
// Get values
auto threshold = std::get<long>(event.get_parameter("threshold"));
auto role_id = std::get<dpp::snowflake>(event.get_parameter("role"));
// Check threshold
if (threshold <= 0) {
event.reply(dpp::message("You cannot set a role for this level!").set_flags(dpp::message_flags::m_ephemeral));
return;
}
// Insert into database
bot->db << "INSERT INTO levels_guild_roles (guild_id, role_id, threshold) VALUES (?, ?, ?);"
<< std::to_string(event.command.guild_id)
<< std::to_string(role_id)
<< threshold;
// Report success
event.reply(dpp::message("People with at least level "+std::to_string(threshold)+" are now assigned the role <@&"+std::to_string(role_id)+">!").set_allowed_mentions(true, false, false, true, {}, {}));
// Assign role where needed
bot->db << "SELECT user_id FROM levels "
"WHERE guild_id = ? AND level >= ?;"
<< std::to_string(event.command.guild_id)
<< threshold
>> [&](std::string user_id_str) {
bot->cluster.guild_member_add_role(event.command.guild_id, user_id_str, role_id);
};
});
bot->add_chatcommand(Bot::ChatCommand({"levels_remove_role", "level_role_remove"}, "Remove a level role", dpp::slashcommand().add_option(dpp::command_option(dpp::command_option_type::co_role, "role", "Role to no longer be assigned", true))), [&](const dpp::slashcommand_t& event) {
// Check that user has enough permissions
if (!event.command.get_guild().base_permissions(event.command.member).has(dpp::permissions::p_manage_roles)) {
event.reply(dpp::message("You need role management rights to use this command.").set_flags(dpp::message_flags::m_ephemeral));
return;
}
// Get values
auto role_id = std::get<dpp::snowflake>(event.get_parameter("role"));
bot->db << "DELETE FROM levels_guild_roles "
"WHERE role_id = ? AND guild_id = ?;"
<< std::to_string(role_id)
<< std::to_string(event.command.guild_id);
// Report success
event.reply(dpp::message("The role <@&"+std::to_string(role_id)+"> is now no longer a level role.").set_allowed_mentions(true, false, false, true, {}, {}));
});
bot->add_usercommand(Bot::UserCommand({"Level anzeigen"}, "Shows the level of a member"), [&](const dpp::user_context_menu_t& event) {
// Get guild settings
db_add_guild(event.command.guild_id);
auto [enabled, embed_enabled, speed, color] = get_guild_data(event.command.guild_id);
// Make sure levels are enabled here
if (!enabled) {
event.reply(dpp::message("This server doesn't have levels enabled.").set_flags(dpp::message_flags::m_ephemeral));
return;
}
// Get target
dpp::snowflake target_id = event.get_user().id;
// Get level info message and reply with it
event.reply(get_level_info_message(event.command.guild_id, target_id, target_id == event.command.usr.id, embed_enabled, color));
});
bot->cluster.on_message_create([&](const dpp::message_create_t& message) {
const auto& author = message.msg.author;
// Make sure user isn't a bot
if (author.is_bot()) {
return;
}
// Make sure guild has entry in database
db_add_guild(message.msg.guild_id);
// Get guild data
auto [enabled, embed_enabled, speed, color] = get_guild_data(message.msg.guild_id);
// Make sure leveling is enabled on guild
if (!enabled) {
return;
}
// Make sure user has entry in database
db_add_user(message.msg.guild_id, author.id);
// Get user data
auto [level, xp] = get_user_data(message.msg.guild_id, author.id);
// Increment XP
xp += (1.0/double(level+1))*0.4739*speed;
// Increment level and reset xp as needed
while (xp >= 1.0) {
xp -= 1.0;
level++;
// Notify user about levelup
std::string msg_str = "You are now level **"+std::to_string(level)+"**.";
dpp::message msg;
msg.set_channel_id(message.msg.channel_id)
.set_allowed_mentions(true, false, false, true, {}, {});
if (embed_enabled) {
dpp::embed embed;
embed.set_title("× Level up!")
.set_description(msg_str)
.set_thumbnail(message.msg.author.get_avatar_url())
.set_color(color);
msg.add_embed(embed)
.set_content(message.msg.member.get_mention());
bot->cluster.message_create(msg);
} else {
message.reply(msg.set_content(message.msg.member.get_mention()+'\n'+msg_str));
}
// Get highest level role
dpp::snowflake highest_level_role = 0;
unsigned highest_threshold = 0;
bot->db << "SELECT role_id, threshold FROM levels_guild_roles "
"WHERE guild_id = ? AND threshold <= ?;"
<< std::to_string(message.msg.guild_id)
<< level
>> [&](std::string role_id_str, unsigned threshold) {
if (threshold > highest_threshold) {
highest_threshold = threshold;
highest_level_role = role_id_str;
}
};
// Update their roles if needed
if (highest_threshold) {
bot->cluster.guild_member_add_role(message.msg.guild_id, author.id, highest_level_role, [this, role_id = highest_level_role](const dpp::confirmation_callback_t& ccb) {
// Drop role from database on error
if (ccb.is_error()) {
try {
bot->db << "DELETE FROM levels_guild_roles "
"WHERE role_id = ?;"
<< std::to_string(role_id);
} catch (...) {}
}
});
}
}
// Update database
set_user_data(message.msg.guild_id, author.id, level, xp);
});
}
};
BOT_ADD_MODULE(Levels);