# TODO — Loki Bot v2.1 ## ✅ Что уже сделано | # | Статус | Проблема | Изменения | |---|--------|----------|-----------| | C1–C15 | ✅ | Предыдущие раунды | См. git history | | J21 | ✅ | Java 17 → 21 | `build.gradle`: source/target 21 | | A05 | ✅ | EventDispatcher: CachedThreadPool → VirtualThreadPool | `Executors.newVirtualThreadPerTaskExecutor()` | | A06 | ✅ | SwapCommand + RepTopCommand: `new Thread()` → `Thread.startVirtualThread()` | | | A25 | ✅ | CommandExceptionHandler: instanceof pattern matching | | | A26 | ✅ | ClearCommand: instanceof pattern matching | | | A27 | ✅ | DatabaseMigration: instanceof pattern matching | | | A28 | ✅ | InfoCommand: switch → arrow-switch | | | A29 | ✅ | RateCommand: `"⭐".repeat(rating)` + `"☆".repeat()` | | | A32 | ✅ | .collect(Collectors.toList()) → .toList() | SwapCommand, RepTopCommand, ChoiceOptionParser | | A33 | ✅ | HelpCategories: Map.ofEntries() вместо HashMap + unmodifiable | | | A02 | ✅ | RankCardGenerator: блокировка Event Thread | Исправлено (команды на virtual threads) | | A10 | ✅ | RankCardGenerator: утечка InputStream | try-with-resources в loadFont + loadBackground | | A11 | ✅ | RankCardGenerator: шрифты не кэшировались | ConcurrentHashMap cache | | A12 | ✅ | RankCardGenerator: SVG-иконки не работали | Заменены на геометрические фигуры (Java2D) | | A23 | ✅ | RankCardGenerator: throws Exception | loadFont безопасный | | A03 | ✅ | Database: race condition на connection | synchronized методы | | A13 | ✅ | NekosLifeApi: Thread.sleep в catch | Сон вынесен в retry loop | | A14 | ✅ | GenerateRatingsCommand: блокировка event-потока | `complete()` вместо `queue()` | | A35 | ✅ | GenerateRatingsCommand: опечатка | `Пропускаемботов` → `Пропускаем ботов` | | A04 | ✅ | PrefixCommandRegistrar: God Constructor (22 param) | Разбит на 4 per-feature CommandRegistrar, Multibinder, 1 param | | A21 | ✅ | BotModule: путь к БД хардкодом | Вынесен в .env (`DB_PATH`) + BotConfig | | A34 | ✅ | CommandCategories: мёртвый код | Уже удалён (старый пакет) | | A37 | ✅ | ProfileCommand: регистрация в PrefixCommandRegistrar | LevelCommandRegistrar | --- ## ♻️ Правила разработки - KISS, DRY, SOLID - Файлы ≤150–200 строк (в идеале ≤100) - В папке ≤6 файлов - Пакет = домен/фича, а не тип файла (`feature/level/`, а не `commands/listeners/`) - Каждая фича содержит свои команды, сервисы, репозитории - Единый слой персистентности через `repository/` - Интеграции в `integration/` --- ## 🔴 Проблемы (результаты аудита, которые ещё не исправлены) ### HIGH | # | Приоритет | Проблема | Статус | |---|-----------|----------|--------| | A07 | 🟠 **HIGH** | `HealthHttpServer` — single thread executor | ✅ Virtual threads | | A08 | 🟠 **HIGH** | Тестовое покрытие ~5% (~34 теста на 149 продакшен-файлов). Нужно 20-30% | ⏳ 268 тестов в 22 файлах | | A09 | 🟠 **HIGH** | Нет Docker / docker-compose | ✅ Dockerfile + docker-compose + .env.example | | A01 | ⚪ **INFO** | `.env` с токеном в git-истории (локальный репозиторий) | ➖ | ### MEDIUM | # | Приоритет | Проблема | Статус | |---|-----------|----------|--------| | A15 | 🟡 **MEDIUM** | ~33 файла команд в `command/impl/` — не перенесены в `feature/*/commands/` | ✅ 33 файла перенесены | | A16 | 🟡 **MEDIUM** | ~16 репозиториев в `database/` — не разнесены в `feature/*/repository/` | ✅ 13+3 репозитория перенесены | | A18 | 🟡 **MEDIUM** | `DatabaseMigration` — устаревший код для JSON→SQLite. Нужно оценить | ✅ Harmless, idempotent — keep as-is | | A19 | 🟡 **MEDIUM** | Нет статического анализа (checkstyle / spotbugs) | ✅ checkstyle.xml + spotbugs + 169 known violations | | A20 | 🟡 **MEDIUM** | Нет версионного каталога (`libs.versions.toml`) | ✅ gradle/libs.versions.toml, build.gradle переписан | | A22 | 🟡 **MEDIUM** | Нет пула соединений (HikariCP). Голый JDBC | ✅ HikariCP 5.1.0 + proxy-based prepare() | | A24 | 🟡 **MEDIUM** | VoiceTracker, RateLimiter, CooldownManager, MuteScheduler — ScheduledExecutorService. virtual threads? | ✅ Virtual threads | | A40 | 🟡 **MEDIUM** | `HelpMenuHandler` в `event/interaction/` — должен быть в `feature/help/` | ✅ Перенесён | ### LOW | # | Приоритет | Проблема | Статус | |---|-----------|----------|--------| | A30 | 🟢 **LOW** | SlowmodeCommand: ручная проверка границ → Math.clamp (другая семантика) | ➖ | | A31 | 🟢 **LOW** | ClearCommand: ручная проверка границ → Math.clamp (другая семантика) | ➖ | | A39 | 🟢 **LOW** | EmbedFactory: `set*` вместо `with*` для builder | ✅ Renamed in 43 files | | A36 | 🟢 **LOW** | RankCardGenerator: URI → URL, deprecated API | ✅ `URI.toURL()` → `new URL()` | --- ## 📋 План рефакторинга ### Этап 1: Java 21 ✅ (14 items) - [x] EventDispatcher: VirtualThreadPool - [x] instanceof pattern matching (3 файла) - [x] arrow-switch (InfoCommand) - [x] String.repeat() (RateCommand) - [x] .toList() (3 файла) - [x] Map.ofEntries() (HelpCategories) ### Этап 2: Рефакторинг PrefixCommandRegistrar ✅ - [x] Создан интерфейс `CommandRegistrar` - [x] 4 per-feature регистратора - [x] Guice Multibinder для сбора регистраторов - [x] 22 параметра → 1 (`Set`) ### Этап 3: Critical-баги ✅ - [x] RankCardGenerator: async + try-with-resources + cache + SVG - [x] Database: synchronized connection - [x] NekosLifeApi: чистый retry loop - [x] GenerateRatingsCommand: async через complete() - [x] DB path: вынесен в .env ### Этап 4: Перемещение файлов в feature-пакеты ✅ - [x] `command/impl/utility/` → `feature/*/commands/` - [x] `command/impl/entertainment/` → `feature/*/commands/` - [x] `command/entertainment/social/` → `feature/social/commands/` - [x] `database/*/` → `feature/*/repository/` - [x] `event/interaction/HelpMenuHandler` → `feature/help/` ### Этап 5: Тесты (цель: 20-30% покрытие) - [x] Тесты на репозитории (in-memory SQLite) — Config, ShipPair, UserStats, Ratings, Warnings, ActiveMutes - [x] Unit-тесты сервисов — MessageCooldownService, LevelService (был), XpFormula, ChoiceOptionParser - [x] Unit-тесты value objects — UserStats (был), LevelProgress, LevelUpResult, BotConfig - [x] Тесты embed — EmbedFactory (builder chaining), EmbedTemplates (factory methods) - [x] Тесты форматирования — LeaderboardFormatter - [ ] Unit-тесты на команды (с моками JDA) - [ ] Smoke-тест: все команды регистрируются без коллизий - [ ] Тесты на RankCardGenerator (изолированные, без сети) - [ ] Тесты на EventDispatcher (маршрутизация, rate limiting) ### Этап 6: Инфраструктура ✅ - [x] Dockerfile (multi-stage) - [x] docker-compose.yml - [x] Статический анализ: checkstyle / spotbugs - [x] Версионный каталог: `gradle/libs.versions.toml` - [x] HikariCP вместо голого JDBC ### Этап 7: Кодстайл - [x] EmbedFactory: `set*` → `with*` для builder - [ ] DatabaseMigration: оценить нужность - [x] HelpMenuHandler: переместить в feature/help/ --- ## 🚀 Новые фичи (Roadmap) ## 🚀 Экономика — ✅ реализовано ### Модель данных ```sql CREATE TABLE IF NOT EXISTS economy ( user_id TEXT PRIMARY KEY, cash INTEGER NOT NULL DEFAULT 0, -- на руках bank INTEGER NOT NULL DEFAULT 0, -- в банке total_earned INTEGER NOT NULL DEFAULT 0, total_spent INTEGER NOT NULL DEFAULT 0, last_daily TEXT, -- ISO date daily_streak INTEGER DEFAULT 0, fishing_luck INTEGER DEFAULT 0, -- stat lvl 0-5 efficiency INTEGER DEFAULT 0, -- stat lvl 0-5 last_fish TEXT, -- ISO datetime job TEXT, -- название работы last_work TEXT -- ISO datetime ); ``` ### Заработок | Механика | Доход | Кулдаун | |---|---|---| | Сообщение | 1–3 LC | 60с | | Голос | 1 LC / 2 мин | 2 мин | | Ежедневно | 50 LC + streak ×1.5 на 7й день | 24ч | | +rep (полученная) | 10 LC | — | | Рыбалка | base × speed_mul × (1 + fishing_luck×0.01) | 2ч | | Работа | random(range) × (1 + efficiency×0.01) | 6ч | | Блэкджек | ×1.5 при победе | — | | Слоты | ×0–×50 | — | ### Миниигры **Рыбалка** (`!fish easy/normal/hard`) - Сетка N×N (emoji + 🌊), бесплатно, неправильный ответ = 0 - Множитель скорости: <50% времени = ×1.5, <30% = ×2.0 - Стат `fishing_luck` (макс 5, стоимость 800×lvl) **Блэкджек** (`!bj `) - Дилер стоит на 16, ничья = проигрыш, BJ = ×1.5 - Кнопки Hit/Stand **Слоты** (`!slot `) - 🍒🍒🍒 ×3 (10%), 🔔🔔🔔 ×5 (5%), 💎💎💎 ×10 (2%), 👑👑👑 ×15 (1%), ⚡⚡⚡ ×50 (0.1%) **Работы** (`!job list`, `!job join`, `!work`) | Работа | Ур. | Доход | |---|---|---| | 🪓 Лесоруб | — | 15–30 | | ⛏️ Шахтёр | — | 20–40 | | 🎣 Рыбак | — | 25–50 | | 🍳 Повар | 15 | 30–60 | | 💻 Программист | 20 | 40–80 | | 🧑‍🚀 Космонавт | 35 | 50–100 | Стат `efficiency` (макс 5, стоимость 800×lvl) **Rob** (`!rob @user`) - 24ч кулдаун, 40% успех, крадёт до 15% cash (макс 10к) - Провал: штраф 10% cash (макс 5к) → жертве - Банк не трогается **Банк** (`!bank deposit/withdraw/balance`) - Без процентов, защита от rob ### Структура пакета ``` feature/economy/ EconomyModule.java EconomyCommandRegistrar.java domain/ EconomyAccount.java EconomyTransaction.java Job.java SlotCombination.java repository/ EconomyRepository.java SqlEconomyRepository.java service/ EconomyService.java FishingService.java BlackjackService.java SlotService.java JobService.java command/ core/ (Balance, Baltop, Pay, Daily) minigame/ (Fish, Blackjack, Slot, Job, Work) social/ (Rob) bank/ (Bank) ``` --- ### Приоритет 1 - [ ] Система достижений (Achievements) - [x] Система экономики (Economy) — **✅** ### Приоритет 2 - [x] Авто-модерация (3 варна = авто-мут на 1ч) - [x] Музыка (lavaplayer: play, skip, stop, queue, np, volume, leave) - [x] ~~Музыка — поиск с выбором (yt/sc/list + выбор номера)~~ - [x] Server Stats (участники, каналы, роли, бусты) - [x] Level Roles (роли за уровни) - [x] Красивые level-up embed с прогресс-баром ### Приоритет 3 - [ ] Статистика сервера — графики, CSV export - [ ] Система квестов - [ ] Wiki по установке и командам --- --- ## 🔴 Результаты аудита KISS/DRY/SOLID (2026-07-04) ### DRY — дублирование кода (критично) | # | Что дублируется | Где | Сколько раз | Статус | |---|----------------|-----|-------------|--------| | D1 | `getDeterministicValue()` — целиком | GayCommand, FatCommand, IqCommand, PpCommand, BoykisserCommand, MustacheCommand, RateCommand | 7 | ✅ RatingService | | D2 | Cooldown check boilerplate (5 строк) | Все entertainment команды | 10+ | ✅ RatingService | | D3 | `trunc()` / `err()` helpers | MuteCommand, WarnCommand, SwapAndCaseCommand, UnmuteCommand, PunishCommand | 5 | ✅ ModerationHelper | | D4 | Voice connect retry loop (цикл 10×200ms) | PlayCommand, RadioManager | 2 | | D5 | RatingConfig — 6 методов с идентичной структурой | RatingConfig.java | 6 | | D6 | Social команды (hug/kiss/slap/poke/tickle/cuddle/feed/pat) — шаблон | 10 файлов | 10 | ### SOLID | # | Нарушение | Файл | Описание | |---|-----------|------|----------| | S1 | SRP | BotModule.java | Биндит всё приложение | | S2 | SRP | SwapAndCaseCommand.java | Две разные команды в одном файле | | S3 | SRP | BotInitializer.java | Инициализирует 3 JDA, Lavalink, mutes, counters, health | | S4 | SRP | ShutdownManager.java | 9 зависимостей в конструкторе | | S5 | SRP | PlayCommand.java | execute() на 145 строк | | S6 | OCP | Meter-команды (gay/fat/iq/pp) | Добавить новую = копировать 90 строк | ✅ RatingService | | S7 | DIP | MuteCommand, WarnCommand | >5 конкретных репозиториев вместо интерфейсов | ### Файлы >150 строк | # | Файл | Строк | |---|------|-------| | F1 | PlayCommand.java | 219 | | F2 | QueueSlashHandler.java | 207 | | F3 | MusicBotControlHandler.java | 207 | | F4 | QueueCommands.java | 207 | | F5 | TrackScheduler.java | 202 | | F6 | MuteCommand.java | 199 | | F7 | ModerationLogger.java | 199 | | F8 | FeatureTables.java | 193 | | F9 | DatabaseMigration.java | 193 | | F10 | VoiceChannelManager.java | 187 | | F11 | ServerStatsService.java | 187 | | F12 | VoiceSetupPrefixCommand.java | 183 | | F13 | RadioManager.java | 182 | | F14 | MetricsCollector.java | 182 | | F15 | WarnCommand.java | 184 | | F16 | BotInitializer.java | 182 | | F17 | SlashHandler's (Queue/Filter) | >100 | | F18 | GenerateRatingsCommand.java | 172 | | F19 | BlackjackService.java | 172 | | F20 | SqlEconomyRepository.java | 169 | | F21 | GuildMusicManager.java | 167 | | F22 | ProfileCommand/RankCardGen | >150 | ### >3 файла в папке | # | Папка | Файлов | |---|-------|--------| | P1 | feature/entertainment/commands/ | 15 | | P2 | feature/social/commands/ | 10 | | P3 | feature/economy/command/minigame/ | 7 | | P4 | feature/level/ (все субпапки) | 21 | | P5 | feature/moderation/ (все субпакеты) | 18 | | P6 | feature/music/ (все субпакеты) | 22 | | P7 | feature/voice/ (все субпакеты) | 12 | ### Нет тестов | # | Фича | Файлов | |---|------|--------| | T1 | music | 20+ | | T2 | voice | 12 | | T3 | social | 10 | | T4 | role | 9 | | T5 | radio | 5 | | T6 | reputation | 4 | | T7 | counter | 7 | | T8 | admin | 1 | | T9 | help | 2 | | T10 | utility | 2 | | T11 | welcome | 2 | | T12 | profile | 5 | | T13 | serverstats | 1 | ## 📋 План рефакторинга (KISS/DRY/SOLID) ### Этап 8: Убить дублирование (DRY) — в процессе - [x] D1/D2: `RatingService` с `getDeterministicValue()`, cooldown, member parsing — 7 команд переписаны - [x] D3: `ModerationHelper` с `trunc()`/`err()` — MuteCommand, UnmuteCommand, WarnCommand, SwapAndCaseCommand, PunishCommand - [ ] D4: Вынести voice connect retry в утилиту - [ ] D5: RatingConfig — заменить 6 методов на data-driven (Map или config) - [ ] D6: Social команды — единый `SocialCommand` с параметром action ### Этап 9: SOLID - [ ] S1: Разбить `BotModule` на per-feature модули (FeatureModule → install в BotModule) - [ ] S2: Разделить `SwapAndCaseCommand` на два отдельных файла - [ ] S3: Выделить `MusicBotFactory`, `RadioBotFactory` из `BotInitializer` - [ ] S4: Уменьшить кол-во зависимостей `ShutdownManager` через события (EventBus) - [ ] S5: Разбить `PlayCommand.execute()` на методы - [ ] S6: Meter-команды сделать data-driven через `RatingService` - [ ] S7: Выделить интерфейсы для репозиториев/сервисов (DIP) ### Этап 10: Файлы ≤150 строк - [ ] F01: PlayCommand.java — разбить на методы/классы - [ ] F02-04: QueueSlashHandler, MusicBotControlHandler, QueueCommands — вынести общие части - [ ] F05: TrackScheduler.java — вывести FilterState в отд. файл или утилиту - [ ] F06: MuteCommand.java — разбить на subcommand handler'ы - [ ] F07: ModerationLogger.java — разбить по типам событий - [ ] F08-09: FeatureTables, DatabaseMigration — разбить по фичам - [ ] F10-12: Voice* — вынести логику в сервисы - [ ] F13: RadioManager.java — вынести voice connect в утилиту - [ ] F14: MetricsCollector.java — разбить по типам метрик - [ ] F15: WarnCommand.java — вынести auto-mute в отдельный сервис - [ ] F16: BotInitializer.java — вынести фабрики ботов - [ ] Остальные: разбить по SOLID ### Этап 11: ≤6 файлов в папке - [ ] P1: Entertainment команды — группировать по смыслу (coin/dice → `random/`, gay/iq/pp → `rating/`) - [ ] P2: Social команды — объединить в `SocialCommand` с параметром - [ ] P4: Level — разбить на commands/, service/, repository/ - [ ] P5: Moderation — разбить по поддоменам (commands/punish, commands/utils, warn) - [ ] P6: Music — разбить на commands/, player/, lavalink/ ### Этап 12: Тесты - [ ] T1: Music — написать тесты на LavalinkManager, TrackScheduler, PlayCommand - [ ] T2: Voice — VoiceChannelManager, VoiceButtonHandler - [ ] T3: Social — интеграционные тесты с NekosLifeApi (мок) - [ ] T4: Radio — RadioManager, RadioCommand - [ ] T5: Role — CustomRoleManager, RoleCommandValidator - [ ] T6-13: Остальные фичи — минимум smoke test ### Техдолг (баги) - [ ] B1: EmbedFactoryTest — 2 падающих теста (`withTitle`, `withColor`) — IllegalStateException - [ ] B2: SlowmodeCommandTest — починен (было 7 ошибок компиляции из-за JDA 6.4 SelfMember) --- **Версия:** 2.3 | **Последнее обновление:** 2026-07-04 | **Java 21 ✅, God Constructor ✅, Critical баги ✅, Economy ✅, Level Roles ✅, Lavalink Music ✅, SpotBugs ✅, Checkstyle ✅, CI/CD ✅** ## ✅ Что добавлено в v2.3 ### 🎵 Lavalink Music Service - [x] Node.js сервис с 4 Discord ботами (kazagumo + shoukaku) - [x] Поддержка YouTube, SoundCloud, радио (`!radio `) - [x] Команды: play, stop, skip, queue, np, volume, pause, resume, shuffle, repeat, clear, move - [x] Lavalink контейнер с healthcheck - [x] docker-compose с интеграцией в основной проект - [x] `.env.example` с 4 токенами ### 🔧 SpotBugs (все реальные баги исправлены) - [x] FS (7): `\n` → `%n` в MetricsCollector, ServerStatsService - [x] DMI (6): Random → static final Random в entertainment командах - [x] SF (4): missing default → добавлены default в switch - [x] RCN (3): Redundant nullcheck удалены - [x] RV (1): Bad abs(hashCode) исправлен - [x] Dm (2): Стандартная кодировка → UTF-8 - [x] OBL/ODR: Database.prepare() закрытие connection - [x] REC: RankCardGenerator — узкий catch - [x] DLS: FishingService — dead store удалён - [x] EI2/EI (96): excluded как DI false positives - [x] `ignoreFailures = false` ### 🔍 Checkstyle - [x] Все 9 нарушений исправлены - [x] `maxErrors`/`maxWarnings` удалены (0 нарушений) ### 🧪 Тесты - [x] BalanceCommandTest (3 теста) - [x] DailyCommandTest (2 теста) - [x] PayCommandTest (3 теста) - [x] CoinCommandTest (2 теста) - [x] DiceCommandTest (2 теста) - [x] EightBallCommandTest (2 теста) - [x] Всего: 51 файл тестов, ~421+ тестов ### 🚀 CI/CD - [x] Docker publish workflow (ghcr.io) - [x] Release workflow (GitHub Releases + JAR artifact)