#include "../bot.hpp" #include "../util.hpp" #include #include 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 get_user_data(dpp::snowflake guild_id, dpp::snowflake user_id) { std::pair 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 get_guild_data(dpp::snowflake guild_id) { std::tuple 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(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(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(opt_embed) << std::to_string(event.command.guild_id); } if (opt_color.index() != 0) { auto color = Util::str_to_color(std::get(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(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(event.get_parameter("threshold")); auto role_id = std::get(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(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);