Refactor: remove TTS system, slash commands folder, reorganize stats leaderboard

This commit is contained in:
loki5512344 2026-05-11 14:12:30 +02:00
parent f43db6a527
commit 64a8928d41
29 changed files with 313 additions and 1764 deletions

View file

@ -1,163 +0,0 @@
package creator.loki.audio;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
import com.sedmelluq.discord.lavaplayer.track.playback.NonAllocatingAudioFrameBuffer;
import net.dv8tion.jda.api.entities.Guild;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class AudioManager {
private static final Logger logger = LoggerFactory.getLogger(AudioManager.class);
private static final long CLEANUP_INTERVAL_MINUTES = 10;
private static final long INACTIVE_THRESHOLD_MINUTES = 30;
private final AudioPlayerManager playerManager;
private final Map<Long, GuildMusicManager> musicManagers;
private final Map<Long, Long> lastActivityTime; // guildId -> timestamp
private final Map<Long, TtsQueue> ttsQueues; // guildId -> TtsQueue
private final ScheduledExecutorService cleanupScheduler;
public AudioManager() {
this.musicManagers = new ConcurrentHashMap<>();
this.lastActivityTime = new ConcurrentHashMap<>();
this.ttsQueues = new ConcurrentHashMap<>();
this.playerManager = new DefaultAudioPlayerManager();
this.playerManager.getConfiguration().setFrameBufferFactory(NonAllocatingAudioFrameBuffer::new);
AudioSourceManagers.registerRemoteSources(playerManager);
AudioSourceManagers.registerLocalSource(playerManager);
// Запускаем периодическую очистку неактивных менеджеров
this.cleanupScheduler = Executors.newSingleThreadScheduledExecutor();
this.cleanupScheduler.scheduleAtFixedRate(
this::cleanupInactiveManagers,
CLEANUP_INTERVAL_MINUTES,
CLEANUP_INTERVAL_MINUTES,
TimeUnit.MINUTES
);
logger.info("AudioManager инициализирован с автоочисткой каждые {} минут", CLEANUP_INTERVAL_MINUTES);
}
public synchronized GuildMusicManager getGuildAudioPlayer(Guild guild) {
long guildId = guild.getIdLong();
GuildMusicManager musicManager = musicManagers.get(guildId);
if (musicManager == null) {
musicManager = new GuildMusicManager(playerManager);
musicManagers.put(guildId, musicManager);
logger.debug("Создан новый GuildMusicManager для гильдии: {}", guild.getName());
}
// Обновляем время последней активности
lastActivityTime.put(guildId, System.currentTimeMillis());
guild.getAudioManager().setSendingHandler(musicManager.getSendHandler());
return musicManager;
}
/**
* Очищает неактивные GuildMusicManager
*/
private void cleanupInactiveManagers() {
long currentTime = System.currentTimeMillis();
long thresholdTime = currentTime - TimeUnit.MINUTES.toMillis(INACTIVE_THRESHOLD_MINUTES);
musicManagers.entrySet().removeIf(entry -> {
long guildId = entry.getKey();
Long lastActivity = lastActivityTime.get(guildId);
if (lastActivity == null || lastActivity < thresholdTime) {
GuildMusicManager manager = entry.getValue();
// Проверяем, что плеер не активен
if (manager.getPlayer().getPlayingTrack() == null) {
logger.info("Удаление неактивного GuildMusicManager для гильдии ID: {}", guildId);
manager.getPlayer().destroy();
lastActivityTime.remove(guildId);
return true;
}
}
return false;
});
}
/**
* Принудительно удаляет GuildMusicManager для гильдии
*/
public synchronized void removeGuildAudioPlayer(long guildId) {
GuildMusicManager manager = musicManagers.remove(guildId);
if (manager != null) {
manager.getPlayer().destroy();
lastActivityTime.remove(guildId);
logger.debug("GuildMusicManager удалён для гильдии ID: {}", guildId);
}
}
/**
* Получить или создать TtsQueue для гильдии
*/
public TtsQueue getTtsQueue(Guild guild) {
long guildId = guild.getIdLong();
return ttsQueues.computeIfAbsent(guildId, id -> {
logger.info("Создана новая TtsQueue для гильдии: {}", guild.getName());
return new TtsQueue();
});
}
/**
* Добавить TTS запрос в очередь
*/
public void enqueueTts(Guild guild, String text, net.dv8tion.jda.api.entities.channel.middleman.MessageChannel channel) {
GuildMusicManager musicManager = getGuildAudioPlayer(guild);
TtsQueue queue = getTtsQueue(guild);
TtsRequest request = new TtsRequest(this, musicManager, guild, text, channel);
queue.enqueue(request);
logger.info("TTS запрос добавлен для гильдии: {} (очередь: {})",
guild.getName(), queue.getQueueSize());
}
public AudioPlayerManager getPlayerManager() {
return playerManager;
}
/**
* Завершает работу AudioManager
*/
public void shutdown() {
logger.info("Завершение работы AudioManager...");
// Останавливаем все TTS очереди
ttsQueues.values().forEach(TtsQueue::shutdown);
ttsQueues.clear();
// Останавливаем scheduler
cleanupScheduler.shutdown();
try {
if (!cleanupScheduler.awaitTermination(5, TimeUnit.SECONDS)) {
cleanupScheduler.shutdownNow();
}
} catch (InterruptedException e) {
cleanupScheduler.shutdownNow();
Thread.currentThread().interrupt();
}
// Уничтожаем все плееры
musicManagers.values().forEach(manager -> manager.getPlayer().destroy());
musicManagers.clear();
lastActivityTime.clear();
logger.info("AudioManager завершён");
}
}

View file

@ -1,36 +0,0 @@
package creator.loki.audio;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.track.playback.MutableAudioFrame;
import net.dv8tion.jda.api.audio.AudioSendHandler;
import java.nio.ByteBuffer;
public class AudioPlayerSendHandler implements AudioSendHandler {
private final AudioPlayer audioPlayer;
private final ByteBuffer buffer;
private final MutableAudioFrame frame;
public AudioPlayerSendHandler(AudioPlayer audioPlayer) {
this.audioPlayer = audioPlayer;
this.buffer = ByteBuffer.allocate(1024);
this.frame = new MutableAudioFrame();
this.frame.setBuffer(buffer);
}
@Override
public boolean canProvide() {
return audioPlayer.provide(frame);
}
@Override
public ByteBuffer provide20MsAudio() {
buffer.flip();
return buffer;
}
@Override
public boolean isOpus() {
return true;
}
}

View file

@ -1,52 +0,0 @@
package creator.loki.audio;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
import net.dv8tion.jda.api.entities.Guild;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GuildMusicManager {
private static final Logger logger = LoggerFactory.getLogger(GuildMusicManager.class);
private final AudioPlayer player;
private final AudioPlayerSendHandler sendHandler;
private Guild guild;
private boolean autoDisconnectEnabled = false;
public GuildMusicManager(AudioPlayerManager manager) {
this.player = manager.createPlayer();
this.sendHandler = new AudioPlayerSendHandler(player);
}
public void setGuild(Guild guild) {
this.guild = guild;
}
public void enableAutoDisconnect() {
if (autoDisconnectEnabled) {
return; // Уже включено
}
autoDisconnectEnabled = true;
player.addListener(new AudioEventAdapter() {
@Override
public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {
if (endReason.mayStartNext && guild != null) {
logger.info("TTS трек завершён, отключаемся от голосового канала");
guild.getAudioManager().closeAudioConnection();
}
}
});
}
public AudioPlayer getPlayer() {
return player;
}
public AudioPlayerSendHandler getSendHandler() {
return sendHandler;
}
}

View file

@ -1,61 +0,0 @@
package creator.loki.audio;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class TtsAudioSource {
private static final Logger logger = LoggerFactory.getLogger(TtsAudioSource.class);
private static final String TTS_API = "https://translate.google.com/translate_tts";
public static void playTts(AudioManager audioManager, GuildMusicManager musicManager,
String text, MessageChannel channel) {
try {
// Генерируем URL для TTS
String encodedText = URLEncoder.encode(text, StandardCharsets.UTF_8);
String ttsUrl = TTS_API + "?ie=UTF-8&client=tw-ob&tl=ru&q=" + encodedText;
logger.info("Загрузка TTS: {}", ttsUrl);
// Загружаем напрямую из URL
audioManager.getPlayerManager().loadItem(ttsUrl, new AudioLoadResultHandler() {
@Override
public void trackLoaded(AudioTrack track) {
logger.info("TTS трек загружен успешно");
musicManager.getPlayer().playTrack(track);
}
@Override
public void playlistLoaded(AudioPlaylist playlist) {
logger.info("TTS плейлист загружен");
if (!playlist.getTracks().isEmpty()) {
trackLoaded(playlist.getTracks().get(0));
}
}
@Override
public void noMatches() {
logger.error("TTS: Не найдено совпадений");
channel.sendMessage("❌ Не удалось загрузить TTS").queue();
}
@Override
public void loadFailed(FriendlyException exception) {
logger.error("TTS: Ошибка загрузки", exception);
channel.sendMessage("❌ Ошибка TTS: " + exception.getMessage()).queue();
}
});
} catch (Exception e) {
logger.error("TTS: Ошибка генерации", e);
channel.sendMessage("❌ Ошибка генерации TTS: " + e.getMessage()).queue();
}
}
}

View file

@ -1,176 +0,0 @@
package creator.loki.audio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.*;
/**
* Очередь TTS запросов с автоматическим отключением через 30 секунд бездействия
*/
public class TtsQueue {
private static final Logger logger = LoggerFactory.getLogger(TtsQueue.class);
private static final int DISCONNECT_DELAY_SECONDS = 30;
private final BlockingQueue<TtsRequest> queue = new LinkedBlockingQueue<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final ExecutorService processor = Executors.newSingleThreadExecutor();
private ScheduledFuture<?> disconnectTask;
private volatile boolean isProcessing = false;
private volatile boolean isShutdown = false;
public TtsQueue() {
startProcessor();
}
/**
* Добавить TTS запрос в очередь
*/
public void enqueue(TtsRequest request) {
if (isShutdown) {
logger.warn("TtsQueue уже завершена, запрос отклонён");
return;
}
queue.offer(request);
cancelDisconnectTimer();
logger.info("TTS запрос добавлен в очередь. Размер очереди: {}", queue.size());
}
/**
* Запустить обработчик очереди
*/
private void startProcessor() {
processor.submit(() -> {
while (!isShutdown) {
try {
TtsRequest request = queue.poll(1, TimeUnit.SECONDS);
if (request != null) {
processRequest(request);
} else if (!isProcessing && queue.isEmpty()) {
scheduleDisconnect(request != null ? request : getLastRequest());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
logger.error("Ошибка обработки TTS запроса", e);
}
}
});
}
/**
* Обработать один TTS запрос
*/
private void processRequest(TtsRequest request) {
isProcessing = true;
cancelDisconnectTimer();
try {
logger.info("Обработка TTS: {}", request.getText());
request.process();
// Ждём завершения воспроизведения
waitForPlaybackEnd(request);
} catch (Exception e) {
logger.error("Ошибка воспроизведения TTS", e);
} finally {
isProcessing = false;
}
}
/**
* Ждать завершения воспроизведения
*/
private void waitForPlaybackEnd(TtsRequest request) {
try {
// Ждём максимум 30 секунд
for (int i = 0; i < 30; i++) {
if (!request.isPlaying()) {
break;
}
Thread.sleep(1000);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* Запланировать отключение через 30 секунд
*/
private void scheduleDisconnect(TtsRequest lastRequest) {
if (disconnectTask != null && !disconnectTask.isDone()) {
return; // Уже запланировано
}
disconnectTask = scheduler.schedule(() -> {
if (queue.isEmpty() && !isProcessing && lastRequest != null) {
logger.info("30 секунд бездействия, отключаемся от голосового канала");
lastRequest.disconnect();
}
}, DISCONNECT_DELAY_SECONDS, TimeUnit.SECONDS);
logger.info("Запланировано отключение через {} секунд", DISCONNECT_DELAY_SECONDS);
}
/**
* Отменить таймер отключения
*/
private void cancelDisconnectTimer() {
if (disconnectTask != null && !disconnectTask.isDone()) {
disconnectTask.cancel(false);
logger.debug("Таймер отключения отменён");
}
}
/**
* Получить последний запрос (для отключения)
*/
private TtsRequest getLastRequest() {
return queue.peek();
}
/**
* Завершить работу очереди
*/
public void shutdown() {
isShutdown = true;
cancelDisconnectTimer();
scheduler.shutdown();
processor.shutdown();
try {
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
if (!processor.awaitTermination(5, TimeUnit.SECONDS)) {
processor.shutdownNow();
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
processor.shutdownNow();
Thread.currentThread().interrupt();
}
logger.info("TtsQueue завершена");
}
/**
* Получить размер очереди
*/
public int getQueueSize() {
return queue.size();
}
/**
* Проверить, обрабатывается ли запрос
*/
public boolean isProcessing() {
return isProcessing;
}
}

View file

@ -1,158 +0,0 @@
package creator.loki.audio;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Один TTS запрос
*/
public class TtsRequest {
private static final Logger logger = LoggerFactory.getLogger(TtsRequest.class);
private static final String TTS_API = "https://translate.google.com/translate_tts";
private final AudioManager audioManager;
private final GuildMusicManager musicManager;
private final Guild guild;
private final String text;
private final MessageChannel channel;
private final CountDownLatch loadLatch = new CountDownLatch(1);
private volatile boolean isPlaying = false;
private volatile AudioTrack currentTrack;
public TtsRequest(AudioManager audioManager, GuildMusicManager musicManager,
Guild guild, String text, MessageChannel channel) {
this.audioManager = audioManager;
this.musicManager = musicManager;
this.guild = guild;
this.text = text;
this.channel = channel;
}
/**
* Обработать запрос (загрузить и воспроизвести)
*/
public void process() {
try {
String encodedText = URLEncoder.encode(text, StandardCharsets.UTF_8);
String ttsUrl = TTS_API + "?ie=UTF-8&client=tw-ob&tl=ru&q=" + encodedText;
logger.info("Загрузка TTS: {}", ttsUrl);
audioManager.getPlayerManager().loadItem(ttsUrl, new AudioLoadResultHandler() {
@Override
public void trackLoaded(AudioTrack track) {
logger.info("TTS трек загружен: {}", text);
currentTrack = track;
isPlaying = true;
AudioPlayer player = musicManager.getPlayer();
player.playTrack(track);
loadLatch.countDown();
}
@Override
public void playlistLoaded(AudioPlaylist playlist) {
if (!playlist.getTracks().isEmpty()) {
trackLoaded(playlist.getTracks().get(0));
} else {
loadFailed(new FriendlyException("Плейлист пуст",
FriendlyException.Severity.COMMON, null));
}
}
@Override
public void noMatches() {
logger.error("TTS: Не найдено совпадений для '{}'", text);
channel.sendMessage("❌ Не удалось загрузить TTS").queue();
loadLatch.countDown();
}
@Override
public void loadFailed(FriendlyException exception) {
logger.error("TTS: Ошибка загрузки '{}'", text, exception);
channel.sendMessage("❌ Ошибка TTS: " + exception.getMessage()).queue();
loadLatch.countDown();
}
});
// Ждём загрузки (максимум 10 секунд)
if (!loadLatch.await(10, TimeUnit.SECONDS)) {
logger.warn("TTS: Таймаут загрузки для '{}'", text);
return;
}
// Ждём завершения воспроизведения
waitForPlaybackEnd();
} catch (Exception e) {
logger.error("TTS: Ошибка обработки '{}'", text, e);
channel.sendMessage("❌ Ошибка TTS: " + e.getMessage()).queue();
}
}
/**
* Ждать завершения воспроизведения
*/
private void waitForPlaybackEnd() {
AudioPlayer player = musicManager.getPlayer();
// Ждём максимум 30 секунд
for (int i = 0; i < 30; i++) {
AudioTrack playingTrack = player.getPlayingTrack();
if (playingTrack == null || playingTrack != currentTrack) {
isPlaying = false;
logger.info("TTS воспроизведение завершено: {}", text);
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
isPlaying = false;
}
/**
* Отключиться от голосового канала
*/
public void disconnect() {
if (guild != null) {
guild.getAudioManager().closeAudioConnection();
logger.info("Отключились от голосового канала в гильдии: {}", guild.getName());
}
}
/**
* Проверить, воспроизводится ли трек
*/
public boolean isPlaying() {
return isPlaying;
}
public String getText() {
return text;
}
public Guild getGuild() {
return guild;
}
}

View file

@ -1,4 +1,4 @@
package creator.loki.command.impl.stats;
package creator.loki.command.impl.stats.leaderboard;
import creator.loki.level.LevelSystem;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;

View file

@ -1,4 +1,4 @@
package creator.loki.command.impl.stats;
package creator.loki.command.impl.stats.leaderboard;
import net.dv8tion.jda.api.interactions.components.buttons.Button;

View file

@ -1,4 +1,4 @@
package creator.loki.command.impl.stats;
package creator.loki.command.impl.stats.leaderboard;
import creator.loki.embed.EmbedFactory;
import creator.loki.level.UserStats;

View file

@ -1,4 +1,4 @@
package creator.loki.command.impl.stats;
package creator.loki.command.impl.stats.leaderboard;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;

View file

@ -1,106 +0,0 @@
package creator.loki.command.impl.utility;
import creator.loki.audio.AudioManager;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedTemplates;
import creator.loki.util.CooldownManager;
import net.dv8tion.jda.api.entities.GuildVoiceState;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
public class TtsCommand implements Command {
private final CooldownManager cooldownManager;
private final AudioManager audioManager;
public TtsCommand(CooldownManager cooldownManager, AudioManager audioManager) {
this.cooldownManager = cooldownManager;
this.audioManager = audioManager;
}
@Override
public String getName() {
return "tts";
}
@Override
public String getDescription() {
return "Бот заходит в голосовой канал и произносит текст";
}
@Override
public String getUsage() {
return "tts <текст>";
}
@Override
public String getCategory() {
return "Развлечения";
}
@Override
public void execute(CommandContext context) {
if (context.getGuild() == null) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")
).queue();
return;
}
if (context.getArgs().isEmpty()) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Ошибка", "Укажи текст для TTS")
).queue();
return;
}
Member member = context.getAuthor();
if (member == null) return;
// Проверка cooldown (15 секунд)
if (cooldownManager.isOnCooldown(member.getId(), "tts")) {
long remaining = cooldownManager.getRemainingSeconds(member.getId(), "tts");
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error(
"Подожди!",
"Ты сможешь использовать TTS через " + remaining + " сек."
)
).queue();
return;
}
// Проверка, что пользователь в голосовом канале
GuildVoiceState voiceState = member.getVoiceState();
if (voiceState == null || !voiceState.inAudioChannel()) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Ошибка", "Ты должен быть в голосовом канале")
).queue();
return;
}
VoiceChannel voiceChannel = voiceState.getChannel().asVoiceChannel();
String text = String.join(" ", context.getArgs());
if (text.length() > 200) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Слишком длинно", "Максимум 200 символов")
).queue();
return;
}
// Подключаемся к голосовому каналу (если ещё не подключены)
net.dv8tion.jda.api.managers.AudioManager audioManagerJda = context.getGuild().getAudioManager();
if (!audioManagerJda.isConnected()) {
audioManagerJda.openAudioConnection(voiceChannel);
}
// Добавляем TTS в очередь
audioManager.enqueueTts(context.getGuild(), text, context.getChannel());
cooldownManager.applyCooldown(member.getId(), "tts", 15);
context.getChannel().sendMessageEmbeds(
EmbedTemplates.success("TTS", "🔊 Добавлено в очередь: " + text)
).queue();
}
}

View file

@ -1,50 +0,0 @@
package creator.loki.command.slash;
import creator.loki.command.SlashCommand;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import java.awt.Color;
/**
* Slash команда /avatar
*/
public class AvatarSlashCommand implements SlashCommand {
@Override
public void execute(SlashCommandInteractionEvent event) {
User targetUser = event.getOption("user") != null
? event.getOption("user").getAsUser()
: event.getUser();
String avatarUrl = targetUser.getEffectiveAvatarUrl() + "?size=512";
EmbedBuilder embed = new EmbedBuilder()
.setTitle("🖼️ Аватар " + targetUser.getName())
.setImage(avatarUrl)
.setColor(new Color(88, 101, 242))
.setFooter("Запросил: " + event.getUser().getName());
event.replyEmbeds(embed.build()).queue();
}
@Override
public CommandData getCommandData() {
return Commands.slash("avatar", "Показывает аватар пользователя")
.addOption(OptionType.USER, "user", "Пользователь (по умолчанию - вы)", false);
}
@Override
public String getName() {
return "avatar";
}
@Override
public String getDescription() {
return "Показывает аватар пользователя";
}
}

View file

@ -1,103 +0,0 @@
package creator.loki.command.slash;
import creator.loki.command.SlashCommand;
import creator.loki.embed.EmbedTemplates;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
public class BanSlashCommand implements SlashCommand {
@Override
public void execute(SlashCommandInteractionEvent event) {
if (event.getGuild() == null) {
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")).setEphemeral(true).queue();
return;
}
Member author = event.getMember();
if (author == null || !author.hasPermission(Permission.BAN_MEMBERS)) {
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "У вас нет прав на бан пользователей")).setEphemeral(true).queue();
return;
}
if (!event.getGuild().getSelfMember().hasPermission(Permission.BAN_MEMBERS)) {
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "У бота нет прав на бан пользователей")).setEphemeral(true).queue();
return;
}
Member target = event.getOption("user").getAsMember();
if (target == null) {
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Пользователь не найден")).setEphemeral(true).queue();
return;
}
if (target.getId().equals(author.getId())) {
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Вы не можете забанить самого себя")).setEphemeral(true).queue();
return;
}
if (target.getUser().isBot() && target.getId().equals(event.getGuild().getSelfMember().getId())) {
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Я не могу забанить самого себя")).setEphemeral(true).queue();
return;
}
if (!author.canInteract(target)) {
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "Вы не можете забанить этого пользователя")).setEphemeral(true).queue();
return;
}
if (!event.getGuild().getSelfMember().canInteract(target)) {
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "Я не могу забанить этого пользователя")).setEphemeral(true).queue();
return;
}
String reason = event.getOption("reason") != null
? event.getOption("reason").getAsString()
: "Причина не указана";
int deleteDays = event.getOption("delete_days") != null
? event.getOption("delete_days").getAsInt()
: 0;
event.deferReply().queue();
event.getGuild().ban(target, deleteDays, java.util.concurrent.TimeUnit.DAYS)
.reason(reason)
.queue(
success -> event.getHook().editOriginalEmbeds(
EmbedTemplates.success(
"Пользователь забанен",
"**Пользователь:** " + target.getUser().getAsTag() + "\n" +
"**Причина:** " + reason + "\n" +
"**Удалено сообщений за дней:** " + deleteDays + "\n" +
"**Модератор:** " + author.getAsMention()
)
).queue(),
error -> event.getHook().editOriginalEmbeds(
EmbedTemplates.error("Ошибка", "Не удалось забанить: " + error.getMessage())
).queue()
);
}
@Override
public CommandData getCommandData() {
return Commands.slash("ban", "Банит пользователя на сервере")
.addOption(OptionType.USER, "user", "Пользователь для бана", true)
.addOption(OptionType.STRING, "reason", "Причина бана", false)
.addOption(OptionType.INTEGER, "delete_days", "Удалить сообщения за N дней (0-7)", false);
}
@Override
public String getName() {
return "ban";
}
@Override
public String getDescription() {
return "Банит пользователя на сервере";
}
}

View file

@ -1,74 +0,0 @@
package creator.loki.command.slash;
import creator.loki.command.SlashCommand;
import creator.loki.embed.EmbedTemplates;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
public class ClearSlashCommand implements SlashCommand {
@Override
public void execute(SlashCommandInteractionEvent event) {
if (event.getGuild() == null) {
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")).setEphemeral(true).queue();
return;
}
Member author = event.getMember();
if (author == null || !author.hasPermission(Permission.MESSAGE_MANAGE)) {
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "У вас нет прав на управление сообщениями")).setEphemeral(true).queue();
return;
}
if (!event.getGuild().getSelfMember().hasPermission(Permission.MESSAGE_MANAGE)) {
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "У бота нет прав на управление сообщениями")).setEphemeral(true).queue();
return;
}
int amount = event.getOption("amount").getAsInt();
if (amount < 1 || amount > 100) {
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Количество должно быть от 1 до 100")).setEphemeral(true).queue();
return;
}
event.deferReply(true).queue();
TextChannel channel = event.getChannel().asTextChannel();
channel.getHistory().retrievePast(amount).queue(
messages -> {
channel.purgeMessages(messages);
event.getHook().editOriginalEmbeds(
EmbedTemplates.success(
"Сообщения удалены",
"Удалено сообщений: " + messages.size()
)
).queue();
},
error -> event.getHook().editOriginalEmbeds(
EmbedTemplates.error("Ошибка", "Не удалось удалить сообщения: " + error.getMessage())
).queue()
);
}
@Override
public CommandData getCommandData() {
return Commands.slash("clear", "Удаляет указанное количество сообщений")
.addOption(OptionType.INTEGER, "amount", "Количество сообщений (1-100)", true);
}
@Override
public String getName() {
return "clear";
}
@Override
public String getDescription() {
return "Удаляет указанное количество сообщений";
}
}

View file

@ -1,42 +0,0 @@
package creator.loki.command.slash;
import creator.loki.command.SlashCommand;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import java.awt.Color;
import java.util.Random;
public class CoinSlashCommand implements SlashCommand {
private static final Random random = new Random();
@Override
public void execute(SlashCommandInteractionEvent event) {
boolean isHeads = random.nextBoolean();
String result = isHeads ? "Орёл 🦅" : "Решка 🪙";
EmbedBuilder embed = new EmbedBuilder()
.setTitle("🪙 Подбрасывание монетки")
.setDescription("Результат: **" + result + "**")
.setColor(isHeads ? new Color(255, 215, 0) : new Color(192, 192, 192));
event.replyEmbeds(embed.build()).queue();
}
@Override
public CommandData getCommandData() {
return Commands.slash("coin", "Подбросить монетку");
}
@Override
public String getName() {
return "coin";
}
@Override
public String getDescription() {
return "Подбросить монетку";
}
}

View file

@ -1,54 +0,0 @@
package creator.loki.command.slash;
import creator.loki.command.SlashCommand;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import java.awt.Color;
import java.util.Random;
public class DiceSlashCommand implements SlashCommand {
private static final Random random = new Random();
@Override
public void execute(SlashCommandInteractionEvent event) {
int result = random.nextInt(6) + 1;
String emoji = getDiceEmoji(result);
EmbedBuilder embed = new EmbedBuilder()
.setTitle("🎲 Бросок кубика")
.setDescription("Выпало: **" + result + "** " + emoji)
.setColor(new Color(88, 101, 242));
event.replyEmbeds(embed.build()).queue();
}
private String getDiceEmoji(int number) {
return switch (number) {
case 1 -> "⚀";
case 2 -> "⚁";
case 3 -> "⚂";
case 4 -> "⚃";
case 5 -> "⚄";
case 6 -> "⚅";
default -> "🎲";
};
}
@Override
public CommandData getCommandData() {
return Commands.slash("dice", "Бросить кубик");
}
@Override
public String getName() {
return "dice";
}
@Override
public String getDescription() {
return "Бросить кубик";
}
}

View file

@ -1,73 +0,0 @@
package creator.loki.command.slash;
import creator.loki.command.SlashCommand;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import java.awt.Color;
import java.util.Random;
public class EightBallSlashCommand implements SlashCommand {
private static final Random random = new Random();
private static final String[] ANSWERS = {
"Бесспорно ✅",
"Предрешено ✅",
"Никаких сомнений ✅",
"Определённо да ✅",
"Можешь быть уверен в этом ✅",
"Мне кажется — да ✅",
"Вероятнее всего ✅",
"Хорошие перспективы ✅",
"Знаки говорят — да ✅",
"Да ✅",
"Пока не ясно 🤔",
"Спроси позже 🤔",
"Лучше не рассказывать 🤔",
"Сейчас нельзя предсказать 🤔",
"Сконцентрируйся и спроси опять 🤔",
"Даже не думай ❌",
"Мой ответ — нет ❌",
"По моим данным — нет ❌",
"Перспективы не очень хорошие ❌",
"Весьма сомнительно ❌"
};
@Override
public void execute(SlashCommandInteractionEvent event) {
var questionOption = event.getOption("question");
if (questionOption == null) {
event.reply("Задайте вопрос!").setEphemeral(true).queue();
return;
}
String question = questionOption.getAsString();
String answer = ANSWERS[random.nextInt(ANSWERS.length)];
EmbedBuilder embed = new EmbedBuilder()
.setTitle("🎱 Магический шар")
.addField("Вопрос", question, false)
.addField("Ответ", answer, false)
.setColor(new Color(88, 101, 242));
event.replyEmbeds(embed.build()).queue();
}
@Override
public CommandData getCommandData() {
return Commands.slash("8ball", "Задать вопрос магическому шару")
.addOption(OptionType.STRING, "question", "Ваш вопрос", true);
}
@Override
public String getName() {
return "8ball";
}
@Override
public String getDescription() {
return "Задать вопрос магическому шару";
}
}

View file

@ -1,48 +0,0 @@
package creator.loki.command.slash;
import creator.loki.command.SlashCommand;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import java.awt.Color;
public class HugSlashCommand implements SlashCommand {
@Override
public void execute(SlashCommandInteractionEvent event) {
var targetOption = event.getOption("user");
if (targetOption == null) {
event.reply("Укажите пользователя!").setEphemeral(true).queue();
return;
}
var target = targetOption.getAsUser();
var author = event.getUser();
EmbedBuilder embed = new EmbedBuilder()
.setDescription(author.getAsMention() + " обнимает " + target.getAsMention() + " 🤗")
.setColor(new Color(255, 182, 193))
.setImage("https://media.tenor.com/images/hug.gif");
event.replyEmbeds(embed.build()).queue();
}
@Override
public CommandData getCommandData() {
return Commands.slash("hug", "Обнять пользователя")
.addOption(OptionType.USER, "user", "Кого обнять", true);
}
@Override
public String getName() {
return "hug";
}
@Override
public String getDescription() {
return "Обнять пользователя";
}
}

View file

@ -1,97 +0,0 @@
package creator.loki.command.slash;
import creator.loki.command.SlashCommand;
import creator.loki.embed.EmbedTemplates;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
public class KickSlashCommand implements SlashCommand {
@Override
public void execute(SlashCommandInteractionEvent event) {
if (event.getGuild() == null) {
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")).setEphemeral(true).queue();
return;
}
Member author = event.getMember();
if (author == null || !author.hasPermission(Permission.KICK_MEMBERS)) {
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "У вас нет прав на кик пользователей")).setEphemeral(true).queue();
return;
}
if (!event.getGuild().getSelfMember().hasPermission(Permission.KICK_MEMBERS)) {
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "У бота нет прав на кик пользователей")).setEphemeral(true).queue();
return;
}
Member target = event.getOption("user").getAsMember();
if (target == null) {
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Пользователь не найден")).setEphemeral(true).queue();
return;
}
if (target.getId().equals(author.getId())) {
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Вы не можете кикнуть самого себя")).setEphemeral(true).queue();
return;
}
if (target.getUser().isBot() && target.getId().equals(event.getGuild().getSelfMember().getId())) {
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Я не могу кикнуть самого себя")).setEphemeral(true).queue();
return;
}
if (!author.canInteract(target)) {
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "Вы не можете кикнуть этого пользователя")).setEphemeral(true).queue();
return;
}
if (!event.getGuild().getSelfMember().canInteract(target)) {
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "Я не могу кикнуть этого пользователя")).setEphemeral(true).queue();
return;
}
String reason = event.getOption("reason") != null
? event.getOption("reason").getAsString()
: "Причина не указана";
event.deferReply().queue();
event.getGuild().kick(target)
.reason(reason)
.queue(
success -> event.getHook().editOriginalEmbeds(
EmbedTemplates.success(
"Пользователь кикнут",
"**Пользователь:** " + target.getUser().getAsTag() + "\n" +
"**Причина:** " + reason + "\n" +
"**Модератор:** " + author.getAsMention()
)
).queue(),
error -> event.getHook().editOriginalEmbeds(
EmbedTemplates.error("Ошибка", "Не удалось кикнуть: " + error.getMessage())
).queue()
);
}
@Override
public CommandData getCommandData() {
return Commands.slash("kick", "Кикает пользователя с сервера")
.addOption(OptionType.USER, "user", "Пользователь для кика", true)
.addOption(OptionType.STRING, "reason", "Причина кика", false);
}
@Override
public String getName() {
return "kick";
}
@Override
public String getDescription() {
return "Кикает пользователя с сервера";
}
}

View file

@ -1,58 +0,0 @@
package creator.loki.command.slash;
import creator.loki.command.SlashCommand;
import creator.loki.metrics.MetricsCollector;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import java.awt.Color;
/**
* Slash команда /metrics
*/
public class MetricsSlashCommand implements SlashCommand {
private final MetricsCollector metrics;
public MetricsSlashCommand(MetricsCollector metrics) {
this.metrics = metrics;
}
@Override
public void execute(SlashCommandInteractionEvent event) {
// Только администраторы
if (event.getMember() == null || !event.getMember().hasPermission(Permission.ADMINISTRATOR)) {
event.reply("❌ Эта команда доступна только администраторам.")
.setEphemeral(true)
.queue();
return;
}
String stats = metrics.getFullStats();
EmbedBuilder embed = new EmbedBuilder()
.setTitle("📊 Метрики бота")
.setDescription("```\n" + stats + "\n```")
.setColor(new Color(88, 101, 242))
.setFooter("Slash команда");
event.replyEmbeds(embed.build()).setEphemeral(true).queue();
}
@Override
public CommandData getCommandData() {
return Commands.slash("metrics", "Показывает метрики и статистику бота (только для админов)");
}
@Override
public String getName() {
return "metrics";
}
@Override
public String getDescription() {
return "Показывает метрики и статистику бота";
}
}

View file

@ -1,50 +0,0 @@
package creator.loki.command.slash;
import creator.loki.command.SlashCommand;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import java.awt.Color;
/**
* Slash команда /ping
*/
public class PingSlashCommand implements SlashCommand {
@Override
public void execute(SlashCommandInteractionEvent event) {
JDA jda = event.getJDA();
long gatewayPing = jda.getGatewayPing();
event.deferReply().queue(hook -> {
long restPing = hook.getInteraction().getJDA().getRestPing().complete();
EmbedBuilder embed = new EmbedBuilder()
.setTitle("🏓 Pong!")
.setColor(new Color(88, 101, 242))
.addField("Gateway Ping", gatewayPing + " ms", true)
.addField("REST Ping", restPing + " ms", true)
.setFooter("Slash команда");
hook.editOriginalEmbeds(embed.build()).queue();
});
}
@Override
public CommandData getCommandData() {
return Commands.slash("ping", "Проверяет задержку бота");
}
@Override
public String getName() {
return "ping";
}
@Override
public String getDescription() {
return "Проверяет задержку бота";
}
}

View file

@ -1,79 +0,0 @@
package creator.loki.command.slash;
import creator.loki.command.SlashCommand;
import creator.loki.data.JsonDataStore;
import creator.loki.level.LevelSystem;
import creator.loki.profile.ProfileData;
import creator.loki.profile.ProfileService;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import java.awt.Color;
/**
* Slash команда /profile
*/
public class ProfileSlashCommand implements SlashCommand {
private final ProfileService profileService;
public ProfileSlashCommand(JsonDataStore dataStore) {
this.profileService = new ProfileService(dataStore);
}
@Override
public void execute(SlashCommandInteractionEvent event) {
Member targetMember = event.getOption("user") != null
? event.getOption("user").getAsMember()
: event.getMember();
if (targetMember == null) {
event.reply("❌ Не удалось получить информацию о пользователе.")
.setEphemeral(true)
.queue();
return;
}
ProfileData profile = profileService.getProfileData(targetMember, event.getGuild());
// Создаём прогресс-бар
int progress = (int) ((double) profile.getCurrentXp() / profile.getNextLevelXp() * 20);
String progressBar = "▰".repeat(progress) + "▱".repeat(20 - progress);
EmbedBuilder embed = new EmbedBuilder()
.setTitle("📊 Профиль " + profile.getUsername())
.setThumbnail(profile.getAvatarUrl())
.setColor(new Color(138, 80, 255))
.addField("🎯 Уровень", String.valueOf(profile.getLevel()), true)
.addField("⭐ Ранг", "#" + profile.getRank(), true)
.addField("💬 Сообщений", String.valueOf(profile.getMessagesCount()), true)
.addField("🎤 Голос (мин)", String.valueOf(profile.getVoiceMinutes()), true)
.addField("👍 Репутация", String.valueOf(profile.getReputation()), true)
.addField("📈 Прогресс",
progressBar + "\n" + profile.getCurrentXp() + " / " + profile.getNextLevelXp() + " XP",
false)
.setFooter("Запросил: " + event.getUser().getName());
event.replyEmbeds(embed.build()).queue();
}
@Override
public CommandData getCommandData() {
return Commands.slash("profile", "Показывает профиль пользователя")
.addOption(OptionType.USER, "user", "Пользователь (по умолчанию - вы)", false);
}
@Override
public String getName() {
return "profile";
}
@Override
public String getDescription() {
return "Показывает профиль пользователя";
}
}

View file

@ -1,62 +0,0 @@
package creator.loki.command.slash;
import creator.loki.command.SlashCommand;
import creator.loki.data.JsonDataStore;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import java.awt.Color;
import java.util.Map;
public class RankSlashCommand implements SlashCommand {
private final JsonDataStore dataStore;
public RankSlashCommand(JsonDataStore dataStore) {
this.dataStore = dataStore;
}
@Override
public void execute(SlashCommandInteractionEvent event) {
String userId = event.getUser().getId();
String levelKey = "level_" + userId;
String xpKey = "xp_" + userId;
Integer level = dataStore.getData(levelKey, Integer.class);
Integer xp = dataStore.getData(xpKey, Integer.class);
if (level == null) level = 1;
if (xp == null) xp = 0;
int xpNeeded = calculateXpForNextLevel(level);
EmbedBuilder embed = new EmbedBuilder()
.setTitle("📊 Ваш ранг")
.setDescription(event.getUser().getAsMention())
.addField("Уровень", String.valueOf(level), true)
.addField("Опыт", xp + " / " + xpNeeded, true)
.setColor(new Color(88, 101, 242))
.setThumbnail(event.getUser().getAvatarUrl());
event.replyEmbeds(embed.build()).queue();
}
private int calculateXpForNextLevel(int level) {
return 100 * level;
}
@Override
public CommandData getCommandData() {
return Commands.slash("rank", "Показать ваш текущий ранг и уровень");
}
@Override
public String getName() {
return "rank";
}
@Override
public String getDescription() {
return "Показать ваш текущий ранг и уровень";
}
}

View file

@ -1,70 +0,0 @@
package creator.loki.command.slash;
import creator.loki.command.SlashCommand;
import creator.loki.data.JsonDataStore;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import java.awt.Color;
public class RepSlashCommand implements SlashCommand {
private final JsonDataStore dataStore;
public RepSlashCommand(JsonDataStore dataStore) {
this.dataStore = dataStore;
}
@Override
public void execute(SlashCommandInteractionEvent event) {
var targetOption = event.getOption("user");
if (targetOption == null) {
event.reply("Укажите пользователя!").setEphemeral(true).queue();
return;
}
var target = targetOption.getAsUser();
var author = event.getUser();
if (target.getId().equals(author.getId())) {
event.reply("Вы не можете дать репутацию самому себе!").setEphemeral(true).queue();
return;
}
if (target.isBot()) {
event.reply("Нельзя давать репутацию ботам!").setEphemeral(true).queue();
return;
}
String key = "rep_" + target.getId();
Integer currentRep = dataStore.getData(key, Integer.class);
if (currentRep == null) currentRep = 0;
dataStore.setData(key, currentRep + 1);
EmbedBuilder embed = new EmbedBuilder()
.setTitle("✨ Репутация")
.setDescription(author.getAsMention() + " дал репутацию " + target.getAsMention())
.addField("Новая репутация", String.valueOf(currentRep + 1), false)
.setColor(new Color(0, 255, 0));
event.replyEmbeds(embed.build()).queue();
}
@Override
public CommandData getCommandData() {
return Commands.slash("rep", "Дать репутацию пользователю")
.addOption(OptionType.USER, "user", "Кому дать репутацию", true);
}
@Override
public String getName() {
return "rep";
}
@Override
public String getDescription() {
return "Дать репутацию пользователю";
}
}

View file

@ -1,80 +0,0 @@
package creator.loki.command.slash;
import creator.loki.command.SlashCommand;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import java.awt.Color;
import java.util.Random;
public class ShipSlashCommand implements SlashCommand {
private static final Random random = new Random();
@Override
public void execute(SlashCommandInteractionEvent event) {
var user1Option = event.getOption("user1");
var user2Option = event.getOption("user2");
if (user1Option == null || user2Option == null) {
event.reply("Укажите двух пользователей!").setEphemeral(true).queue();
return;
}
var user1 = user1Option.getAsUser();
var user2 = user2Option.getAsUser();
int percentage = random.nextInt(101);
String hearts = getHearts(percentage);
String comment = getComment(percentage);
EmbedBuilder embed = new EmbedBuilder()
.setTitle("💕 Ship-метр")
.setDescription(
user1.getAsMention() + " ❤️ " + user2.getAsMention() + "\n\n" +
hearts + " **" + percentage + "%**\n\n" +
comment
)
.setColor(getColor(percentage));
event.replyEmbeds(embed.build()).queue();
}
private String getHearts(int percentage) {
int hearts = percentage / 10;
return "❤️".repeat(hearts) + "🖤".repeat(10 - hearts);
}
private String getComment(int percentage) {
if (percentage >= 90) return "Идеальная пара! 💖";
if (percentage >= 70) return "Отличная совместимость! 💕";
if (percentage >= 50) return "Неплохие шансы! 💗";
if (percentage >= 30) return "Есть потенциал... 💔";
return "Лучше остаться друзьями 💔";
}
private Color getColor(int percentage) {
if (percentage >= 70) return new Color(255, 105, 180);
if (percentage >= 40) return new Color(255, 182, 193);
return new Color(128, 128, 128);
}
@Override
public CommandData getCommandData() {
return Commands.slash("ship", "Проверить совместимость двух пользователей")
.addOption(OptionType.USER, "user1", "Первый пользователь", true)
.addOption(OptionType.USER, "user2", "Второй пользователь", true);
}
@Override
public String getName() {
return "ship";
}
@Override
public String getDescription() {
return "Проверить совместимость двух пользователей";
}
}

View file

@ -1,64 +0,0 @@
package creator.loki.command.slash;
import creator.loki.command.SlashCommand;
import creator.loki.audio.AudioManager;
import creator.loki.embed.EmbedTemplates;
import net.dv8tion.jda.api.entities.GuildVoiceState;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
public class TtsSlashCommand implements SlashCommand {
private final AudioManager audioManager;
public TtsSlashCommand(AudioManager audioManager) {
this.audioManager = audioManager;
}
@Override
public void execute(SlashCommandInteractionEvent event) {
if (event.getGuild() == null) {
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")).setEphemeral(true).queue();
return;
}
Member member = event.getMember();
if (member == null) {
event.reply("Ошибка получения информации о пользователе").setEphemeral(true).queue();
return;
}
GuildVoiceState voiceState = member.getVoiceState();
if (voiceState == null || !voiceState.inAudioChannel()) {
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Вы должны находиться в голосовом канале")).setEphemeral(true).queue();
return;
}
String text = event.getOption("text").getAsString();
if (text.length() > 200) {
event.reply("Текст слишком длинный! Максимум 200 символов.").setEphemeral(true).queue();
return;
}
event.reply("🔊 Воспроизведение TTS...").queue();
audioManager.enqueueTts(event.getGuild(), text, event.getChannel());
}
@Override
public CommandData getCommandData() {
return Commands.slash("tts", "Озвучить текст в голосовом канале")
.addOption(OptionType.STRING, "text", "Текст для озвучки (макс. 200 символов)", true);
}
@Override
public String getName() {
return "tts";
}
@Override
public String getDescription() {
return "Озвучить текст в голосовом канале";
}
}

View file

@ -5,7 +5,7 @@ import creator.loki.audio.AudioManager;
import creator.loki.command.impl.setup.CounterSetupCommand;
import creator.loki.command.impl.setup.LogSetupCommand;
import creator.loki.command.impl.setup.VoiceSetupCommand;
import creator.loki.command.impl.stats.LeaderboardButtonListener;
import creator.loki.command.impl.stats.leaderboard.LeaderboardButtonListener;
import creator.loki.config.BotConfig;
import creator.loki.counter.CounterManager;
import creator.loki.data.JsonDataStore;

View file

@ -3,7 +3,6 @@ package creator.loki.di;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import creator.loki.audio.AudioManager;
import creator.loki.config.BotConfig;
import creator.loki.config.ConfigLoader;
import creator.loki.core.CommandRegistry;
@ -38,7 +37,6 @@ public class BotModule extends AbstractModule {
bind(CooldownManager.class).in(Singleton.class);
bind(GlobalRateLimiter.class).in(Singleton.class);
bind(MetricsCollector.class).in(Singleton.class);
bind(AudioManager.class).in(Singleton.class);
}
@Provides