feat: economy system with minigames, bank, rob, jobs, fishing, blackjack, slots

+ economy domain (EconomyAccount, Job, SlotCombination, EconomyTransaction)
+ EconomyRepository + SqlEconomyRepository + DB schema
+ EconomyService (transfer, daily, deposit/withdraw, stat upgrades)
+ FishingService (fish grid with difficulty levels, speed bonus)
+ BlackjackService (dealer stands on 16, ties lose)
+ SlotService (weighted combos up to x50)
+ JobService (6 jobs with level reqs and efficiency stat)
+ 11 commands: balance, baltop, pay, daily, bank, rob,
  fish, blackjack, slot, job, work
+ Tests for SqlEconomyRepository
+ EconomyModule + EconomyCommandRegistrar wired into BotModule
This commit is contained in:
loki5512344 2026-06-20 00:23:15 +02:00
parent fcb5cd7207
commit fe121c8546
270 changed files with 18632 additions and 11656 deletions

928
TODO.md
View file

@ -1,746 +1,254 @@
# TODO - План реализации функций
## Правила разработки
### Архитектура и код:
- KISS, DRY, SOLID принципы
- Файлы максимум 150-200 строк, стараться делать 100 строк
- В одной папке не больше 6 файлов
- Не создавать новые MD файлы без необходимости
- Разделение по доменам, а не по типам артефактов
### Принципы структуры:
- Пакет = домен/фича, а не тип файла (не "команды/листенеры", а "profile/level/voice")
- Каждая фича содержит свои команды, сервисы, репозитории
- Единый слой персистентности через repository/
- Интеграции в отдельном пакете integration/
---
## Рефакторинг структуры проекта
### Целевая оценка: 7/10
**Проблемы:**
1. `util/` - мусорная корзина (CooldownManager, RateLimiter, NekosLifeApi)
2. Два механизма персистентности (data/ и database/) без абстракции
3. `command/impl/entertainment/` - 23 файла в одном пакете
4. `exception/` - избыточный пакет для 2 файлов
5. `embed/` - presentation layer оторван от domain
6. `listener/` - смешаны разные ответственности
7. Техническое разделение вместо доменного
8. **LevelSystem - God Class (5/10):**
- Нарушение SRP: хранение + кэширование + формулы + лидерборды + прогресс-бары
- Прямая зависимость от JsonDataStore (нет интерфейса Repository)
- O(n) на каждое сообщение (читает/пишет весь JSON)
- Кэш messageTimestamps не персистентный (теряется при рестарте)
- Рассогласование формул: комментарий говорит 200, код использует 100
- LevelUpResult и LevelProgress - вложенные классы вместо отдельных
- getAllStats() возвращает mutable map (можно сломать консистентность)
9. **UserStats - анемичная модель (6/10):**
- Сеттеры позволяют нарушить инварианты (setLevel(999) без XP)
- incrementMessages() скрыто меняет lastMessageTime (side effect)
- Поля lastMessageTime и lastVoiceJoin не используются системой
- Нет equals/hashCode для использования в коллекциях
### Целевая структура (по доменам):
```
src/main/java/creator/loki/
├── App.java # Точка входа (временно, потом в bootstrap/)
│
├── core/ # Ядро системы
│ ├── BotInitializer
│ ├── EventDispatcher
│ ├── registry/ # Регистрация команд
│ │ ├── CommandRegistry
│ │ ├── SlashCommandRegistry
│ │ ├── PrefixCommandRegistrar
│ │ └── SlashCommandRegistrar
│ ├── security/ # Безопасность и ограничения
│ │ ├── RateLimiter # (из util/)
│ │ └── CooldownManager # (из util/)
│ └── exception/ # Глобальная обработка ошибок
│ └── CommandExceptionHandler
│
├── command/ # Базовые интерфейсы команд
│ ├── Command.java
│ ├── CommandContext.java
│ ├── CommandException.java # (из exception/)
│ ├── SlashCommand.java
│ ├── moderation/ # Модерация (3 файла)
│ │ ├── BanCommand
│ │ ├── KickCommand
│ │ └── ClearCommand
│ ├── entertainment/ # Развлечения
│ │ ├── games/ # Игры (12 файлов)
│ │ └── social/ # Взаимодействия (11 файлов)
│ ├── utility/ # Утилиты (5 файлов)
│ │ ├── HelpCommand
│ │ ├── PingCommand
│ │ ├── InfoCommand
│ │ ├── AvatarCommand
│ │ └── PollCommand
│ └── system/ # Системные команды
│ └── MetricsCommand # (из utility/)
│
├── feature/ # Крупные фичи/домены
│ ├── level/ # Система уровней
│ │ ├── LevelSystem
│ │ ├── UserStats
│ │ ├── LevelUtils # (из profile/)
│ │ ├── RankCommand # (из command/stats/)
│ │ ├── StatsCommand # (из command/stats/)
│ │ ├── LevelUpNotifier # (из listener/)
│ │ └── VoiceTracker # (из listener/)
│ │
│ ├── profile/ # Профили пользователей
│ │ ├── ProfileService
│ │ ├── ProfileGenerator
│ │ ├── ProfileData
│ │ ├── ProfileCommand # (из command/utility/)
│ │ └── ProfileEmbedFactory # (из embed/)
│ │
│ ├── reputation/ # Система репутации
│ │ ├── ReputationService # (логика из RepCommand)
│ │ ├── RepCommand # (из command/stats/)
│ │ ├── MinusRepCommand # (из command/stats/)
│ │ └── RepTopCommand # (из command/stats/)
│ │
│ ├── leaderboard/ # Таблица лидеров
│ │ ├── LeaderboardCommand # (NewLeaderboardCommand)
│ │ ├── LeaderboardService # (логика из команды)
│ │ ├── LeaderboardFormatter
│ │ ├── LeaderboardButtons
│ │ └── LeaderboardButtonListener
│ │
│ ├── voice/ # Личные голосовые каналы
│ │ ├── VoiceChannelManager
│ │ ├── VoiceChannelData
│ │ ├── VoicePreset
│ │ ├── VoiceSetupCommand # (из command/setup/)
│ │ ├── VoiceChannelListener # (из listener/)
│ │ └── VoiceButtonHandler # (из listener/)
│ │
│ ├── counter/ # Счетчик участников
│ │ ├── CounterManager
│ │ ├── DailyStats
│ │ ├── CounterSetupCommand # (из command/setup/)
│ │ └── MemberCounterListener # (из listener/)
│ │
│ └── role/ # Кастомные роли
│ ├── CustomRoleManager
│ ├── CreateRoleCommand # (из command/roles/)
│ ├── DeleteRoleCommand # (из command/roles/)
│ ├── EditRoleCommand # (из command/roles/)
│ ├── MyRolesCommand # (из command/roles/)
│ └── RoleCommandValidator # (из command/roles/)
│
├── event/ # Обработчики событий Discord
│ ├── ReadyListener # (из listener/)
│ ├── MessageListener # (из listener/)
│ ├── SlashCommandListener # (из listener/)
│ ├── HelpMenuHandler # (из listener/)
│ └── ModerationLogger # (из listener/)
│
├── repository/ # Единый слой данных
│ ├── UserRepository # Интерфейс
│ ├── GuildConfigRepository # Интерфейс
│ └── impl/
│ ├── JsonUserRepository # (из data/)
│ └── SqliteConfigRepository # (из database/)
│
├── database/ # Инфраструктура БД
│ ├── Database
│ ├── DatabaseSchema
│ └── DatabaseMigration
│
├── integration/ # Внешние API
│ └── nekos/
│ └── NekosLifeApi # (из util/)
│
├── config/ # Конфигурация
│ ├── BotConfig
│ └── ConfigLoader
│
├── embed/ # Общие шаблоны embed
│ ├── EmbedFactory
│ └── EmbedTemplates
│
├── metrics/ # Метрики
│ └── MetricsCollector
│
└── di/ # Dependency Injection
└── BotModule
```
### План действий (этапы):
#### Этап 1: Фундамент (делаем первым)
**1. Создать repository/ (средний → высокий приоритет)**
- [x] Создать `repository/`
- [x] Создать интерфейс `repository/UserStatsRepository.java`
- [x] Создать `repository/impl/`
- [x] Создать `repository/impl/JsonUserStatsRepository.java` (обёртка над JsonDataStore)
- [ ] Создать интерфейс `repository/GuildConfigRepository.java`
- [ ] Рефакторить `database/ConfigRepository` → использовать интерфейс
**2. Реорганизовать util/ (мусорная корзина)**
- [x] Создать `core/security/`
- [x] Переместить `util/CooldownManager` → `core/security/CooldownManager`
- [x] Переместить `util/GlobalRateLimiter` → `core/security/RateLimiter`
- [x] Создать `integration/nekos/`
- [x] Переместить `util/NekosLifeApi` → `integration/nekos/NekosLifeApi`
- [x] Удалить пустую папку `util/`
- [x] Обновить все импорты в командах
- [x] Добавить `UserStatsRepository` в `BotModule`
**3. Удалить импорты AudioManager и мертвые ссылки**
- [x] Удалить `import creator.loki.audio.AudioManager` из `BotInitializer`
- [x] Удалить `import creator.loki.audio.AudioManager` из `PrefixCommandRegistrar`
- [x] Удалить `import creator.loki.audio.AudioManager` из `SlashCommandRegistrar`
- [x] Удалить параметр `AudioManager audioManager` из конструкторов
- [x] Удалить поля `private final AudioManager audioManager`
- [x] Удалить `registry.register(new TtsCommand(...))` из `PrefixCommandRegistrar`
- [x] Удалить `registry.register(new TtsSlashCommand(...))` из `SlashCommandRegistrar`
- [x] Исправить `registry.register(new NewLeaderboardCommand(...))` → использовать новый путь
- [x] Удалить все `registry.register(new *SlashCommand(...))` из `SlashCommandRegistrar`
- [x] Удалить `import creator.loki.command.slash.*` из `SlashCommandRegistrar`
- [x] Проверить компиляцию: `gradle build`
---
#### Этап 2: feature/level/ (зависит от Этапа 1)
**4. Создать feature/level/domain/**
- [x] Создать `feature/level/`
- [x] Создать `feature/level/domain/`
- [x] Создать `feature/level/domain/XpFormula.java`
- [x] Вынести `LevelUpResult` из `LevelSystem` → `feature/level/domain/LevelUpResult.java`
- [x] Вынести `LevelProgress` из `LevelSystem` → `feature/level/domain/LevelProgress.java`
- [x] Переместить `level/UserStats` → `feature/level/domain/UserStats.java`
- [x] **НЕ делать immutable сейчас** (Gson требует сеттеры, сделаем после Repository)
- [x] Добавить `equals()` и `hashCode()` в `UserStats`
- [x] Переименовать `incrementMessages()` → `recordMessage(long timestamp)`
**5. Создать feature/level/service/**
- [x] Создать `feature/level/service/`
- [x] Создать `feature/level/service/MessageCooldownService.java`
- [x] Создать `feature/level/service/LeaderboardService.java`
- [x] Рефакторить `level/LevelSystem` → `feature/level/service/LevelService.java`
- [x] Использовать `UserStatsRepository` вместо `JsonDataStore`
- [x] Использовать `XpFormula` для вычислений
- [x] Использовать `MessageCooldownService` для кулдаунов
- [x] Убрать методы лидерборда (в `LeaderboardService`)
- [x] Обновить все импорты UserStats на новый путь
- [x] Удалить старый `level/UserStats.java`
- [x] Добавить сервисы в `BotModule`
**6. Переместить команды и листенеры в feature/level/**
- [x] Переместить `command/impl/stats/RankCommand` → `feature/level/RankCommand`
- [x] Переместить `command/impl/stats/StatsCommand` → `feature/level/StatsCommand`
- [x] Переместить `listener/LevelUpNotifier` → `feature/level/LevelUpNotifier`
- [x] Переместить `listener/VoiceTracker` → `feature/level/VoiceTracker`
- [x] Обновить все импорты
- [x] Обновить `BotInitializer`, `BotModule`, `PrefixCommandRegistrar`
- [x] Обновить команды для использования `LevelService` вместо `LevelSystem`
**7. Создать команду сброса уровней (миграция на новую систему)**
- [ ] Создать `feature/level/ResetLevelsCommand.java`:
- Только для администраторов
- Подтверждение через кнопки (Да/Нет)
- Сброс всех уровней и XP
- Очистка таблицы `user_stats` в БД
- Логирование действия
- Уведомление в канал логов
- Embed с предупреждением о необратимости
- [ ] Зарегистрировать команду в `PrefixCommandRegistrar`
- [ ] Добавить в категорию "Настройка" (только админы)
---
#### Этап 3: Остальные feature/ (параллельно, независимо)
**7. Создать feature/voice/ (голосовые каналы)**
- [ ] Создать `feature/voice/`
- [ ] Переместить `voice/VoiceChannelManager` → `feature/voice/`
- [ ] Переместить `voice/VoiceChannelData` → `feature/voice/`
- [ ] Переместить `voice/VoicePreset` → `feature/voice/`
- [ ] Переместить `command/impl/setup/VoiceSetupCommand` → `feature/voice/`
- [ ] Переместить `listener/VoiceChannelListener` → `feature/voice/`
- [ ] Переместить `listener/VoiceButtonHandler` → `feature/voice/`
- [ ] Удалить пустую папку `voice/`
- [ ] Обновить импорты
**8. Создать feature/counter/ (счетчик участников)**
- [ ] Создать `feature/counter/`
- [ ] Переместить `counter/CounterManager` → `feature/counter/`
- [ ] Переместить `counter/DailyStats` → `feature/counter/`
- [ ] Переместить `command/impl/setup/CounterSetupCommand` → `feature/counter/`
- [ ] Переместить `listener/MemberCounterListener` → `feature/counter/`
- [ ] Удалить пустую папку `counter/`
- [ ] Обновить импорты
**9. Создать feature/role/ (кастомные роли)**
- [ ] Создать `feature/role/`
- [ ] Переместить `role/CustomRoleManager` → `feature/role/`
- [ ] Переместить `command/impl/roles/CreateRoleCommand` → `feature/role/`
- [ ] Переместить `command/impl/roles/DeleteRoleCommand` → `feature/role/`
- [ ] Переместить `command/impl/roles/EditRoleCommand` → `feature/role/`
- [ ] Переместить `command/impl/roles/MyRolesCommand` → `feature/role/`
- [ ] Переместить `command/impl/roles/RoleCommandValidator` → `feature/role/`
- [ ] Удалить пустые папки `role/` и `command/impl/roles/`
- [ ] Обновить импорты
**10. Создать feature/reputation/ (система репутации)**
- [ ] Создать `feature/reputation/`
- [ ] Создать `feature/reputation/ReputationService.java` (вынести логику из команд)
- [ ] Переместить `command/impl/stats/RepCommand` → `feature/reputation/`
- [ ] Переместить `command/impl/stats/MinusRepCommand` → `feature/reputation/`
- [ ] Переместить `command/impl/stats/RepTopCommand` → `feature/reputation/`
- [ ] Рефакторить команды: использовать `ReputationService`
- [ ] Обновить импорты
**11. Создать feature/leaderboard/ (таблица лидеров)**
- [ ] Создать `feature/leaderboard/`
- [ ] Переместить `command/impl/stats/leaderboard/NewLeaderboardCommand` → `feature/leaderboard/LeaderboardCommand`
- [ ] Переместить `command/impl/stats/leaderboard/LeaderboardFormatter` → `feature/leaderboard/`
- [ ] Переместить `command/impl/stats/leaderboard/LeaderboardButtons` → `feature/leaderboard/`
- [ ] Переместить `command/impl/stats/leaderboard/LeaderboardButtonListener` → `feature/leaderboard/`
- [ ] Создать `feature/leaderboard/LeaderboardService.java` (использовать из level/)
- [ ] Удалить пустую папку `command/impl/stats/`
- [ ] Обновить импорты
**12. Создать feature/profile/ (профили пользователей)**
- [ ] Создать `feature/profile/`
- [ ] Переместить `profile/ProfileService` → `feature/profile/`
- [ ] Переместить `profile/ProfileGenerator` → `feature/profile/`
- [ ] Переместить `profile/ProfileData` → `feature/profile/`
- [ ] Переместить `command/impl/utility/ProfileCommand` → `feature/profile/`
- [ ] Создать `feature/profile/ProfileEmbedFactory.java` (вынести из команды)
- [ ] Удалить пустую папку `profile/`
- [ ] Обновить импорты
**13. Создать feature/metrics/ (метрики системы)**
- [ ] Создать `feature/metrics/`
- [ ] Переместить `metrics/MetricsCollector` → `feature/metrics/`
- [ ] Переместить `command/impl/utility/MetricsCommand` → `feature/metrics/`
- [ ] Создать `feature/metrics/MetricsService.java` (если нужна логика)
- [ ] Удалить пустую папку `metrics/`
- [ ] Обновить импорты
---
#### Этап 4: Инфраструктура (после feature/)
**14. Реорганизовать event/ (listener)**
- [ ] Создать `event/`
- [ ] Создать `event/gateway/` (JDA Gateway events)
- [ ] Переместить `listener/ReadyListener` → `event/gateway/`
- [ ] Переместить `listener/MessageListener` → `event/gateway/`
- [ ] Создать `event/gateway/GuildListener.java` (member join/leave + moderation)
- [ ] Переместить `listener/ModerationLogger` → `event/gateway/GuildListener` (или отдельно)
- [ ] Создать `event/interaction/` (Button/Menu interactions)
- [ ] Переместить `listener/SlashCommandListener` → `event/interaction/`
- [ ] Переместить `listener/HelpMenuHandler` → `event/interaction/`
- [ ] Удалить пустую папку `listener/`
- [ ] Обновить импорты
**15. Реорганизовать core/**
- [ ] Создать `core/registry/`
- [ ] Переместить `core/CommandRegistry` → `core/registry/`
- [ ] Переместить `core/SlashCommandRegistry` → `core/registry/`
- [ ] Переместить `core/PrefixCommandRegistrar` → `core/registry/`
- [ ] Переместить `core/SlashCommandRegistrar` → `core/registry/`
- [ ] Создать `core/exception/`
- [ ] Переместить `exception/CommandExceptionHandler` → `core/exception/`
- [ ] Переместить `exception/CommandException` → `command/CommandException`
- [ ] Удалить пустую папку `exception/`
- [ ] Обновить импорты
**16. Почистить command/**
- [ ] Переименовать `command/impl/entertainment/interaction/` → `command/entertainment/social/`
- [ ] Удалить `command/impl/setup/` (команды перенесены в feature/)
- [ ] Удалить `command/impl/utility/SwapCommand.java` (не используется)
- [ ] Оставить в `command/` только:
- Интерфейсы: `Command.java`, `CommandContext.java`, `SlashCommand.java`, `CommandException.java`
- `command/moderation/` (если не выносим в feature/)
- `command/entertainment/` (кросс-доменные развлечения)
- `command/utility/` (HelpCommand, PingCommand, InfoCommand, AvatarCommand, PollCommand)
**17. Вынести App.java из корня**
- [ ] Создать `bootstrap/`
- [ ] Создать `bootstrap/App.java`
- [ ] Переместить логику инициализации из корневого `App.java`
- [ ] Удалить корневой `App.java`
- [ ] Обновить `build.gradle` (mainClass)
---
#### Этап 5: Финальная полировка (опционально)
**18. UserStats immutable (после Repository)**
- [ ] Создать `UserStats.Builder`
- [ ] Убрать public сеттеры (оставить package-private для Gson)
- [ ] Добавить методы `addXP()`, `recordMessage()`, `addVoiceTime()`
- [ ] Добавить `getLevel(XpFormula)` вместо внешних вычислений
- [ ] Обновить `JsonUserStatsRepository` для работы с immutable
**19. Обновить документацию**
- [ ] Обновить `FEATURES.md` (убрать TTS, добавить новые фичи)
- [ ] Обновить `README.md` (новая структура проекта)
- [ ] Создать `ARCHITECTURE.md` (описание доменов и feature/)
- [ ] Обновить комментарии в коде
**20. Финальная проверка**
- [ ] `gradle build` - компиляция без ошибок
- [ ] Проверить все импорты
- [ ] Проверить что нет папок с >6 файлами
- [ ] Проверить что нет файлов >200 строк
- [ ] Запустить бота и протестировать основные команды
---
## Рефакторинг кода (Code Quality)
### Убрать if-else леса в rating командах
**Проблема:**
Все rating команды (MustacheCommand, IqCommand, FatCommand, GayCommand, PpCommand, BoykisserCommand) содержат огромные if-else цепочки для определения уровня/комментария/цвета.
**Пример плохого кода (MustacheCommand):**
```java
String comment;
Color color;
if (mustacheLevel >= 1000) {
comment = "🎩 ЛЕГЕНДАРНЫЕ УСИЩИ!";
color = new Color(255, 215, 0);
} else if (mustacheLevel >= 80) {
comment = "🧔 Роскошные усы!";
color = new Color(139, 69, 19);
} else if (mustacheLevel >= 60) {
comment = "👨 Хорошие усы";
color = new Color(160, 82, 45);
} else if (mustacheLevel >= 40) {
comment = "😊 Усики растут";
color = new Color(205, 133, 63);
} else if (mustacheLevel >= 20) {
comment = "🙂 Лёгкая щетина";
color = new Color(210, 180, 140);
} else if (mustacheLevel > 0) {
comment = "😐 Почти нет усов";
color = new Color(128, 128, 128);
} else {
comment = "🪒 Гладко выбрит";
color = new Color(200, 200, 200);
}
```
**Решение:**
Создать общий класс `RatingLevel` с конфигурацией уровней:
```java
public class RatingLevel {
private final int threshold;
private final String comment;
private final Color color;
public static RatingLevel findLevel(int value, List<RatingLevel> levels) {
return levels.stream()
.filter(level -> value >= level.threshold)
.findFirst()
.orElse(levels.get(levels.size() - 1));
}
}
```
**План действий:**
- [x] Создать `command/impl/entertainment/games/rating/RatingLevel.java`
- [x] Создать `command/impl/entertainment/games/rating/RatingConfig.java` с предустановленными конфигурациями
- [x] Рефакторить MustacheCommand - использовать RatingLevel
- [x] Рефакторить IqCommand - использовать RatingLevel
- [x] Рефакторить FatCommand - использовать RatingLevel
- [x] Рефакторить GayCommand - использовать RatingLevel (без уровней, только прогресс-бар)
- [x] Рефакторить PpCommand - использовать RatingLevel
- [x] Рефакторить BoykisserCommand - использовать RatingLevel
- [x] Проверить что все команды работают корректно
- [x] Удалить дублирующийся код
**Приоритет:** ✅ **ГОТОВО**
---
## Основные задачи
### 1. ⚡ Переделать систему опыта (XP System Rework)
**Текущая проблема:**
- Формула: `XP = 100 * level^1.5` (слишком сложная)
- XP за сообщение: 10 XP
- XP за минуту в войсе: 5 XP
- Cooldown на сообщения: 60 секунд
**Новая система (из FEATURES.md):**
- ✅ **Формула прогрессии:** `XP для следующего уровня = 200 * текущий_уровень`
- Пример: уровень 1 → 2 требует 200 XP
- Уровень 5 → 6 требует 1000 XP
- Уровень 10 → 11 требует 2000 XP
- ✅ **Голосовая активность:**
- 1 XP каждую минуту нахождения в голосовом канале
- Автоматическое отслеживание через `VoiceXpListener`
- Уведомление при повышении уровня в войс-канале
**Файлы для изменения:**
- `src/main/java/creator/loki/level/LevelSystem.java` - изменить формулу
- `src/main/java/creator/loki/listener/VoiceTracker.java` - изменить начисление XP
- Добавить уведомления о level up в войсе
**Статус:** ✅ **ГОТОВО**
---
### 2. 🎤 Система личных голосовых каналов (Voice Management System)
**Концепция:**
Пользователь заходит в специальный канал-триггер → бот автоматически создает личный войс-канал → перемещает пользователя туда → пишет сообщение в чат войса с настройками
**Функционал:**
#### 2.1 Автоматическое создание каналов
- ✅ Создать `VoiceChannelManager` класс
- ✅ Listener на вход в канал-триггер (например, "➕ Создать канал")
- ✅ Автоматическое создание канала с именем пользователя
- ✅ Перемещение пользователя в созданный канал
- ✅ Выдача прав управления владельцу канала
- ✅ Автоматическое удаление канала когда он пустой
#### 2.2 Сообщение с настройками в чате войса
При создании канала бот отправляет embed с кнопками:
- 🔒 **Закрыть/Открыть** - переключение приватности
- 👥 **Лимит** - установить лимит пользователей (0-99)
- ✏️ **Переименовать** - изменить название канала
- 🎨 **Битрейт** - изменить качество звука
- 🚫 **Удалить** - удалить канал
#### 2.3 Система пресетов
- ✅ Сохранение настроек как пресет
- ✅ Быстрое применение сохраненных настроек
- ✅ Хранение в `data/voice_presets.json`
**Структура данных:**
```json
{
"voice_channels": {
"channel_id": {
"owner_id": "user_id",
"created_at": 1234567890,
"settings": {
"name": "Канал пользователя",
"limit": 5,
"bitrate": 64000,
"locked": false
}
}
},
"presets": {
"user_id": [
{
"name": "Gaming",
"limit": 4,
"bitrate": 96000
}
]
}
}
```
**Файлы для создания:**
- `src/main/java/creator/loki/voice/VoiceChannelManager.java`
- `src/main/java/creator/loki/voice/VoiceChannelData.java`
- `src/main/java/creator/loki/voice/VoicePreset.java`
- `src/main/java/creator/loki/listener/VoiceChannelListener.java`
- `src/main/java/creator/loki/command/impl/voice/VoiceSettingsCommand.java` (для кнопок)
**Статус:** ✅ **ГОТОВО** (базовая реализация, кнопки управления требуют обработчиков)
---
### 3. 📊 Счетчик участников (Member Counter)
**Функционал:**
- ✅ Обновление названия голосового канала с количеством участников
- ✅ Формат: `【👥】Участники: 123`
- ✅ Отслеживание дневной статистики:
- Количество присоединившихся за день
- Количество покинувших за день
- Разница за 24 часа
- ✅ Обновление каждые 5 минут
- ✅ Сохранение статистики в `data/counter_data.json`
**Структура данных:**
```json
{
"counter_channel_id": "1234567890",
"daily_stats": {
"date": "2026-05-11",
"joined": 15,
"left": 8,
"difference": 7
},
"history": [
{
"date": "2026-05-10",
"joined": 20,
"left": 12,
"difference": 8
}
]
}
```
**Файлы для создания:**
- `src/main/java/creator/loki/listener/MemberCounterListener.java`
- `src/main/java/creator/loki/counter/CounterManager.java`
- `src/main/java/creator/loki/counter/DailyStats.java`
**Статус:** 🔴 Не начато
---
# TODO — Loki Bot v2.1
## ✅ Что уже сделано
### ✅ Система опыта переделана
- Новая формула: `XP для уровня N = 200 * N`
- 1 XP за минуту в войсе
- Обновление каждую минуту вместо 5 минут
- Метод `addVoiceTime` теперь возвращает `LevelUpResult` для уведомлений
### ✅ Система личных войс-каналов (базовая)
- `VoiceChannelManager` - управление каналами
- `VoiceChannelListener` - автоматическое создание/удаление
- `VoiceChannelData` и `VoicePreset` - модели данных
- Автоматическое создание при входе в триггер-канал
- Автоматическое удаление пустых каналов
- Панель управления с кнопками (требуется обработчик кнопок)
### ✅ Счетчик участников
- `CounterManager` - управление счетчиком
- `MemberCounterListener` - отслеживание входов/выходов
- `DailyStats` - дневная статистика
- Обновление каждые 5 минут
- История за 30 дней
### ✅ SQLite база данных + автомиграция
- `Database` - менеджер SQLite
- `DatabaseMigration` - автоматическая миграция из JSON
- `ConfigRepository` - работа с конфигурацией
- Все таблицы созданы автоматически
- Миграция данных при первом запуске
### ✅ Команды настройки
- `/voice-setup trigger` - установить канал-триггер
- `/voice-setup category` - установить категорию
- `/voice-setup text-channel` - установить текстовый канал
- `/voice-setup info` - показать конфигурацию
- `/counter-setup channel` - установить канал счетчика
- `/counter-setup format` - установить формат
- `/counter-setup info` - показать конфигурацию
| # | Статус | Проблема | Изменения |
|---|--------|----------|-----------|
| 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 |
---
## 🔧 Дополнительные улучшения
## ♻️ Правила разработки
### 4. 🎨 Улучшение уведомлений о level up
- ✅ Красивые embed сообщения при повышении уровня
- ✅ Отправка в тот же канал где получен XP
- ✅ Для войса - отправка в текстовый канал связанный с войсом
- ✅ Показывать прогресс до следующего уровня
**Статус:** 🔴 Не начато
- KISS, DRY, SOLID
- Файлы ≤150–200 строк (в идеале ≤100)
- В папке ≤6 файлов
- Пакет = домен/фича, а не тип файла (`feature/level/`, а не `commands/listeners/`)
- Каждая фича содержит свои команды, сервисы, репозитории
- Единый слой персистентности через `repository/`
- Интеграции в `integration/`
---
### 5. 🛠️ Команды для настройки систем (Admin Commands)
## 🔴 Проблемы (результаты аудита, которые ещё не исправлены)
#### Voice System Setup:
- `/voice-setup trigger <channel>` - установить канал-триггер
- `/voice-setup category <category>` - установить категорию для личных каналов
- `/voice-setup text-channel <channel>` - установить текстовый канал для уведомлений
### HIGH
#### Counter Setup:
- `/counter-setup channel <voice-channel>` - установить канал для счетчика
- `/counter-setup format <format>` - изменить формат отображения
- `/counter stats` - показать статистику за период
| # | Приоритет | Проблема | Статус |
|---|-----------|----------|--------|
| 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. ⚡ Переделать систему опыта (простая формула)
2. 🎤 Базовая система личных войс-каналов (создание/удаление)
3. 📊 Счетчик участников
### Этап 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)
### Средний приоритет:
4. 🎤 Настройки войс-каналов через кнопки
5. 🎨 Улучшение уведомлений о level up
6. 🎤 Система пресетов для войс-каналов
### Этап 2: Рефакторинг PrefixCommandRegistrar ✅
- [x] Создан интерфейс `CommandRegistrar`
- [x] 4 per-feature регистратора
- [x] Guice Multibinder для сбора регистраторов
- [x] 22 параметра → 1 (`Set<CommandRegistrar>`)
### Низкий приоритет:
7. 🛠️ Админ команды для настройки
### Этап 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/
---
## 🐛 Известные проблемы (Bugs to Fix)
## 🚀 Новые фичи (Roadmap)
### Критические:
- [ ] Проверить корректность начисления XP в войсе
- [ ] Убедиться что VoiceTracker правильно отслеживает время
## 🚀 Экономика — план реализации (в работе)
### Некритические:
- [ ] Удалить неиспользуемый logger в LevelSystem.java
- [ ] Убрать ненужный @SuppressWarnings в LevelSystem.java
### Модель данных
---
```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
);
```
## 📚 Технические детали
### Заработок
### Зависимости:
- JDA 5.0.0 (уже есть)
- Guice для DI (уже есть)
- Gson для JSON (уже есть)
| Механика | Доход | Кулдаун |
|---|---|---|
| Сообщение | 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 | — |
### Конфигурация (добавить в config):
```yaml
voice:
trigger_channel_id: 1234567890
category_id: 1234567890
text_channel_id: 1234567890
auto_delete_empty: true
counter:
channel_id: 1234567890
update_interval: 300 # секунды
format: "【👥】Участники: {count}"
### Миниигры
**Рыбалка** (`!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
- [ ] Система достижений (Achievements)
- [x] Система экономики (Economy) — **в работе**
- [ ] Все функции протестированы
- [ ] Логирование работает корректно
- [ ] Нет утечек памяти (проверить scheduler'ы)
- [ ] Graceful shutdown для всех scheduler'ов
- [ ] Документация обновлена
- [ ] FEATURES.md актуализирован
### Приоритет 2
- [x] Авто-модерация (3 варна = авто-мут на 1ч)
- [x] Музыка (lavaplayer: play, skip, stop, queue, np, volume, leave)
- [x] ~~Музыка — поиск с выбором (yt/sc/list + выбор номера)~~
- [x] Server Stats (участники, каналы, роли, бусты)
- [ ] Level Roles (роли за уровни)
- [ ] Красивые level-up embed с прогресс-баром
### Приоритет 3
- [ ] Статистика сервера — графики, CSV export
- [ ] Система квестов
- [ ] Wiki по установке и командам
---
**Дата создания:** 2026-05-11
**Версия:** 1.0
**Автор:** Loki Bot Team
**Версия:** 2.2 | **Последнее обновление:** 2026-06-20 | **Java 21 ✅, God Constructor ✅, Critical баги ✅, Economy 🚧**