feat: add ResetLevelsCommand, user voice presets with extended settings, fix channel name format
This commit is contained in:
parent
9f6e19e5c6
commit
63f8763ff9
5 changed files with 349 additions and 3 deletions
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
152
src/main/java/creator/loki/feature/level/ResetLevelsCommand.java
Normal file
152
src/main/java/creator/loki/feature/level/ResetLevelsCommand.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue