diff --git a/TODO.md b/TODO.md index d7709ac..949b13d 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,313 @@ # TODO - План реализации функций -## 🎯 Основные задачи +## Правила разработки + +### Архитектура и код: +- KISS, DRY, SOLID принципы +- Файлы максимум 150-200 строк, стараться делать 100 строк +- В одной папке не больше 6 файлов +- Не создавать новые MD файлы без необходимости +- Разделение по доменам, а не по типам артефактов + +### Принципы структуры: +- Пакет = домен/фича, а не тип файла (не "команды/листенеры", а "profile/level/voice") +- Каждая фича содержит свои команды, сервисы, репозитории +- Единый слой персистентности через repository/ +- Интеграции в отдельном пакете integration/ + +--- + +## Рефакторинг структуры проекта + +### Целевая оценка: 7/10 + +**Проблемы:** +1. `util/` - мусорная корзина (CooldownManager, RateLimiter, NekosLifeApi) +2. Два механизма персистентности (data/ и database/) без абстракции +3. `command/impl/entertainment/` - 23 файла в одном пакете +4. `exception/` - избыточный пакет для 2 файлов +5. `embed/` - presentation layer оторван от domain +6. `listener/` - смешаны разные ответственности +7. Техническое разделение вместо доменного +8. **LevelSystem - God Class (5/10):** + - Нарушение SRP: хранение + кэширование + формулы + лидерборды + прогресс-бары + - Прямая зависимость от JsonDataStore (нет интерфейса Repository) + - O(n) на каждое сообщение (читает/пишет весь JSON) + - Кэш messageTimestamps не персистентный (теряется при рестарте) + - Рассогласование формул: комментарий говорит 200, код использует 100 + - LevelUpResult и LevelProgress - вложенные классы вместо отдельных + - getAllStats() возвращает mutable map (можно сломать консистентность) +9. **UserStats - анемичная модель (6/10):** + - Сеттеры позволяют нарушить инварианты (setLevel(999) без XP) + - incrementMessages() скрыто меняет lastMessageTime (side effect) + - Поля lastMessageTime и lastVoiceJoin не используются системой + - Нет equals/hashCode для использования в коллекциях + +### Целевая структура (по доменам): + +``` +src/main/java/creator/loki/ +├── App.java # Точка входа (временно, потом в bootstrap/) +│ +├── core/ # Ядро системы +│ ├── BotInitializer +│ ├── EventDispatcher +│ ├── registry/ # Регистрация команд +│ │ ├── CommandRegistry +│ │ ├── SlashCommandRegistry +│ │ ├── PrefixCommandRegistrar +│ │ └── SlashCommandRegistrar +│ ├── security/ # Безопасность и ограничения +│ │ ├── RateLimiter # (из util/) +│ │ └── CooldownManager # (из util/) +│ └── exception/ # Глобальная обработка ошибок +│ └── CommandExceptionHandler +│ +├── command/ # Базовые интерфейсы команд +│ ├── Command.java +│ ├── CommandContext.java +│ ├── CommandException.java # (из exception/) +│ ├── SlashCommand.java +│ ├── moderation/ # Модерация (3 файла) +│ │ ├── BanCommand +│ │ ├── KickCommand +│ │ └── ClearCommand +│ ├── entertainment/ # Развлечения +│ │ ├── games/ # Игры (12 файлов) +│ │ └── social/ # Взаимодействия (11 файлов) +│ ├── utility/ # Утилиты (5 файлов) +│ │ ├── HelpCommand +│ │ ├── PingCommand +│ │ ├── InfoCommand +│ │ ├── AvatarCommand +│ │ └── PollCommand +│ └── system/ # Системные команды +│ └── MetricsCommand # (из utility/) +│ +├── feature/ # Крупные фичи/домены +│ ├── level/ # Система уровней +│ │ ├── LevelSystem +│ │ ├── UserStats +│ │ ├── LevelUtils # (из profile/) +│ │ ├── RankCommand # (из command/stats/) +│ │ ├── StatsCommand # (из command/stats/) +│ │ ├── LevelUpNotifier # (из listener/) +│ │ └── VoiceTracker # (из listener/) +│ │ +│ ├── profile/ # Профили пользователей +│ │ ├── ProfileService +│ │ ├── ProfileGenerator +│ │ ├── ProfileData +│ │ ├── ProfileCommand # (из command/utility/) +│ │ └── ProfileEmbedFactory # (из embed/) +│ │ +│ ├── reputation/ # Система репутации +│ │ ├── ReputationService # (логика из RepCommand) +│ │ ├── RepCommand # (из command/stats/) +│ │ ├── MinusRepCommand # (из command/stats/) +│ │ └── RepTopCommand # (из command/stats/) +│ │ +│ ├── leaderboard/ # Таблица лидеров +│ │ ├── LeaderboardCommand # (NewLeaderboardCommand) +│ │ ├── LeaderboardService # (логика из команды) +│ │ ├── LeaderboardFormatter +│ │ ├── LeaderboardButtons +│ │ └── LeaderboardButtonListener +│ │ +│ ├── voice/ # Личные голосовые каналы +│ │ ├── VoiceChannelManager +│ │ ├── VoiceChannelData +│ │ ├── VoicePreset +│ │ ├── VoiceSetupCommand # (из command/setup/) +│ │ ├── VoiceChannelListener # (из listener/) +│ │ └── VoiceButtonHandler # (из listener/) +│ │ +│ ├── counter/ # Счетчик участников +│ │ ├── CounterManager +│ │ ├── DailyStats +│ │ ├── CounterSetupCommand # (из command/setup/) +│ │ └── MemberCounterListener # (из listener/) +│ │ +│ └── role/ # Кастомные роли +│ ├── CustomRoleManager +│ ├── CreateRoleCommand # (из command/roles/) +│ ├── DeleteRoleCommand # (из command/roles/) +│ ├── EditRoleCommand # (из command/roles/) +│ ├── MyRolesCommand # (из command/roles/) +│ └── RoleCommandValidator # (из command/roles/) +│ +├── event/ # Обработчики событий Discord +│ ├── ReadyListener # (из listener/) +│ ├── MessageListener # (из listener/) +│ ├── SlashCommandListener # (из listener/) +│ ├── HelpMenuHandler # (из listener/) +│ └── ModerationLogger # (из listener/) +│ +├── repository/ # Единый слой данных +│ ├── UserRepository # Интерфейс +│ ├── GuildConfigRepository # Интерфейс +│ └── impl/ +│ ├── JsonUserRepository # (из data/) +│ └── SqliteConfigRepository # (из database/) +│ +├── database/ # Инфраструктура БД +│ ├── Database +│ ├── DatabaseSchema +│ └── DatabaseMigration +│ +├── integration/ # Внешние API +│ └── nekos/ +│ └── NekosLifeApi # (из util/) +│ +├── config/ # Конфигурация +│ ├── BotConfig +│ └── ConfigLoader +│ +├── embed/ # Общие шаблоны embed +│ ├── EmbedFactory +│ └── EmbedTemplates +│ +├── metrics/ # Метрики +│ └── MetricsCollector +│ +└── di/ # Dependency Injection + └── BotModule +``` + +### План действий (приоритеты): + +#### Высокий приоритет (делаем сейчас): + +**1. Удалить ненужное** +- [x] Удалить `command/slash/` (дубликаты) +- [x] Удалить `audio/` (TTS не работает) +- [x] Удалить `TtsCommand.java` +- [ ] Удалить `SwapCommand.java` +- [ ] Удалить все импорты AudioManager + +**2. Реорганизовать util/ (мусорная корзина)** +- [ ] Создать `core/security/` +- [ ] Переместить `CooldownManager` → `core/security/` +- [ ] Переместить `GlobalRateLimiter` → `core/security/` +- [ ] Создать `integration/nekos/` +- [ ] Переместить `NekosLifeApi` → `integration/nekos/` +- [ ] Удалить пустую папку `util/` + +**3. Создать feature/level/ (система уровней)** +- [ ] Создать `feature/level/` +- [ ] Создать `feature/level/domain/` для доменных объектов +- [ ] Создать `feature/level/service/` для сервисов +- [ ] Создать `feature/level/repository/` для интерфейсов +- [ ] **Рефакторинг LevelSystem (God Class 5/10):** + - [ ] Вынести `LevelUpResult` в `feature/level/domain/LevelUpResult.java` + - [ ] Вынести `LevelProgress` в `feature/level/domain/LevelProgress.java` + - [ ] Создать `feature/level/domain/XpFormula.java` (инкапсуляция математики) + - [ ] Создать `feature/level/repository/UserStatsRepository.java` (интерфейс) + - [ ] Создать `feature/level/repository/JsonUserStatsRepository.java` (реализация) + - [ ] Создать `feature/level/service/LeaderboardService.java` (топы и агрегации) + - [ ] Исправить формулу: 100 vs 200 (рассогласование в коде) + - [ ] Убрать `messageTimestamps` ConcurrentHashMap, использовать `UserStats.lastMessageTime` + - [ ] Рефакторить `LevelSystem` → `feature/level/service/LevelService.java` +- [ ] **Рефакторинг UserStats (анемичная модель 6/10):** + - [ ] Сделать immutable (убрать сеттеры) + - [ ] Добавить методы `addXP()`, `recordMessage()`, `addVoiceTime()` + - [ ] Добавить `getLevel(XpFormula)` вместо внешних вычислений + - [ ] Добавить `equals()` и `hashCode()` + - [ ] Переименовать `incrementMessages()` → `recordMessage(long timestamp)` +- [ ] Переместить `level/LevelSystem` → `feature/level/service/LevelService.java` +- [ ] Переместить `level/UserStats` → `feature/level/domain/UserStats.java` +- [ ] Переместить `profile/LevelUtils` → `feature/level/` (если нужен) +- [ ] Переместить `command/impl/stats/RankCommand` → `feature/level/` +- [ ] Переместить `command/impl/stats/StatsCommand` → `feature/level/` +- [ ] Переместить `listener/LevelUpNotifier` → `feature/level/` +- [ ] Переместить `listener/VoiceTracker` → `feature/level/` +- [ ] Удалить пустую папку `level/` + +**4. Создать feature/voice/ (голосовые каналы)** +- [ ] Создать `feature/voice/` +- [ ] Переместить `voice/*` → `feature/voice/` +- [ ] Переместить `command/impl/setup/VoiceSetupCommand` → `feature/voice/` +- [ ] Переместить `listener/VoiceChannelListener` → `feature/voice/` +- [ ] Переместить `listener/VoiceButtonHandler` → `feature/voice/` +- [ ] Удалить пустую папку `voice/` + +**5. Создать feature/counter/ (счетчик)** +- [ ] Создать `feature/counter/` +- [ ] Переместить `counter/*` → `feature/counter/` +- [ ] Переместить `command/impl/setup/CounterSetupCommand` → `feature/counter/` +- [ ] Переместить `listener/MemberCounterListener` → `feature/counter/` +- [ ] Удалить пустую папку `counter/` + +**6. Создать feature/role/ (кастомные роли)** +- [ ] Создать `feature/role/` +- [ ] Переместить `role/CustomRoleManager` → `feature/role/` +- [ ] Переместить `command/impl/roles/*` → `feature/role/` +- [ ] Удалить пустые папки `role/` и `command/impl/roles/` + +#### Средний приоритет (потом): + +**7. Создать feature/reputation/** +- [ ] Создать `feature/reputation/` +- [ ] Создать `ReputationService` (вынести логику из команд) +- [ ] Переместить `command/impl/stats/RepCommand` → `feature/reputation/` +- [ ] Переместить `command/impl/stats/MinusRepCommand` → `feature/reputation/` +- [ ] Переместить `command/impl/stats/RepTopCommand` → `feature/reputation/` + +**8. Создать feature/leaderboard/** +- [ ] Создать `feature/leaderboard/` +- [ ] Переместить `command/impl/stats/leaderboard/*` → `feature/leaderboard/` +- [ ] Создать `LeaderboardService` +- [ ] Удалить пустую папку `command/impl/stats/` + +**9. Реорганизовать profile/** +- [ ] Создать `feature/profile/` +- [ ] Переместить `profile/*` → `feature/profile/` +- [ ] Переместить `command/impl/utility/ProfileCommand` → `feature/profile/` +- [ ] Создать `ProfileEmbedFactory` (вынести из команды) +- [ ] Удалить пустую папку `profile/` + +**10. Реорганизовать event/ (listener)** +- [ ] Создать `event/` +- [ ] Переместить `listener/ReadyListener` → `event/` +- [ ] Переместить `listener/MessageListener` → `event/` +- [ ] Переместить `listener/SlashCommandListener` → `event/` +- [ ] Переместить `listener/HelpMenuHandler` → `event/` +- [ ] Переместить `listener/ModerationLogger` → `event/` +- [ ] Удалить пустую папку `listener/` + +**11. Реорганизовать core/** +- [ ] Создать `core/registry/` +- [ ] Переместить `core/CommandRegistry` → `core/registry/` +- [ ] Переместить `core/SlashCommandRegistry` → `core/registry/` +- [ ] Переместить `core/PrefixCommandRegistrar` → `core/registry/` +- [ ] Переместить `core/SlashCommandRegistrar` → `core/registry/` +- [ ] Создать `core/exception/` +- [ ] Переместить `exception/CommandExceptionHandler` → `core/exception/` +- [ ] Переместить `exception/CommandException` → `command/` +- [ ] Удалить пустую папку `exception/` + +**12. Реорганизовать command/** +- [ ] Переименовать `command/impl/entertainment/interaction/` → `command/entertainment/social/` +- [ ] Переместить `command/impl/utility/MetricsCommand` → `command/system/` +- [ ] Переместить `command/impl/setup/LogSetupCommand` → `event/` (или удалить setup/) + +#### Низкий приоритет (опционально): + +**13. Создать repository/ (единый слой данных)** +- [ ] Создать `repository/` +- [ ] Создать интерфейсы `UserRepository`, `GuildConfigRepository` +- [ ] Создать `repository/impl/` +- [ ] Рефакторить `data/JsonDataStore` → `repository/impl/` +- [ ] Рефакторить `database/ConfigRepository` → `repository/impl/` + +**14. Обновить документацию** +- [ ] Обновить FEATURES.md (убрать TTS) +- [ ] Обновить README.md (новая структура) +- [ ] Добавить ARCHITECTURE.md (описание доменов) + +--- + +## Основные задачи ### 1. ⚡ Переделать систему опыта (XP System Rework) diff --git a/src/main/java/creator/loki/audio/AudioManager.java b/src/main/java/creator/loki/audio/AudioManager.java deleted file mode 100644 index c85bdce..0000000 --- a/src/main/java/creator/loki/audio/AudioManager.java +++ /dev/null @@ -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 musicManagers; - private final Map lastActivityTime; // guildId -> timestamp - private final Map 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 завершён"); - } -} diff --git a/src/main/java/creator/loki/audio/AudioPlayerSendHandler.java b/src/main/java/creator/loki/audio/AudioPlayerSendHandler.java deleted file mode 100644 index 1a2db11..0000000 --- a/src/main/java/creator/loki/audio/AudioPlayerSendHandler.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/creator/loki/audio/GuildMusicManager.java b/src/main/java/creator/loki/audio/GuildMusicManager.java deleted file mode 100644 index 0762784..0000000 --- a/src/main/java/creator/loki/audio/GuildMusicManager.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/creator/loki/audio/TtsAudioSource.java b/src/main/java/creator/loki/audio/TtsAudioSource.java deleted file mode 100644 index d2f11ea..0000000 --- a/src/main/java/creator/loki/audio/TtsAudioSource.java +++ /dev/null @@ -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(); - } - } -} diff --git a/src/main/java/creator/loki/audio/TtsQueue.java b/src/main/java/creator/loki/audio/TtsQueue.java deleted file mode 100644 index dc7dd73..0000000 --- a/src/main/java/creator/loki/audio/TtsQueue.java +++ /dev/null @@ -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 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; - } -} diff --git a/src/main/java/creator/loki/audio/TtsRequest.java b/src/main/java/creator/loki/audio/TtsRequest.java deleted file mode 100644 index 923f58d..0000000 --- a/src/main/java/creator/loki/audio/TtsRequest.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/creator/loki/command/impl/stats/LeaderboardButtonListener.java b/src/main/java/creator/loki/command/impl/stats/leaderboard/LeaderboardButtonListener.java similarity index 97% rename from src/main/java/creator/loki/command/impl/stats/LeaderboardButtonListener.java rename to src/main/java/creator/loki/command/impl/stats/leaderboard/LeaderboardButtonListener.java index af13d27..d286a6c 100644 --- a/src/main/java/creator/loki/command/impl/stats/LeaderboardButtonListener.java +++ b/src/main/java/creator/loki/command/impl/stats/leaderboard/LeaderboardButtonListener.java @@ -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; diff --git a/src/main/java/creator/loki/command/impl/stats/LeaderboardButtons.java b/src/main/java/creator/loki/command/impl/stats/leaderboard/LeaderboardButtons.java similarity index 89% rename from src/main/java/creator/loki/command/impl/stats/LeaderboardButtons.java rename to src/main/java/creator/loki/command/impl/stats/leaderboard/LeaderboardButtons.java index 64e87b8..0a0eeac 100644 --- a/src/main/java/creator/loki/command/impl/stats/LeaderboardButtons.java +++ b/src/main/java/creator/loki/command/impl/stats/leaderboard/LeaderboardButtons.java @@ -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; diff --git a/src/main/java/creator/loki/command/impl/stats/LeaderboardFormatter.java b/src/main/java/creator/loki/command/impl/stats/leaderboard/LeaderboardFormatter.java similarity index 98% rename from src/main/java/creator/loki/command/impl/stats/LeaderboardFormatter.java rename to src/main/java/creator/loki/command/impl/stats/leaderboard/LeaderboardFormatter.java index 25e1a4c..daaa446 100644 --- a/src/main/java/creator/loki/command/impl/stats/LeaderboardFormatter.java +++ b/src/main/java/creator/loki/command/impl/stats/leaderboard/LeaderboardFormatter.java @@ -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; diff --git a/src/main/java/creator/loki/command/impl/stats/NewLeaderboardCommand.java b/src/main/java/creator/loki/command/impl/stats/leaderboard/NewLeaderboardCommand.java similarity index 96% rename from src/main/java/creator/loki/command/impl/stats/NewLeaderboardCommand.java rename to src/main/java/creator/loki/command/impl/stats/leaderboard/NewLeaderboardCommand.java index 78a47c8..34b2939 100644 --- a/src/main/java/creator/loki/command/impl/stats/NewLeaderboardCommand.java +++ b/src/main/java/creator/loki/command/impl/stats/leaderboard/NewLeaderboardCommand.java @@ -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; diff --git a/src/main/java/creator/loki/command/impl/utility/TtsCommand.java b/src/main/java/creator/loki/command/impl/utility/TtsCommand.java deleted file mode 100644 index 8c0193c..0000000 --- a/src/main/java/creator/loki/command/impl/utility/TtsCommand.java +++ /dev/null @@ -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(); - } -} diff --git a/src/main/java/creator/loki/command/slash/AvatarSlashCommand.java b/src/main/java/creator/loki/command/slash/AvatarSlashCommand.java deleted file mode 100644 index 997b81d..0000000 --- a/src/main/java/creator/loki/command/slash/AvatarSlashCommand.java +++ /dev/null @@ -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 "Показывает аватар пользователя"; - } -} diff --git a/src/main/java/creator/loki/command/slash/BanSlashCommand.java b/src/main/java/creator/loki/command/slash/BanSlashCommand.java deleted file mode 100644 index 2fba328..0000000 --- a/src/main/java/creator/loki/command/slash/BanSlashCommand.java +++ /dev/null @@ -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 "Банит пользователя на сервере"; - } -} diff --git a/src/main/java/creator/loki/command/slash/ClearSlashCommand.java b/src/main/java/creator/loki/command/slash/ClearSlashCommand.java deleted file mode 100644 index 6be4825..0000000 --- a/src/main/java/creator/loki/command/slash/ClearSlashCommand.java +++ /dev/null @@ -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 "Удаляет указанное количество сообщений"; - } -} diff --git a/src/main/java/creator/loki/command/slash/CoinSlashCommand.java b/src/main/java/creator/loki/command/slash/CoinSlashCommand.java deleted file mode 100644 index aeacb4c..0000000 --- a/src/main/java/creator/loki/command/slash/CoinSlashCommand.java +++ /dev/null @@ -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 "Подбросить монетку"; - } -} diff --git a/src/main/java/creator/loki/command/slash/DiceSlashCommand.java b/src/main/java/creator/loki/command/slash/DiceSlashCommand.java deleted file mode 100644 index 12d02d1..0000000 --- a/src/main/java/creator/loki/command/slash/DiceSlashCommand.java +++ /dev/null @@ -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 "Бросить кубик"; - } -} diff --git a/src/main/java/creator/loki/command/slash/EightBallSlashCommand.java b/src/main/java/creator/loki/command/slash/EightBallSlashCommand.java deleted file mode 100644 index ba7a06f..0000000 --- a/src/main/java/creator/loki/command/slash/EightBallSlashCommand.java +++ /dev/null @@ -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 "Задать вопрос магическому шару"; - } -} diff --git a/src/main/java/creator/loki/command/slash/HugSlashCommand.java b/src/main/java/creator/loki/command/slash/HugSlashCommand.java deleted file mode 100644 index bfe5a74..0000000 --- a/src/main/java/creator/loki/command/slash/HugSlashCommand.java +++ /dev/null @@ -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 "Обнять пользователя"; - } -} diff --git a/src/main/java/creator/loki/command/slash/KickSlashCommand.java b/src/main/java/creator/loki/command/slash/KickSlashCommand.java deleted file mode 100644 index 83c272c..0000000 --- a/src/main/java/creator/loki/command/slash/KickSlashCommand.java +++ /dev/null @@ -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 "Кикает пользователя с сервера"; - } -} diff --git a/src/main/java/creator/loki/command/slash/MetricsSlashCommand.java b/src/main/java/creator/loki/command/slash/MetricsSlashCommand.java deleted file mode 100644 index 0335684..0000000 --- a/src/main/java/creator/loki/command/slash/MetricsSlashCommand.java +++ /dev/null @@ -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 "Показывает метрики и статистику бота"; - } -} diff --git a/src/main/java/creator/loki/command/slash/PingSlashCommand.java b/src/main/java/creator/loki/command/slash/PingSlashCommand.java deleted file mode 100644 index 4c6998e..0000000 --- a/src/main/java/creator/loki/command/slash/PingSlashCommand.java +++ /dev/null @@ -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 "Проверяет задержку бота"; - } -} diff --git a/src/main/java/creator/loki/command/slash/ProfileSlashCommand.java b/src/main/java/creator/loki/command/slash/ProfileSlashCommand.java deleted file mode 100644 index afe7c24..0000000 --- a/src/main/java/creator/loki/command/slash/ProfileSlashCommand.java +++ /dev/null @@ -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 "Показывает профиль пользователя"; - } -} diff --git a/src/main/java/creator/loki/command/slash/RankSlashCommand.java b/src/main/java/creator/loki/command/slash/RankSlashCommand.java deleted file mode 100644 index 3e48743..0000000 --- a/src/main/java/creator/loki/command/slash/RankSlashCommand.java +++ /dev/null @@ -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 "Показать ваш текущий ранг и уровень"; - } -} diff --git a/src/main/java/creator/loki/command/slash/RepSlashCommand.java b/src/main/java/creator/loki/command/slash/RepSlashCommand.java deleted file mode 100644 index 0ae85f1..0000000 --- a/src/main/java/creator/loki/command/slash/RepSlashCommand.java +++ /dev/null @@ -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 "Дать репутацию пользователю"; - } -} diff --git a/src/main/java/creator/loki/command/slash/ShipSlashCommand.java b/src/main/java/creator/loki/command/slash/ShipSlashCommand.java deleted file mode 100644 index cf15e45..0000000 --- a/src/main/java/creator/loki/command/slash/ShipSlashCommand.java +++ /dev/null @@ -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 "Проверить совместимость двух пользователей"; - } -} diff --git a/src/main/java/creator/loki/command/slash/TtsSlashCommand.java b/src/main/java/creator/loki/command/slash/TtsSlashCommand.java deleted file mode 100644 index 3197610..0000000 --- a/src/main/java/creator/loki/command/slash/TtsSlashCommand.java +++ /dev/null @@ -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 "Озвучить текст в голосовом канале"; - } -} diff --git a/src/main/java/creator/loki/core/BotInitializer.java b/src/main/java/creator/loki/core/BotInitializer.java index 52cd397..0b1f464 100644 --- a/src/main/java/creator/loki/core/BotInitializer.java +++ b/src/main/java/creator/loki/core/BotInitializer.java @@ -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; diff --git a/src/main/java/creator/loki/di/BotModule.java b/src/main/java/creator/loki/di/BotModule.java index 36636d2..a5da943 100644 --- a/src/main/java/creator/loki/di/BotModule.java +++ b/src/main/java/creator/loki/di/BotModule.java @@ -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