feat: add ResetLevelsCommand, user voice presets with extended settings, fix channel name format

This commit is contained in:
loki5512344 2026-05-11 17:16:40 +02:00
parent 9f6e19e5c6
commit 63f8763ff9
5 changed files with 349 additions and 3 deletions

View file

@ -0,0 +1,104 @@
package creator.loki.feature.level;
import creator.loki.embed.EmbedFactory;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Color;
/**
* Обработчик кнопок подтверждения сброса уровней
*/
public class ResetLevelsButtonHandler extends ListenerAdapter {
private static final Logger logger = LoggerFactory.getLogger(ResetLevelsButtonHandler.class);
private final ResetLevelsCommand resetCommand;
public ResetLevelsButtonHandler(ResetLevelsCommand resetCommand) {
this.resetCommand = resetCommand;
}
@Override
public void onButtonInteraction(ButtonInteractionEvent event) {
String buttonId = event.getComponentId();
// Проверяем что это наша кнопка
if (!buttonId.startsWith("reset_levels_")) {
return;
}
// Извлекаем ID пользователя из кнопки
String[] parts = buttonId.split(":");
if (parts.length != 2) {
return;
}
String authorId = parts[1];
// Проверяем что кнопку нажал тот же пользователь
if (!event.getUser().getId().equals(authorId)) {
event.reply("❌ Только инициатор команды может подтвердить действие!")
.setEphemeral(true)
.queue();
return;
}
if (buttonId.startsWith("reset_levels_confirm:")) {
// Подтверждение
event.deferReply().queue();
try {
String guildId = event.getGuild().getId();
String userId = event.getUser().getId();
// Выполняем сброс
resetCommand.executeReset(guildId, userId);
// Удаляем исходное сообщение с кнопками
event.getMessage().delete().queue();
// Отправляем подтверждение
event.getHook().sendMessageEmbeds(
EmbedFactory.create()
.setTitle("✅ Сброс уровней выполнен")
.setDescription(
"Уровни и XP всех пользователей успешно сброшены!\n\n" +
"**Сохранено:**\n" +
"• Время в голосовых каналах\n" +
"• Количество сообщений\n" +
"• Репутация\n\n" +
"**Сброшено:**\n" +
"• Уровень → 0\n" +
"• XP → 0\n\n" +
"Теперь используется новая система уровней!"
)
.setColor(Color.GREEN)
.setFooter("Инициатор: " + event.getUser().getName(), event.getUser().getAvatarUrl())
.build()
).queue();
logger.info("Сброс уровней подтверждён пользователем: {}", event.getUser().getName());
} catch (Exception e) {
logger.error("Ошибка при выполнении сброса", e);
event.getHook().sendMessageEmbeds(
EmbedFactory.create()
.setTitle("❌ Ошибка")
.setDescription("Произошла ошибка при сбросе уровней: " + e.getMessage())
.setColor(Color.RED)
.build()
).queue();
}
} else if (buttonId.startsWith("reset_levels_cancel:")) {
// Отмена
event.getMessage().delete().queue();
event.reply("❌ Сброс уровней отменён.")
.setEphemeral(true)
.queue();
logger.info("Сброс уровней отменён пользователем: {}", event.getUser().getName());
}
}
}

View file

@ -0,0 +1,152 @@
package creator.loki.feature.level;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.database.ConfigRepository;
import creator.loki.embed.EmbedFactory;
import creator.loki.repository.UserStatsRepository;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Color;
/**
* Одноразовая команда для сброса уровней и XP всех пользователей
* Сохраняет время в войсе и количество сообщений
*/
public class ResetLevelsCommand implements Command {
private static final Logger logger = LoggerFactory.getLogger(ResetLevelsCommand.class);
private static final String RESET_KEY = "levels_reset_executed";
private final UserStatsRepository repository;
private final ConfigRepository configRepo;
public ResetLevelsCommand(UserStatsRepository repository, ConfigRepository configRepo) {
this.repository = repository;
this.configRepo = configRepo;
}
@Override
public String getName() {
return "resetlevels";
}
@Override
public String getDescription() {
return "Сбросить уровни и XP всех пользователей (одноразовая команда, только для админов)";
}
@Override
public String getUsage() {
return "resetlevels";
}
@Override
public String getCategory() {
return "Настройка";
}
@Override
public void execute(CommandContext context) {
// Проверка прав администратора
if (context.getAuthor() == null || !context.getAuthor().hasPermission(Permission.ADMINISTRATOR)) {
context.getChannel().sendMessageEmbeds(
EmbedFactory.create()
.setTitle("❌ Недостаточно прав")
.setDescription("Эта команда доступна только администраторам.")
.setColor(Color.RED)
.build()
).queue();
return;
}
// Проверка, была ли команда уже выполнена
String guildId = context.getGuild().getId();
String resetKey = guildId + ":" + RESET_KEY;
String resetValue = configRepo.get(resetKey);
boolean alreadyReset = "true".equals(resetValue);
if (alreadyReset) {
context.getChannel().sendMessageEmbeds(
EmbedFactory.create()
.setTitle("⚠️ Команда уже выполнена")
.setDescription("Сброс уровней уже был выполнен ранее. Эта команда одноразовая и не может быть использована повторно.")
.setColor(Color.ORANGE)
.addField("💡 Информация", "Если нужно сбросить уровни снова, обратитесь к разработчику бота.", false)
.build()
).queue();
return;
}
// Показываем предупреждение с кнопками подтверждения
context.getChannel().sendMessageEmbeds(
EmbedFactory.create()
.setTitle("⚠️ ВНИМАНИЕ: Сброс уровней")
.setDescription(
"Вы собираетесь сбросить **уровни и XP** всех пользователей на сервере.\n\n" +
"**Что будет сброшено:**\n" +
"• Уровень → 0\n" +
"• XP → 0\n\n" +
"**Что НЕ будет затронуто:**\n" +
"• Время в голосовых каналах\n" +
"• Количество сообщений\n" +
"• Репутация\n\n" +
"**⚠️ Это действие НЕОБРАТИМО и может быть выполнено только ОДИН РАЗ!**"
)
.setColor(Color.RED)
.addField("📊 Новая система уровней",
"После сброса будет использоваться новая формула:\n" +
"• XP для уровня N = 200 × N\n" +
"• Пример: для 15 уровня нужно 22,800 XP",
false)
.setFooter("Подтвердите действие нажатием кнопки ниже")
.build()
).setActionRow(
Button.success("reset_levels_confirm:" + context.getAuthor().getId(), "✅ Подтвердить сброс"),
Button.danger("reset_levels_cancel:" + context.getAuthor().getId(), "❌ Отмена")
).queue();
}
/**
* Выполняет сброс уровней (вызывается из ButtonHandler)
*/
public void executeReset(String guildId, String userId) {
try {
logger.info("========================================");
logger.info("Начало сброса уровней для гильдии: {}", guildId);
logger.info("Инициатор: {}", userId);
logger.info("========================================");
// Получаем всех пользователей (без guildId - метод возвращает всех)
var allStats = repository.getAllStats();
int resetCount = 0;
// Фильтруем по guildId и сбрасываем только XP и уровень
for (var entry : allStats.entrySet()) {
String key = entry.getKey();
// Ключ формата "guildId:userId"
if (key.startsWith(guildId + ":")) {
var stats = entry.getValue();
stats.setTotalXP(0);
stats.setLevel(0);
// Сохраняем обратно
repository.save(stats);
resetCount++;
}
}
// Помечаем что команда выполнена
String resetKey = guildId + ":" + RESET_KEY;
configRepo.set(resetKey, "true");
logger.info("Сброс завершён. Обработано пользователей: {}", resetCount);
logger.info("========================================");
} catch (Exception e) {
logger.error("Ошибка при сбросе уровней", e);
throw new RuntimeException("Ошибка при сбросе уровней: " + e.getMessage(), e);
}
}
}

View file

@ -42,9 +42,12 @@ public class VoiceChannelListener extends ListenerAdapter {
VoiceChannel personalChannel = voiceManager.createPersonalChannel(member, guild); VoiceChannel personalChannel = voiceManager.createPersonalChannel(member, guild);
if (personalChannel != null) { if (personalChannel != null) {
// Применяем сохранённый пресет пользователя
voiceManager.applyUserPreset(personalChannel, member.getId());
// Перемещаем пользователя в созданный канал // Перемещаем пользователя в созданный канал
guild.moveVoiceMember(member, personalChannel).queue( guild.moveVoiceMember(member, personalChannel).queue(
success -> logger.info("Пользователь {} перемещен в личный канал", member.getEffectiveName()), success -> logger.info("Пользователь {} перемещен в личный канал с применённым пресетом", member.getEffectiveName()),
error -> logger.error("Ошибка при перемещении пользователя", error) error -> logger.error("Ошибка при перемещении пользователя", error)
); );
} }

View file

@ -59,7 +59,7 @@ public class VoiceChannelManager {
*/ */
public VoiceChannel createPersonalChannel(Member member, Guild guild) { public VoiceChannel createPersonalChannel(Member member, Guild guild) {
try { try {
String channelName = member.getEffectiveName() + "'s Channel"; String channelName = "Канал: " + member.getEffectiveName();
VoiceChannel channel = guild.createVoiceChannel(channelName) VoiceChannel channel = guild.createVoiceChannel(channelName)
.setParent(guild.getCategoryById(categoryId)) .setParent(guild.getCategoryById(categoryId))
@ -171,4 +171,62 @@ public class VoiceChannelManager {
public String getTriggerChannelId() { return triggerChannelId; } public String getTriggerChannelId() { return triggerChannelId; }
public String getCategoryId() { return categoryId; } public String getCategoryId() { return categoryId; }
public String getTextChannelId() { return textChannelId; } public String getTextChannelId() { return textChannelId; }
/**
* Сохранить пресет пользователя
*/
public void saveUserPreset(String userId, VoicePreset preset) {
Map<String, VoicePreset> presets = getUserPresets();
presets.put(userId, preset);
dataStore.setData("voice_user_presets", presets);
logger.info("Сохранён пресет для пользователя {}", userId);
}
/**
* Получить пресет пользователя
*/
public VoicePreset getUserPreset(String userId) {
Map<String, VoicePreset> presets = getUserPresets();
return presets.getOrDefault(userId, new VoicePreset());
}
/**
* Применить пресет пользователя к каналу
*/
public void applyUserPreset(VoiceChannel channel, String userId) {
VoicePreset preset = getUserPreset(userId);
try {
// Применяем настройки
if (preset.getName() != null && !preset.getName().isEmpty()) {
channel.getManager().setName(preset.getName()).queue();
}
if (preset.getLimit() >= 0) {
channel.getManager().setUserLimit(preset.getLimit()).queue();
}
if (preset.getBitrate() > 0) {
channel.getManager().setBitrate(preset.getBitrate()).queue();
}
// Применяем регион
if (preset.getRegion() != null && !preset.getRegion().equals("auto")) {
// JDA 5 не поддерживает регионы напрямую, но можно сохранить в настройках
logger.info("Регион {} будет применён для канала {}", preset.getRegion(), channel.getId());
}
logger.info("Применён пресет пользователя {} к каналу {}", userId, channel.getId());
} catch (Exception e) {
logger.error("Ошибка при применении пресета", e);
}
}
@SuppressWarnings("unchecked")
private Map<String, VoicePreset> getUserPresets() {
Type type = new TypeToken<Map<String, VoicePreset>>(){}.getType();
Map<String, VoicePreset> presets = dataStore.getData("voice_user_presets", type);
return presets != null ? presets : new ConcurrentHashMap<>();
}
} }

View file

@ -1,26 +1,55 @@
package creator.loki.feature.voice; package creator.loki.feature.voice;
import net.dv8tion.jda.api.Region;
/** /**
* Пресет настроек голосового канала * Пресет настроек голосового канала с расширенными опциями
*/ */
public class VoicePreset { public class VoicePreset {
private String name; private String name;
private int limit; private int limit;
private int bitrate; private int bitrate;
private String region; // Регион канала (auto, russia, europe, us-west, etc.)
private boolean locked; // Закрыт ли канал
private String allowedRoleId; // ID роли с доступом (если канал закрыт)
private String deniedRoleId; // ID роли без доступа
public VoicePreset() {
// Конструктор по умолчанию для Gson
this.name = "Мой канал";
this.limit = 0;
this.bitrate = 64000;
this.region = "auto";
this.locked = false;
this.allowedRoleId = null;
this.deniedRoleId = null;
}
public VoicePreset(String name, int limit, int bitrate) { public VoicePreset(String name, int limit, int bitrate) {
this.name = name; this.name = name;
this.limit = limit; this.limit = limit;
this.bitrate = bitrate; this.bitrate = bitrate;
this.region = "auto";
this.locked = false;
this.allowedRoleId = null;
this.deniedRoleId = null;
} }
// Getters // Getters
public String getName() { return name; } public String getName() { return name; }
public int getLimit() { return limit; } public int getLimit() { return limit; }
public int getBitrate() { return bitrate; } public int getBitrate() { return bitrate; }
public String getRegion() { return region; }
public boolean isLocked() { return locked; }
public String getAllowedRoleId() { return allowedRoleId; }
public String getDeniedRoleId() { return deniedRoleId; }
// Setters // Setters
public void setName(String name) { this.name = name; } public void setName(String name) { this.name = name; }
public void setLimit(int limit) { this.limit = limit; } public void setLimit(int limit) { this.limit = limit; }
public void setBitrate(int bitrate) { this.bitrate = bitrate; } public void setBitrate(int bitrate) { this.bitrate = bitrate; }
public void setRegion(String region) { this.region = region; }
public void setLocked(boolean locked) { this.locked = locked; }
public void setAllowedRoleId(String allowedRoleId) { this.allowedRoleId = allowedRoleId; }
public void setDeniedRoleId(String deniedRoleId) { this.deniedRoleId = deniedRoleId; }
} }