discord-bot/TODO.md
loki5512344 4cbbb36159
fix(lavalink): drop spotify-tokener, lavasrc uses client credentials
- spotify URL resolution was broken (500 NPE): preferPartnerApi +
  customTokenEndpoint depended on dead spotify-tokener (-32000)
- remove tokener service from compose, remove dead SPOTIFY_* env from
  bot container (bot code never used them)
- spotify metadata now resolves via standard client credentials,
  playback is mirrored to SoundCloud via scsearch providers (ISRC/QUERY)
2026-08-30 00:29:13 +02:00

43 KiB
Raw Blame History

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 модулях
SP1 ✅ Spotify не работал: lavasrc не мог получить токен (spotify-tokener упал с -32000, NPE 500 на loadtracks) Удалён spotify-tokener, lavasrc → client credentials; плейбек Spotify → scsearch-мирроринг (SoundCloud)
PL1 ✅ Плейлист SoundCloud: игрался только 1 трек (REPLACED-гонка в TrackScheduler) Локальный current-трекинг + атомарный skip + сохранение volume (5e83356)

♻️ Правила разработки

  • 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()

Аудит 30.08.2026 — баг-хант (3 суб-агента, не исправлено)

Экономика / уровень / модериование / config / voice / counter

# Приоритет Проблема Файл
F1 🔴 CRITICAL !slot: cashAfter пишется из устаревшего снимка аккаунта — параллельные спины = бесплатные спины / двойные списания. Нужен атомарный UPDATE ... SET cash = cash - ? WHERE cash >= ? SlotService.java:45-46
F2 🔴 CRITICAL transfer() неатомарный read-check-write без транзакции — двойная трата / потеря денег при параллельных переводах EconomyService.java:29-41
F3 🟠 HIGH /setup целиком без проверок прав — любой участник меняет счётчик, логи, voice-триггер, роли за уровни SetupSlashHandler.java:50-105, SetupInteractions.java:19-71
F4 🟠 HIGH claimDaily() check-then-act по last_daily — двойной дейли при параллельных запросах EconomyService.java:88-93
F5 🟠 HIGH WorkCommand передаёт level=0 — работы с requiredLevel > 0 (COOK/PROGRAMMER/ASTRONAUT) недоступны никогда WorkCommand.java:51
F6 🟠 HIGH LevelResetService: фильтр guildId + ":" не совпадает с ключами user_stats — сброс уровней никого не трогает, а одноразовый флаг сгорает LevelResetService.java:34-43
F7 🟠 HIGH Blackjack: hit/stand NPE при гонке (state==null после удаления), общий несинхронизированный ArrayList карт BlackjackCommand.java:125, BlackjackService.java:52-95
F8 🟠 HIGH Блокирующие JDBC / .complete() на gateway-потоке JDA: 4 SQL на каждое сообщение, SQL на join/leave, REST на создание канала MessageListener.java:42-48, MemberCounterListener.java:59-74, FishMessageListener.java:39, VoiceChannelListener.java:41, VoiceTracker.java:78-79
F9 🟡 MEDIUM Blackjack push (равные очки) — игрок теряет ставку, ничья не обработана BlackjackService.java:89
F10 🟡 MEDIUM Blackjack TOCTOU containsKey — двойное списание ставки при параллельных !bj; ставка теряется при рестарте (игры только в памяти) BlackjackService.java:22,48
F11 🟡 MEDIUM Рыбалка: кулдаун пишется только при верном ответе — можно бесконечно перегенерировать поле без штрафа FishingService.java:24-30,87
F12 🟡 MEDIUM Счётчик участников: getMemberCount() (с ботами) vs countMembers() (без) — число на канале прыгает CounterChannelUpdater.java:22, CounterManager.java:81
F13 🟡 MEDIUM XP/время в войсе: read-modify-write без синхронизации — терянные апдейты при параллельных начислениях LevelService.java:41-49
F14 🟡 MEDIUM getSystemChannel() @Nullable → NPE при level-up в войсе VoiceTracker.java:85

Music / radio

# Приоритет Проблема Файл
M1 🟠 HIGH Утечка потоков: removeMusicManager() не вызывает destroy() — каждый автолив оставляет навсегда живой ScheduledExecutorService LavalinkManager.java:96-98, AutoLeaveController.java:47
M2 🟠 HIGH m!play: блокирующий connectForPlayback (sleep до ~4с) прямо на gateway-потоке music-бота MusicBotPlayHandler.java:36
M3 🟠 HIGH Селект-меню поиска: то же — блокирующий connectForPlayback в onStringSelectInteraction PlaySelectHandler.java:55
M4 🟡 MEDIUM asVoiceChannel() кидает IllegalStateException в stage-каналах — команды падают у пользователей VoiceHelper.java:59, PlaySlashHandler.java:31, RadioBotCommands.java:40
M5 🟡 MEDIUM Гонка RadioManager.startRadio — двойной !r!play теряет GuildMusicManager (утечка), connect()==null оставляет «зомби-сессию» RadioManager.java:64-83
M6 🟡 MEDIUM Отложенный loadItem оживляет уничтоженное радио (enqueue на выключенном executor → RejectedExecutionException глотается) RadioStreamLoader.java:36
M7 🟡 MEDIUM !queue: embed-поле >1024 символов при длинных названиях → IllegalArgumentException, команда падает QueueView.java:29,51-64
M8 🟢 LOW Integer.parseInt переполняется на большом «номере трека» из поиска PlayCommand.java:83
M9 🟢 LOW Заголовок embed поиска не обрезан до 256 символов SearchHandler.java:50
M10 🟢 LOW TOCTOU link.getCachedPlayer() != null ? ...getX() — NPE между проверкой и вызовом QueueView.java:40, VolumeControl.java:11-22
M11 🟢 LOW FilterState.customBands — несинхронизированный ArrayList под конкурентным доступом FilterState.java:19,89
M12 🟢 LOW m!play игнорирует неудачу подключения — «Добавлено в очередь», а звука нет MusicBotPlayHandler.java:36
M13 🟢 LOW destroy() глотает InterruptedException без восстановления флага GuildMusicManager.java:64-66
M14 🟢 LOW r!volume рапортует успех при отсутствии радио-сессии RadioSubcommands.java:108-113

Core / database

# Приоритет Проблема Файл
C1 🟠 HIGH Прокси PreparedStatement не разворачивает InvocationTargetException → SQL-ошибки вылетают как UndeclaredThrowableException, все catch (SQLException) их не ловят Database.java:76-89
C2 🟡 MEDIUM Переполнение long в MuteDurationParser (!mute 106751991167301d → отрицательные секунды) MuteDurationParser.java:55-57
C3 🟡 MEDIUM event.getGuild() в ЛС кидает IllegalStateException (JDA 6.5.0) — префикс-команды в ЛС падают EventDispatcher.java:81,107
C4 🟡 MEDIUM CooldownManager check-then-act — два параллельных !rep/!rob обходят кулдаун CooldownManager.java:23-57
C5 🟡 MEDIUM Миграция продолжается после частичного провала, migrated=true ставится всегда — тихая потеря данных DatabaseMigration.java:40-44, JsonMigration.java:43,63,88,117
C6 🟢 LOW PRAGMA foreign_keys = ON выполняется на одном соединении, не на всех в пуле Database.java:43-48
C7 🟢 LOW JsonMigration: NPE на битых записях легаси-JSON → бот не стартует JsonMigration.java:33-39
C8 🟢 LOW Lavalink-клиенты не регистрируются в ShutdownManager — websocket не закрывается при graceful shutdown BotInitializer.java:73-83
C9 🟢 LOW Ответ «команда не найдена» до rate-limiter — флуд несуществующими командами не ограничен EventDispatcher.java:63-77
C10 🟢 LOW ApiRequestQueue: future.get() без таймаута; при прерывании возвращает null ApiRequestQueue.java:38

📋 План рефакторинга

Этап 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): из-за него !neko 30с отдавал одну и ту же фотку
  • 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/asdf
  • gradle/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: 1g
  • Dockerfile ENTRYPOINT: 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 — интерфейс для 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

  • 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)