- 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
34 KiB
34 KiB
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)
- EventDispatcher: VirtualThreadPool
- instanceof pattern matching (3 файла)
- arrow-switch (InfoCommand)
- String.repeat() (RateCommand)
- .toList() (3 файла)
- Map.ofEntries() (HelpCategories)
Этап 2: Рефакторинг PrefixCommandRegistrar ✅
- Создан интерфейс
CommandRegistrar - 4 per-feature регистратора
- Guice Multibinder для сбора регистраторов
- 22 параметра → 1 (
Set<CommandRegistrar>)
Этап 3: Critical-баги ✅
- RankCardGenerator: async + try-with-resources + cache + SVG
- Database: synchronized connection
- NekosLifeApi: чистый retry loop
- GenerateRatingsCommand: async через complete()
- DB path: вынесен в .env
Этап 4: Перемещение файлов в feature-пакеты ✅
command/impl/utility/→feature/*/commands/command/impl/entertainment/→feature/*/commands/command/entertainment/social/→feature/social/commands/database/*/→feature/*/repository/event/interaction/HelpMenuHandler→feature/help/
Этап 5: Тесты (цель: 20-30% покрытие)
- Тесты на репозитории (in-memory SQLite) — Config, ShipPair, UserStats, Ratings, Warnings, ActiveMutes
- Unit-тесты сервисов — MessageCooldownService, LevelService (был), XpFormula, ChoiceOptionParser
- Unit-тесты value objects — UserStats (был), LevelProgress, LevelUpResult, BotConfig
- Тесты embed — EmbedFactory (builder chaining), EmbedTemplates (factory methods)
- Тесты форматирования — LeaderboardFormatter
- Unit-тесты на команды (с моками JDA)
- Smoke-тест: все команды регистрируются без коллизий
- Тесты на RankCardGenerator (изолированные, без сети)
- Тесты на EventDispatcher (маршрутизация, rate limiting)
Этап 6: Инфраструктура ✅
- Dockerfile (multi-stage)
- docker-compose.yml
- Статический анализ: checkstyle / spotbugs
- Версионный каталог:
gradle/libs.versions.toml - HikariCP вместо голого JDBC
Этап 7: Кодстайл
- EmbedFactory:
set*→with*для builder - DatabaseMigration: оценить нужность
- HelpMenuHandler: переместить в feature/help/
🚀 Новые фичи (Roadmap)
🚀 Экономика — ✅ реализовано
Модель данных
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
- Система экономики (Economy) — ✅
Приоритет 2
- Авто-модерация (3 варна = авто-мут на 1ч)
- Музыка (lavaplayer: play, skip, stop, queue, np, volume, leave)
Музыка — поиск с выбором (yt/sc/list + выбор номера)- Server Stats (участники, каналы, роли, бусты)
- Level Roles (роли за уровни)
- Красивые level-up embed с прогресс-баром
Приоритет 3
- Статистика сервера — графики, CSV export — ✅
!serverstats [csv|chart]
🐾 Животные — зоо-викторины и картинки (public-apis) — 🟢 в работе
feature/animals/AnimalApi— клиент к 8 API (thecatapi, dog.ceo, randomfox, shibe.online, some-random-api, nekos.life, waifu.pics, catboys.com) с fallback-цепочкой!catperson/!dogperson/!foxperson— «Какой ты кот/пёс/лис»: % + роль + картинка (детерминировано ID)!shibe/!birb— картинки шиба-ину и птичек!neko/!waifu/!catboy— аниме-картинки (неко, вайфу, кошко-мальчик)- Факты:
!catfact/!dogfact— факт + картинка (catfact.ninja, dog facts API) !joke— анекдоты (jokeapi.dev, категории Any/Programming/Misc)!chuck— цитаты Чака Норриса (api.chucknorris.io)!inspire— мотивационные цитаты (zenquotes.io)!apod— астрономическая картинка дня NASA (api.nasa.gov, DEMO_KEY)!iss— где сейчас МКС (api.wheretheiss.at)!space— кто сейчас в космосе (open-notify.org)!xkcd— свежий стрип (xkcd.com/api)!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)
AnimalApi— Caffeine-кэш успешных URL (TTL 30 с): повторные!neko/!catboy/зоо-викторины отвечают мгновенноAnimalApi— кэш полностью убран (2026-08-19): из-за него!neko30с отдавал одну и ту же фотку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
build.gradle:toolchain { languageVersion = 26 }— Gradle сам находит JDK 26 (/usr/lib/jvm/java-26-openjdk),export JAVA_HOMEбольше не нужен.java-version(файл26) — для IDE/jenv/asdfgradle/wrapper/gradle-wrapper.properties: 9.3.1 → 9.7.0 (системный gradle 9.7.0 в/usr/bin/gradle)Dockerfile:eclipse-temurin:21-jdk-alpine→26-jdk-alpine,21-jre-alpine→26-jre-alpine- Mockito 5.15.2 тянет ByteBuddy 1.14.x (не знает класс-файлы major 70) → добавить
byte-buddy:1.18.8(уже в кэше, поддержка Java 26) - CI workflows (ci/docker-publish/release):
java-version: "21"→"26" - Пересобрать локально +
spotlessApply(Spotless 6.25→8.10.0, SpotBugs 4.9.3→4.10.3 для major 70; 458 тестов ✅)
1.1 Обновление зависимостей (2026-08-19, все 20)
- 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
- sqlite 3.49.1.0→3.53.2.1, hikaricp 6.3.0→7.1.0 (мажор, API совместим, тесты ✅), okhttp 5.4.0→5.5.0
- 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
- slf4j 2.1.0-alpha1 → 2.0.18 (стабильный вместо alpha)
- checkstyle 10.23.0→13.10.0 (toolVersion, требует Java 21+ ✓), spotbugsPlugin 6.1.7→6.4.8, jsoup 1.19.1→1.22.2
- gson 2.14.0, guice 7.0.0, lavalinkClient 3.4.0, dotenv 3.2.0, spotbugs 4.10.3 — актуальные, без изменений
2. !generateratings генерит только на 2 человек
- Причина: JDA
MemberCachePolicy.DEFAULTкэширует только голосовых/owner/self +ChunkingFilterпо умолчанию чанкует только гильдии ≥250 участников →guild.getMembers()= 2 core/JdaFactory.java:.setMemberCachePolicy(MemberCachePolicy.ALL)+.setMemberChunkingFilter(ChunkingFilter.ALL)GenerateRatingsCommand.java:guild.getMembers()→guild.loadMembers().get()(подстраховка)
3. Команда топа/низа рейтингов
RatingsRepository:getTopRatings(guildId, type, limit)+getBottomRatings(...)(ORDER BY value DESC/ASC LIMIT)- Новая команда
feature/rating/commands/board/RatingBoardCommand— флейворыratingstop/ratingbottom(высшие/низшие), рендер<@id> — valueбез резолва участников, медали топ-3 - Зарегистрировать в
feature/utility/UtilityCommandRegistrar(рядом с GenerateRatingsCommand)
4. Лимит памяти 1 ГБ
docker-compose.yml: сервисbot→mem_limit: 1gDockerfileENTRYPOINT: JVM-флаги-Xmx512m -XX:+UseG1GC -XX:+UseStringDeduplication
5. Дальнейшие оптимизации (поиск)
- Изучено: 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 | Биндит всё приложение |
| 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 строк |
| S7 | DIP | MuteCommand, WarnCommand | >5 конкретных репозиториев вместо интерфейсов |
Файлы >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) — ✅ завершён
- D1/D2:
RatingServiceсgetDeterministicValue(), cooldown, member parsing — 7 команд переписаны - D3:
ModerationHelperсtrunc()/err()— MuteCommand, UnmuteCommand, WarnCommand, SwapAndCaseCommand, PunishCommand - D4: Вынести voice connect retry в утилиту — ✅ VoiceHelper.connectForPlayback()
- D5: RatingConfig — заменить 6 методов на data-driven (Map или config) — ✅ getLevels(name) + findLevel(value, name)
- D6: Social команды — единый
SocialCommandс параметром action — ✅ SocialCommand.Flavor (7 команд)
Этап 9: SOLID — ✅ завершён
- S1: Разбить
BotModuleна per-feature модули (FeatureModule → install в BotModule) — ✅ 8 feature модулей + LifecycleModule - S2: Разделить
SwapAndCaseCommandна два отдельных файла — ✅ Already split (SwapCommand + CaseLookupCommand) - S3: Выделить
MusicBotFactory,RadioBotFactoryизBotInitializer— ✅ JdaFactory + LavalinkManagerFactory - S4: Уменьшить кол-во зависимостей
ShutdownManagerчерез события (EventBus) — ✅ Lifecycle interface (9→3 deps) - S5: Разбить
PlayCommand.execute()на методы — ✅ PlayLoadHandler + VoiceHelper.connectForPlayback() - S6: Meter-команды сделать data-driven через
RatingService— ✅ RatingConfig.findLevel(value, name) - S7: Выделить интерфейсы для репозиториев/сервисов (DIP) — ✅ Already interfaces
Этап 10: Файлы ≤150 строк — в процессе
- F01: PlayCommand.java — разбить на методы/классы — ✅ 207→148 строк
- F02-04: QueueSlashHandler, MusicBotControlHandler, QueueCommands — вынести общие части
- F05: TrackScheduler.java — вывести FilterState в отд. файл или утилиту — ✅ 146→113 строк (TrackStarter)
- 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 в отдельный сервис
- F16: BotInitializer.java — вынести фабрики ботов — ✅ 162→135 строк
- F02-04: QueueSlashHandler, MusicBotControlHandler, QueueCommands — вынести общие части — ✅ вынесены при реорге music
- Остальные: разбить по SOLID
Этап 11: ≤6 файлов в папке
- P1: Entertainment команды — уже сгруппированы (choice/, random/; rating-команды в feature/rating/)
- P2: Social команды — уже объединены (SocialCommand + 3 отдельных), 4 файла
- P4: Level — уже разбит на commands/, service/, repository/, domain/
- P5: Moderation — уже разбит по поддоменам (punish, utils, warn, caselookup, log, repository)
- 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
- Команда
!tiktok <url>— скачивание видео из TikTok без watermark - TikTokApi — сервис для работы с tikwm.com API (OkHttp + Gson)
- Скачивание через virtual threads
- Embed с информацией (title, author, duration, cover)
- Проверка размера файла (< 8MB)
- TikTokModule для DI
🔧 Рефакторинг SOLID/DRY
- S1: BotModule — CommandRegistrar bindings перенесены в feature модули (8 модулей)
- S2: SwapAndCaseCommand — уже разделены (SwapCommand + CaseLookupCommand)
- S3: BotInitializer — извлечены JdaFactory + LavalinkManagerFactory (162→135 строк)
- S4: ShutdownManager — Lifecycle interface, 9→3 зависимости (118→89 строк)
- S5: PlayCommand — извлечён PlayLoadHandler + VoiceHelper.connectForPlayback() (207→148 строк)
- S7: DIP — репозитории уже интерфейсы (ModerationCaseRepository, ActiveMuteRepository, WarningRepository)
- D4: Voice connect retry — VoiceHelper.connectForPlayback()
- D5: RatingConfig — data-driven getLevels(name) + findLevel(value, name)
- D6: Social команды — SocialCommand.Flavor (7 команд), pat/compliment/roast — отдельные
- TrackScheduler — извлечён TrackStarter (146→113 строк)
- MuteCommand — execute() разбит на методы (199→173 строки, execute() 23 строки)
📦 Новые файлы
feature/tiktok/TikTokApi.java— API сервисfeature/tiktok/TikTokCommand.java— командаfeature/tiktok/TikTokModule.java— Guice модульcore/Lifecycle.java— интерфейс для shutdowncore/JdaFactory.java— фабрика JDAcore/LavalinkManagerFactory.java— фабрика LavalinkManagercore/LifecycleModule.java— Multibinder для Lifecyclefeature/entertainment/EntertainmentModule.java— CommandRegistrar bindingfeature/utility/UtilityModule.java— CommandRegistrar bindingfeature/music/play/PlayLoadHandler.java— обработчик загрузки трековfeature/music/manage/TrackStarter.java— retry логика запуска треков
✅ Что добавлено в v2.3
🎵 Lavalink Music Service
- Node.js сервис с 4 Discord ботами (kazagumo + shoukaku)
- Поддержка YouTube, SoundCloud, радио (
!radio <url>) - Команды: play, stop, skip, queue, np, volume, pause, resume, shuffle, repeat, clear, move
- Lavalink контейнер с healthcheck
- docker-compose с интеграцией в основной проект
.env.exampleс 4 токенами
🔧 SpotBugs (все реальные баги исправлены)
- FS (7):
\n→%nв MetricsCollector, ServerStatsService - DMI (6): Random → static final Random в entertainment командах
- SF (4): missing default → добавлены default в switch
- RCN (3): Redundant nullcheck удалены
- RV (1): Bad abs(hashCode) исправлен
- Dm (2): Стандартная кодировка → UTF-8
- OBL/ODR: Database.prepare() закрытие connection
- REC: RankCardGenerator — узкий catch
- DLS: FishingService — dead store удалён
- EI2/EI (96): excluded как DI false positives
ignoreFailures = false
🔍 Checkstyle
- Все 9 нарушений исправлены
maxErrors/maxWarningsудалены (0 нарушений)
🧪 Тесты
- BalanceCommandTest (3 теста)
- DailyCommandTest (2 теста)
- PayCommandTest (3 теста)
- CoinCommandTest (2 теста)
- DiceCommandTest (2 теста)
- EightBallCommandTest (2 теста)
- Всего: 51 файл тестов, ~421+ тестов
🚀 CI/CD
- Docker publish workflow (ghcr.io)
- Release workflow (GitHub Releases + JAR artifact)