From 0787e98921c95d5f22dfc38ecd456b294816f85c Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Sat, 20 Jun 2026 00:33:28 +0200 Subject: [PATCH] feat: configurable level roles via !levelrole admin command - LevelRoleCommand: set/remove/clear role mappings per level - LevelRoleService: reads from ConfigRepository, falls back to defaults - LevelCommandRegistrar: wires new command --- TODO.md | 10 +- .../config/repository/ConfigRepository.java | 15 ++ .../feature/level/LevelCommandRegistrar.java | 11 +- .../loki/feature/level/LevelModule.java | 4 +- .../loki/feature/level/LevelRoleService.java | 25 ++- .../level/commands/LevelRoleCommand.java | 182 ++++++++++++++++++ 6 files changed, 237 insertions(+), 10 deletions(-) create mode 100644 src/main/java/creator/loki/feature/level/commands/LevelRoleCommand.java diff --git a/TODO.md b/TODO.md index deac198..e451b4d 100644 --- a/TODO.md +++ b/TODO.md @@ -135,7 +135,7 @@ ## 🚀 Новые фичи (Roadmap) -## 🚀 Экономика — план реализации (в работе) +## 🚀 Экономика — ✅ реализовано ### Модель данных @@ -234,15 +234,15 @@ feature/economy/ ### Приоритет 1 - [ ] Система достижений (Achievements) -- [x] Система экономики (Economy) — **в работе** +- [x] Система экономики (Economy) — **✅** ### Приоритет 2 - [x] Авто-модерация (3 варна = авто-мут на 1ч) - [x] Музыка (lavaplayer: play, skip, stop, queue, np, volume, leave) - [x] ~~Музыка — поиск с выбором (yt/sc/list + выбор номера)~~ - [x] Server Stats (участники, каналы, роли, бусты) -- [ ] Level Roles (роли за уровни) -- [ ] Красивые level-up embed с прогресс-баром +- [x] Level Roles (роли за уровни) +- [x] Красивые level-up embed с прогресс-баром ### Приоритет 3 - [ ] Статистика сервера — графики, CSV export @@ -251,4 +251,4 @@ feature/economy/ --- -**Версия:** 2.2 | **Последнее обновление:** 2026-06-20 | **Java 21 ✅, God Constructor ✅, Critical баги ✅, Economy 🚧** +**Версия:** 2.2 | **Последнее обновление:** 2026-06-20 | **Java 21 ✅, God Constructor ✅, Critical баги ✅, Economy ✅, Level Roles ✅** diff --git a/src/main/java/creator/loki/feature/config/repository/ConfigRepository.java b/src/main/java/creator/loki/feature/config/repository/ConfigRepository.java index 05af403..c75a37e 100644 --- a/src/main/java/creator/loki/feature/config/repository/ConfigRepository.java +++ b/src/main/java/creator/loki/feature/config/repository/ConfigRepository.java @@ -8,6 +8,8 @@ import org.slf4j.LoggerFactory; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; /** * Репозиторий для работы с конфигурацией @@ -54,6 +56,19 @@ public class ConfigRepository implements StringConfigStore { } } + public Map getAll() { + Map result = new HashMap<>(); + try (PreparedStatement stmt = database.prepare("SELECT key, value FROM config")) { + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + result.put(rs.getString("key"), rs.getString("value")); + } + } catch (SQLException e) { + logger.error("Ошибка получения всех конфигураций", e); + } + return result; + } + /** * Удалить значение конфигурации */ diff --git a/src/main/java/creator/loki/feature/level/LevelCommandRegistrar.java b/src/main/java/creator/loki/feature/level/LevelCommandRegistrar.java index 4a7c793..55bbea2 100644 --- a/src/main/java/creator/loki/feature/level/LevelCommandRegistrar.java +++ b/src/main/java/creator/loki/feature/level/LevelCommandRegistrar.java @@ -3,7 +3,9 @@ package creator.loki.feature.level; import com.google.inject.Inject; import creator.loki.core.registry.CommandRegistrar; import creator.loki.core.registry.CommandRegistry; +import creator.loki.feature.config.repository.ConfigRepository; import creator.loki.feature.leaderboard.LeaderboardCommand; +import creator.loki.feature.level.commands.LevelRoleCommand; import creator.loki.feature.level.service.LeaderboardService; import creator.loki.feature.level.service.LevelService; import creator.loki.feature.profile.ProfileCommand; @@ -13,16 +15,22 @@ public class LevelCommandRegistrar implements CommandRegistrar { private final LeaderboardService leaderboardService; private final ResetLevelsCommand resetLevelsCommand; private final ProfileService profileService; + private final LevelRoleService levelRoleService; + private final ConfigRepository configRepository; @Inject public LevelCommandRegistrar( LeaderboardService leaderboardService, ResetLevelsCommand resetLevelsCommand, - ProfileService profileService + ProfileService profileService, + LevelRoleService levelRoleService, + ConfigRepository configRepository ) { this.leaderboardService = leaderboardService; this.resetLevelsCommand = resetLevelsCommand; this.profileService = profileService; + this.levelRoleService = levelRoleService; + this.configRepository = configRepository; } @Override @@ -31,5 +39,6 @@ public class LevelCommandRegistrar implements CommandRegistrar { registry.register(new StatsCommand()); registry.register(resetLevelsCommand); registry.register(new ProfileCommand(profileService)); + registry.register(new LevelRoleCommand(levelRoleService, configRepository)); } } diff --git a/src/main/java/creator/loki/feature/level/LevelModule.java b/src/main/java/creator/loki/feature/level/LevelModule.java index 15963cd..d8dbc36 100644 --- a/src/main/java/creator/loki/feature/level/LevelModule.java +++ b/src/main/java/creator/loki/feature/level/LevelModule.java @@ -58,8 +58,8 @@ public class LevelModule extends AbstractModule { @Provides @Singleton - LevelRoleService provideLevelRoleService() { - return new LevelRoleService(); + LevelRoleService provideLevelRoleService(ConfigRepository configRepository) { + return new LevelRoleService(configRepository); } @Provides diff --git a/src/main/java/creator/loki/feature/level/LevelRoleService.java b/src/main/java/creator/loki/feature/level/LevelRoleService.java index 5088fb6..40e43a0 100644 --- a/src/main/java/creator/loki/feature/level/LevelRoleService.java +++ b/src/main/java/creator/loki/feature/level/LevelRoleService.java @@ -1,5 +1,6 @@ package creator.loki.feature.level; +import creator.loki.feature.config.repository.ConfigRepository; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Role; import org.slf4j.Logger; @@ -10,7 +11,7 @@ import java.util.Map; public class LevelRoleService { private static final Logger logger = LoggerFactory.getLogger(LevelRoleService.class); - private static final Map REWARD_ROLES = Map.of( + private static final Map DEFAULT_ROLES = Map.of( 5, "Активный", 10, "Постоянный", 20, "Ветеран", @@ -18,8 +19,28 @@ public class LevelRoleService { 50, "Мастер" ); + private final ConfigRepository configRepository; + + public LevelRoleService(ConfigRepository configRepository) { + this.configRepository = configRepository; + } + + public String getRoleForLevel(int level) { + String configRole = configRepository.get("level_role_" + level); + if (configRole != null) return configRole; + return DEFAULT_ROLES.get(level); + } + + public void setRoleForLevel(int level, String roleName) { + configRepository.set("level_role_" + level, roleName); + } + + public void removeRoleForLevel(int level) { + configRepository.delete("level_role_" + level); + } + public String assignRewardRole(Member member, int newLevel) { - String roleName = REWARD_ROLES.get(newLevel); + String roleName = getRoleForLevel(newLevel); if (roleName == null) return null; List roles = member.getGuild().getRolesByName(roleName, true); diff --git a/src/main/java/creator/loki/feature/level/commands/LevelRoleCommand.java b/src/main/java/creator/loki/feature/level/commands/LevelRoleCommand.java new file mode 100644 index 0000000..6d16c4e --- /dev/null +++ b/src/main/java/creator/loki/feature/level/commands/LevelRoleCommand.java @@ -0,0 +1,182 @@ +package creator.loki.feature.level.commands; + +import creator.loki.command.Command; +import creator.loki.command.CommandContext; +import creator.loki.embed.EmbedTemplates; +import creator.loki.feature.config.repository.ConfigRepository; +import creator.loki.feature.level.LevelRoleService; +import net.dv8tion.jda.api.Permission; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class LevelRoleCommand implements Command { + private final LevelRoleService levelRoleService; + private final ConfigRepository configRepository; + + public LevelRoleCommand(LevelRoleService levelRoleService, ConfigRepository configRepository) { + this.levelRoleService = levelRoleService; + this.configRepository = configRepository; + } + + @Override + public String getName() { + return "levelrole"; + } + + @Override + public String getDescription() { + return "Управление ролями за уровни"; + } + + @Override + public String getUsage() { + return "levelrole [set |remove |clear]"; + } + + @Override + public String getCategory() { + return "Настройка"; + } + + @Override + public void execute(CommandContext context) { + if (context.getAuthor() == null || !context.getAuthor().hasPermission(Permission.ADMINISTRATOR)) { + context.getChannel().sendMessageEmbeds( + EmbedTemplates.error("Недостаточно прав", "Эта команда доступна только администраторам.") + ).queue(); + return; + } + + List args = context.getArgs(); + if (args.isEmpty()) { + showMappings(context); + return; + } + + switch (args.get(0).toLowerCase()) { + case "set" -> handleSet(context, args); + case "remove" -> handleRemove(context, args); + case "clear" -> handleClear(context); + default -> showMappings(context); + } + } + + private void showMappings(CommandContext context) { + Map allConfig = configRepository.getAll(); + Map customRoles = allConfig.entrySet().stream() + .filter(e -> e.getKey().startsWith("level_role_")) + .collect(Collectors.toMap( + e -> Integer.parseInt(e.getKey().substring("level_role_".length())), + Map.Entry::getValue + )); + + StringBuilder sb = new StringBuilder(); + sb.append("**Текущие настройки ролей за уровни:**\n\n"); + + for (int level : new int[]{5, 10, 20, 30, 50}) { + String custom = customRoles.get(level); + String effective = levelRoleService.getRoleForLevel(level); + sb.append("**Уровень ").append(level).append(":** "); + if (custom != null) { + sb.append("`").append(effective).append("`"); + } else { + sb.append(effective).append(" *(по умолчанию)*"); + } + sb.append("\n"); + } + + for (Map.Entry entry : customRoles.entrySet()) { + int level = entry.getKey(); + if (level != 5 && level != 10 && level != 20 && level != 30 && level != 50) { + sb.append("**Уровень ").append(level).append(":** `").append(entry.getValue()).append("`\n"); + } + } + + context.getChannel().sendMessageEmbeds( + EmbedTemplates.info("Управление ролями", sb.toString()) + ).queue(); + } + + private void handleSet(CommandContext context, List args) { + if (args.size() < 3) { + context.getChannel().sendMessageEmbeds( + EmbedTemplates.error("Ошибка", "Использование: `!levelrole set <уровень> <название роли>`") + ).queue(); + return; + } + + int level; + try { + level = Integer.parseInt(args.get(1)); + } catch (NumberFormatException e) { + context.getChannel().sendMessageEmbeds( + EmbedTemplates.error("Ошибка", "Уровень должен быть числом.") + ).queue(); + return; + } + + if (level < 1) { + context.getChannel().sendMessageEmbeds( + EmbedTemplates.error("Ошибка", "Уровень должен быть положительным числом.") + ).queue(); + return; + } + + String roleName = String.join(" ", args.subList(2, args.size())); + if (roleName.isEmpty()) { + context.getChannel().sendMessageEmbeds( + EmbedTemplates.error("Ошибка", "Название роли не может быть пустым.") + ).queue(); + return; + } + + levelRoleService.setRoleForLevel(level, roleName); + context.getChannel().sendMessageEmbeds( + EmbedTemplates.success("Роль назначена", + "Роль `" + roleName + "` будет выдаваться за уровень **" + level + "**.") + ).queue(); + } + + private void handleRemove(CommandContext context, List args) { + if (args.size() < 2) { + context.getChannel().sendMessageEmbeds( + EmbedTemplates.error("Ошибка", "Использование: `!levelrole remove <уровень>`") + ).queue(); + return; + } + + int level; + try { + level = Integer.parseInt(args.get(1)); + } catch (NumberFormatException e) { + context.getChannel().sendMessageEmbeds( + EmbedTemplates.error("Ошибка", "Уровень должен быть числом.") + ).queue(); + return; + } + + levelRoleService.removeRoleForLevel(level); + context.getChannel().sendMessageEmbeds( + EmbedTemplates.success("Роль сброшена", + "Для уровня **" + level + "** будет использоваться роль по умолчанию.") + ).queue(); + } + + private void handleClear(CommandContext context) { + Map allConfig = configRepository.getAll(); + int removed = 0; + for (String key : allConfig.keySet()) { + if (key.startsWith("level_role_")) { + configRepository.delete(key); + removed++; + } + } + + context.getChannel().sendMessageEmbeds( + EmbedTemplates.success("Настройки сброшены", + "Удалено пользовательских ролей: **" + removed + "**. Будут использоваться роли по умолчанию.") + ).queue(); + } +}