discord-bot/TODO.md
loki5512344 ec25d9e8d3
feat: remove AnimalApi cache + !ratingoverview for super-admin
- AnimalApi: drop Caffeine URL cache (30s TTL made !neko return the same photo)
- RatingsRepository.getExtremeRating(guild, type, excluded, highest) with NOT IN
- !ratingoverview (super-admin only): per-category max/min, excluding bots + super-admin
- RatingsRepositoryTest: 7 new tests for getExtremeRating
2026-08-19 14:36:12 +02:00

571 lines
34 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 |
| A41 | ✅ | TikTok: команда `!tiktok <url>` для скачивания видео | TikTokApi + TikTokCommand + TikTokModule |
| A42 | ✅ | S1-S5: SOLID рефакторинг | Lifecycle, JdaFactory, PlayLoadHandler, feature modules |
| A43 | ✅ | D4-D6: DRY рефакторинг | VoiceHelper.connectForPlayback, RatingConfig data-driven, SocialCommand.Flavor |
| A44 | ✅ | PlayCommand: 207→148 строк | PlayLoadHandler + VoiceHelper.connectForPlayback |
| A45 | ✅ | TrackScheduler: 146→113 строк | TrackStarter (retry логика) |
| A46 | ✅ | MuteCommand: 199→173 строки | execute() разбит на 6 методов |
| A47 | ✅ | BotInitializer: 162→135 строк | JdaFactory + LavalinkManagerFactory |
| A48 | ✅ | ShutdownManager: 9→3 зависимости | Lifecycle interface + Multibinder |
| A49 | ✅ | BotModule: 98→84 строки | CommandRegistrar bindings в feature модулях |
---
## ♻️ Правила разработки
- 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<CommandRegistrar>`)
### Этап 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 <bet>`)
- Дилер стоит на 16, ничья = проигрыш, BJ = ×1.5
- Кнопки Hit/Stand
**Слоты** (`!slot <bet>`)
- 🍒🍒🍒 ×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
- [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
- [x] Статистика сервера — графики, CSV export — ✅ `!serverstats [csv|chart]`
---
### 🐾 Животные — зоо-викторины и картинки (public-apis) — 🟢 в работе
- [x] `feature/animals/AnimalApi` — клиент к 8 API (thecatapi, dog.ceo, randomfox, shibe.online, some-random-api, nekos.life, waifu.pics, catboys.com) с fallback-цепочкой
- [x] `!catperson` / `!dogperson` / `!foxperson` — «Какой ты кот/пёс/лис»: % + роль + картинка (детерминировано ID)
- [x] `!shibe` / `!birb` — картинки шиба-ину и птичек
- [x] `!neko` / `!waifu` / `!catboy` — аниме-картинки (неко, вайфу, кошко-мальчик)
- [x] Факты: `!catfact` / `!dogfact` — факт + картинка (catfact.ninja, dog facts API)
- [x] `!joke` — анекдоты (jokeapi.dev, категории Any/Programming/Misc)
- [x] `!chuck` — цитаты Чака Норриса (api.chucknorris.io)
- [x] `!inspire` — мотивационные цитаты (zenquotes.io)
- [x] `!apod` — астрономическая картинка дня NASA (api.nasa.gov, DEMO_KEY)
- [x] `!iss` — где сейчас МКС (api.wheretheiss.at)
- [x] `!space` — кто сейчас в космосе (open-notify.org)
- [x] `!xkcd` — свежий стрип (xkcd.com/api)
- [x] `!meme <subreddit>` — расширить мем по сабреддиту
---
## ⚡ Стабильность и скорость (2026-08-19, аудит сервера 185.176.94.38)
### Найдено в логах
| # | Проблема | Статус |
|---|----------|--------|
| S01 | `LogEmbeds.ban`/`kick` — NPE `Value may not be null`, если бан/кик без причины (падал onSuccess-колбэк, терялся лог) | ✅ `reason != null ? reason : "Не указана"` |
| S02 | `RatingService.getDeterministicValue` — гонка на общем `Random.setSeed()`: 2 пользователя одновременно → результаты «подменялись» | ✅ локальный `new Random(seed)` (thread-safe) |
| S03 | Пул репортов на внешние API отсутствовал | ✅ `util/ApiRequestQueue` (пул 4, изоляция ответов) |
| S04 | `Missed 2 heartbeats` — 1 раз, транзиентный (сеть), не повторяется | ➖ наблюдать |
### Что сделано
- `util/ApiRequestQueue` — очередь исходящих запросов: каждый запрос выполняется изолированно, ответ не может подмешаться к соседнему, пул 4 не перегружает публичные API. Подключён в `AnimalApi` и `NekosLifeApi`, зарегистрирован в LifecycleModule (graceful shutdown)
- `RatingService` — детерминированное значение теперь без общего `Random` (root cause «подмены ответов»)
- `LogEmbeds.ban/kick` — дефолт причины «Не указана»
### Производительность (наблюдения)
- Команды выполняются за 0–100 мс (virtual threads), CPU ~0.2%, RAM 186 МБ — узких мест нет
- Основная задержка — внешние HTTP API (таймауты 10 с + retry с бэк-оффом); очередь ограничивает параллелизм до 4
### Оптимизации (2026-08-19)
- [x] `AnimalApi` — Caffeine-кэш успешных URL (TTL 30 с): повторные `!neko`/`!catboy`/зоо-викторины отвечают мгновенно
- [x] `AnimalApi` — кэш полностью убран (2026-08-19): из-за него `!neko` 30с отдавал одну и ту же фотку
- [x] `MemeCommand` — HttpURLConnection (без таймаутов, мог висеть) → OkHttp 6с/8с
- [ ] `Database` — включить `PRAGMA journal_mode = WAL` (сейчас нет — блокировки при конкурентной записи)
- [ ] NekosLifeApi — таймауты/ретраи вынести в конфиг (10с → 8с, retry 3 → 2)
- [ ] Негативный кэш неудачных запросов в AnimalApi (не долбить упавший API 30с)
- [ ] Dockerfile — JVM флаги (`-Xmx512m -XX:+UseG1GC -XX:+UseStringDeduplication`)
- [ ] Разобрать остальные `HttpURLConnection` на общий OkHttp-клиент
---
## 🚧 v2.5 — Java 25, рейтинги, память (2026-08-19)
### 1. Java 26 + Gradle 9.7.0
- [x] `build.gradle`: `toolchain { languageVersion = 26 }` — Gradle сам находит JDK 26 (`/usr/lib/jvm/java-26-openjdk`), `export JAVA_HOME` больше не нужен
- [x] `.java-version` (файл `26`) — для IDE/jenv/asdf
- [x] `gradle/wrapper/gradle-wrapper.properties`: 9.3.1 → 9.7.0 (системный gradle 9.7.0 в `/usr/bin/gradle`)
- [x] `Dockerfile`: `eclipse-temurin:21-jdk-alpine` → `26-jdk-alpine`, `21-jre-alpine` → `26-jre-alpine`
- [x] Mockito 5.15.2 тянет ByteBuddy 1.14.x (не знает класс-файлы major 70) → добавить `byte-buddy:1.18.8` (уже в кэше, поддержка Java 26)
- [x] CI workflows (ci/docker-publish/release): `java-version: "21"` → `"26"`
- [x] Пересобрать локально + `spotlessApply` (Spotless 6.25→8.10.0, SpotBugs 4.9.3→4.10.3 для major 70; 458 тестов ✅)
### 1.1 Обновление зависимостей (2026-08-19, все 20)
- [x] jda 6.4.2→6.5.0, lavaplayer 2.2.6→2.2.7, logback 1.5.18→1.6.3, caffeine 3.2.0→3.2.4
- [x] sqlite 3.49.1.0→3.53.2.1, **hikaricp 6.3.0→7.1.0** (мажор, API совместим, тесты ✅), okhttp 5.4.0→5.5.0
- [x] junit 5.12.1→5.13.4, junit-platform-launcher 1.12.1→1.13.4, mockito 5.15.2→5.23.0, bytebuddy 1.18.8→1.18.12
- [x] **slf4j 2.1.0-alpha1 → 2.0.18** (стабильный вместо alpha)
- [x] checkstyle 10.23.0→13.10.0 (toolVersion, требует Java 21+ ✓), spotbugsPlugin 6.1.7→6.4.8, jsoup 1.19.1→1.22.2
- [x] gson 2.14.0, guice 7.0.0, lavalinkClient 3.4.0, dotenv 3.2.0, spotbugs 4.10.3 — актуальные, без изменений
### 2. `!generateratings` генерит только на 2 человек
- [x] **Причина**: JDA `MemberCachePolicy.DEFAULT` кэширует только голосовых/owner/self + `ChunkingFilter` по умолчанию чанкует только гильдии ≥250 участников → `guild.getMembers()` = 2
- [x] `core/JdaFactory.java`: `.setMemberCachePolicy(MemberCachePolicy.ALL)` + `.setMemberChunkingFilter(ChunkingFilter.ALL)`
- [x] `GenerateRatingsCommand.java`: `guild.getMembers()` → `guild.loadMembers().get()` (подстраховка)
### 3. Команда топа/низа рейтингов
- [x] `RatingsRepository`: `getTopRatings(guildId, type, limit)` + `getBottomRatings(...)` (ORDER BY value DESC/ASC LIMIT)
- [x] Новая команда `feature/rating/commands/board/RatingBoardCommand` — флейворы `ratingstop`/`ratingbottom` (высшие/низшие), рендер `<@id> — value` без резолва участников, медали топ-3
- [x] Зарегистрировать в `feature/utility/UtilityCommandRegistrar` (рядом с GenerateRatingsCommand)
### 4. Лимит памяти 1 ГБ
- [x] `docker-compose.yml`: сервис `bot` → `mem_limit: 1g`
- [x] `Dockerfile` ENTRYPOINT: JVM-флаги `-Xmx512m -XX:+UseG1GC -XX:+UseStringDeduplication`
### 5. Дальнейшие оптимизации (поиск)
- [x] Изучено: Gradle 9.4+ нужен для Java 26 (9.7.0 ✓); Gradle 8.14.3 падает на major 69/70; JDA — `loadMembers()` требует GUILD_MEMBERS intent (у бота включён)
- [ ] `Database`: `PRAGMA journal_mode = WAL`
- [ ] NekosLifeApi: таймауты/ретраи в конфиг
- [ ] Негативный кэш в AnimalApi
- [ ] Свести все HttpURLConnection на OkHttp
---
## 🔴 Результаты аудита 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 | ✅ VoiceHelper.connectForPlayback() |
| D5 | RatingConfig — 6 методов с идентичной структурой | RatingConfig.java | 6 | ✅ Data-driven getLevels(name) + findLevel(value, name) |
| D6 | Social команды (hug/kiss/slap/poke/tickle/cuddle/feed/pat) — шаблон | 10 файлов | 10 | ✅ SocialCommand.Flavor (7 команд), pat/compliment/roast — отдельные |
### SOLID
| # | Нарушение | Файл | Описание |
|---|-----------|------|----------|
| S1 | SRP | BotModule.java | Биндит всё приложение | ✅ Перенесён в feature модули + LifecycleModule |
| S2 | SRP | SwapAndCaseCommand.java | Две разные команды в одном файле | ✅ Уже разделены: SwapCommand + CaseLookupCommand |
| S3 | SRP | BotInitializer.java | Инициализирует 3 JDA, Lavalink, mutes, counters, health | ✅ JdaFactory + LavalinkManagerFactory |
| S4 | SRP | ShutdownManager.java | 9 зависимостей в конструкторе | ✅ Lifecycle interface, 3 зависимости |
| S5 | SRP | PlayCommand.java | execute() на 145 строк | ✅ PlayLoadHandler + VoiceHelper.connectForPlayback() |
| S6 | OCP | Meter-команды (gay/fat/iq/pp) | Добавить новую = копировать 90 строк | ✅ RatingService |
| S7 | DIP | MuteCommand, WarnCommand | >5 конкретных репозиториев вместо интерфейсов | ✅ Репозитории уже интерфейсы (ModerationCaseRepository, ActiveMuteRepository, WarningRepository) |
### Файлы >150 строк
| # | Файл | Строк |
|---|------|-------|
| F1 | PlayCommand.java | 148 ✅ |
| F2 | QueueSlashHandler.java | 207 |
| F3 | MusicBotControlHandler.java | 207 |
| F4 | QueueCommands.java | 207 |
| F5 | TrackScheduler.java | 113 ✅ |
| F6 | MuteCommand.java | 173 ✅ |
| 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 | 135 ✅ |
| 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
- [x] D4: Вынести voice connect retry в утилиту — ✅ VoiceHelper.connectForPlayback()
- [x] D5: RatingConfig — заменить 6 методов на data-driven (Map или config) — ✅ getLevels(name) + findLevel(value, name)
- [x] D6: Social команды — единый `SocialCommand` с параметром action — ✅ SocialCommand.Flavor (7 команд)
### Этап 9: SOLID — ✅ завершён
- [x] S1: Разбить `BotModule` на per-feature модули (FeatureModule → install в BotModule) — ✅ 8 feature модулей + LifecycleModule
- [x] S2: Разделить `SwapAndCaseCommand` на два отдельных файла — ✅ Already split (SwapCommand + CaseLookupCommand)
- [x] S3: Выделить `MusicBotFactory`, `RadioBotFactory` из `BotInitializer` — ✅ JdaFactory + LavalinkManagerFactory
- [x] S4: Уменьшить кол-во зависимостей `ShutdownManager` через события (EventBus) — ✅ Lifecycle interface (9→3 deps)
- [x] S5: Разбить `PlayCommand.execute()` на методы — ✅ PlayLoadHandler + VoiceHelper.connectForPlayback()
- [x] S6: Meter-команды сделать data-driven через `RatingService` — ✅ RatingConfig.findLevel(value, name)
- [x] S7: Выделить интерфейсы для репозиториев/сервисов (DIP) — ✅ Already interfaces
### Этап 10: Файлы ≤150 строк — в процессе
- [x] F01: PlayCommand.java — разбить на методы/классы — ✅ 207→148 строк
- [ ] F02-04: QueueSlashHandler, MusicBotControlHandler, QueueCommands — вынести общие части
- [x] F05: TrackScheduler.java — вывести FilterState в отд. файл или утилиту — ✅ 146→113 строк (TrackStarter)
- [x] F06: MuteCommand.java — разбить на subcommand handler'ы — ✅ 199→173 строки
- [ ] F07: ModerationLogger.java — разбить по типам событий
- [ ] F08-09: FeatureTables, DatabaseMigration — разбить по фичам
- [ ] F10-12: Voice* — вынести логику в сервисы
- [ ] F13: RadioManager.java — вынести voice connect в утилиту
- [ ] F14: MetricsCollector.java — разбить по типам метрик
- [ ] F15: WarnCommand.java — вынести auto-mute в отдельный сервис
- [x] F16: BotInitializer.java — вынести фабрики ботов — ✅ 162→135 строк
- [x] F02-04: QueueSlashHandler, MusicBotControlHandler, QueueCommands — вынести общие части — ✅ вынесены при реорге music
- [ ] Остальные: разбить по SOLID
### Этап 11: ≤6 файлов в папке
- [x] P1: Entertainment команды — уже сгруппированы (choice/, random/; rating-команды в feature/rating/)
- [x] P2: Social команды — уже объединены (SocialCommand + 3 отдельных), 4 файла
- [x] P4: Level — уже разбит на commands/, service/, repository/, domain/
- [x] P5: Moderation — уже разбит по поддоменам (punish, utils, warn, caselookup, log, repository)
- [x] P6: Music — ✅ commands/filter|control|queue, listener/bot|select, manage/player|filter, util/embed|voice|query (все папки ≤6)
### Этап 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.4 | **Последнее обновление:** 2026-07-14 | **Java 21 ✅, God Constructor ✅, Critical баги ✅, Economy ✅, Level Roles ✅, Lavalink Music ✅, SpotBugs ✅, Checkstyle ✅, CI/CD ✅, SOLID ✅, TikTok ✅**
## ✅ Что добавлено в v2.4
### 🎬 TikTok Download Command
- [x] Команда `!tiktok <url>` — скачивание видео из TikTok без watermark
- [x] TikTokApi — сервис для работы с tikwm.com API (OkHttp + Gson)
- [x] Скачивание через virtual threads
- [x] Embed с информацией (title, author, duration, cover)
- [x] Проверка размера файла (< 8MB)
- [x] TikTokModule для DI
### 🔧 Рефакторинг SOLID/DRY
- [x] **S1**: BotModule — CommandRegistrar bindings перенесены в feature модули (8 модулей)
- [x] **S2**: SwapAndCaseCommand — уже разделены (SwapCommand + CaseLookupCommand)
- [x] **S3**: BotInitializer — извлечены JdaFactory + LavalinkManagerFactory (162→135 строк)
- [x] **S4**: ShutdownManager — Lifecycle interface, 9→3 зависимости (118→89 строк)
- [x] **S5**: PlayCommand — извлечён PlayLoadHandler + VoiceHelper.connectForPlayback() (207→148 строк)
- [x] **S7**: DIP — репозитории уже интерфейсы (ModerationCaseRepository, ActiveMuteRepository, WarningRepository)
- [x] **D4**: Voice connect retry — VoiceHelper.connectForPlayback()
- [x] **D5**: RatingConfig — data-driven getLevels(name) + findLevel(value, name)
- [x] **D6**: Social команды — SocialCommand.Flavor (7 команд), pat/compliment/roast — отдельные
- [x] **TrackScheduler** — извлечён TrackStarter (146→113 строк)
- [x] **MuteCommand** — execute() разбит на методы (199→173 строки, execute() 23 строки)
### 📦 Новые файлы
- `feature/tiktok/TikTokApi.java` — API сервис
- `feature/tiktok/TikTokCommand.java` — команда
- `feature/tiktok/TikTokModule.java` — Guice модуль
- `core/Lifecycle.java` — интерфейс для shutdown
- `core/JdaFactory.java` — фабрика JDA
- `core/LavalinkManagerFactory.java` — фабрика LavalinkManager
- `core/LifecycleModule.java` — Multibinder для Lifecycle
- `feature/entertainment/EntertainmentModule.java` — CommandRegistrar binding
- `feature/utility/UtilityModule.java` — CommandRegistrar binding
- `feature/music/play/PlayLoadHandler.java` — обработчик загрузки треков
- `feature/music/manage/TrackStarter.java` — retry логика запуска треков
---
## ✅ Что добавлено в v2.3
### 🎵 Lavalink Music Service
- [x] Node.js сервис с 4 Discord ботами (kazagumo + shoukaku)
- [x] Поддержка YouTube, SoundCloud, радио (`!radio <url>`)
- [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)