Initial commit: Discord bot with XP system, voice channels, moderation logs, and member counter
This commit is contained in:
commit
0f664e317e
132 changed files with 12415 additions and 0 deletions
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Gradle
|
||||
.gradle/
|
||||
build/
|
||||
out/
|
||||
bin/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.vscode/
|
||||
|
||||
# Config files with secrets
|
||||
config/application.yml
|
||||
.env
|
||||
*.env
|
||||
|
||||
# Data files
|
||||
data/
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Java
|
||||
*.class
|
||||
*.jar
|
||||
!gradle-wrapper.jar
|
||||
235
FEATURES.md
Normal file
235
FEATURES.md
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
# 🤖 Loki Discord Bot - Функции
|
||||
|
||||
## 📋 Описание
|
||||
|
||||
Многофункциональный Discord бот с системой уровней, личными голосовыми каналами, модерацией и развлекательными командами.
|
||||
|
||||
**Технологии:**
|
||||
- Java 21
|
||||
- JDA 5.0.0
|
||||
- SQLite + автомиграция
|
||||
- Google Guice (DI)
|
||||
|
||||
---
|
||||
|
||||
## ⭐ Основные функции
|
||||
|
||||
### 🎯 Система уровней и опыта
|
||||
- **Формула:** 200 XP × уровень
|
||||
- **Получение XP:**
|
||||
- 10 XP за сообщение (cooldown 60 сек)
|
||||
- 1 XP за минуту в войсе
|
||||
- **Команды:**
|
||||
- `/rank [@user]` - карточка ранга
|
||||
- `/leaderboard` - топ по уровням/сообщениям/войсу
|
||||
- **Уведомления:** Красивые embed при повышении уровня
|
||||
- **Награды:** Роли за достижение уровней (5, 10, 20, 30, 50)
|
||||
|
||||
### 🎤 Личные голосовые каналы
|
||||
- **Автосоздание:** Зайди в канал-триггер → создастся личный канал
|
||||
- **Управление через кнопки:**
|
||||
- 🔒 Закрыть/Открыть
|
||||
- 👥 Лимит пользователей
|
||||
- ✏️ Переименовать
|
||||
- 🎨 Битрейт
|
||||
- 🚫 Удалить
|
||||
- **Автоудаление:** Пустые каналы удаляются автоматически
|
||||
- **Настройка:** `/voice-setup`
|
||||
|
||||
### 📊 Счетчик участников
|
||||
- **Автообновление:** Название канала с количеством участников
|
||||
- **Формат:** `【👥】Участники: 123`
|
||||
- **Статистика:** Присоединения/выходы за день
|
||||
- **История:** 30 дней
|
||||
- **Настройка:** `/counter-setup`
|
||||
|
||||
### 📝 Логи модерации
|
||||
- **Что логируется:**
|
||||
- 🗑️ Удаление сообщений
|
||||
- 🔨 Баны/разбаны
|
||||
- 👢 Кики
|
||||
- 🔇 Муты/размуты
|
||||
- 🎤 Отключение микрофона/звука
|
||||
- **Настройка:** `/log-setup moderation`
|
||||
|
||||
### 🎮 Развлекательные команды
|
||||
|
||||
**Игры:**
|
||||
- `/coin` - подбросить монетку
|
||||
- `/dice` - бросить кубик
|
||||
- `/8ball` - магический шар
|
||||
- `/ship @user1 @user2` - совместимость
|
||||
- `/rate` - оценить что-то
|
||||
- `/gay/@fat/@iq/@pp` - процентомеры
|
||||
|
||||
**Взаимодействия:**
|
||||
- `/hug/@kiss/@pat/@poke` - обнять/поцеловать/погладить/ткнуть
|
||||
- `/slap/@tickle/@cuddle` - шлепнуть/пощекотать/обнять
|
||||
- `/compliment/@roast` - комплимент/жарка
|
||||
- `/feed` - покормить
|
||||
|
||||
### 🛡️ Модерация
|
||||
- `/ban/@kick` - бан/кик пользователя
|
||||
- `/clear <amount>` - очистить сообщения
|
||||
- Автологирование всех действий
|
||||
|
||||
### 🎨 Кастомные роли
|
||||
- `/createrole` - создать роль
|
||||
- `/deleterole` - удалить роль
|
||||
- `/editrole` - редактировать роль
|
||||
- `/myroles` - мои роли
|
||||
|
||||
### 📈 Статистика
|
||||
- `/stats [@user]` - статистика пользователя
|
||||
- `/rep/@minusrep` - репутация
|
||||
- `/reptop` - топ по репутации
|
||||
- `/serverstats` - статистика сервера
|
||||
|
||||
### 🔧 Утилиты
|
||||
- `/avatar [@user]` - аватар пользователя
|
||||
- `/info [@user]` - информация о пользователе
|
||||
- `/poll` - создать голосование
|
||||
- `/ping` - пинг бота
|
||||
- `/help` - справка
|
||||
- `/tts <text>` - озвучить текст
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Команды настройки (только админы)
|
||||
|
||||
### Личные войс-каналы
|
||||
```
|
||||
/voice-setup trigger <channel> - канал-триггер
|
||||
/voice-setup category <category> - категория для каналов
|
||||
/voice-setup text-channel <channel> - текстовый канал для панелей
|
||||
/voice-setup info - показать настройки
|
||||
```
|
||||
|
||||
### Счетчик участников
|
||||
```
|
||||
/counter-setup channel <channel> - установить канал
|
||||
/counter-setup format <format> - формат ({count})
|
||||
/counter-setup info - показать настройки
|
||||
```
|
||||
|
||||
### Логирование
|
||||
```
|
||||
/log-setup moderation <channel> - канал логов модерации
|
||||
/log-setup levelup <channel> - канал уведомлений level up
|
||||
/log-setup info - показать настройки
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ База данных
|
||||
|
||||
**SQLite** с автоматической миграцией из JSON:
|
||||
- `config` - конфигурация
|
||||
- `user_stats` - статистика пользователей
|
||||
- `reputation` - репутация
|
||||
- `ship_pairs` - ship пары
|
||||
- `custom_roles` - кастомные роли
|
||||
- `voice_channels` - личные каналы
|
||||
- `counter_stats` - статистика счетчика
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
1. **Запуск:**
|
||||
```bash
|
||||
gradle run
|
||||
```
|
||||
|
||||
2. **Настройка:**
|
||||
```
|
||||
/voice-setup trigger #создать-канал
|
||||
/voice-setup category Личные каналы
|
||||
/voice-setup text-channel #управление
|
||||
|
||||
/counter-setup channel #счетчик
|
||||
/counter-setup format 【👥】Участники: {count}
|
||||
|
||||
/log-setup moderation #логи
|
||||
/log-setup levelup #level-up
|
||||
```
|
||||
|
||||
3. **Готово!** Зайди в канал-триггер → создастся личный канал
|
||||
|
||||
---
|
||||
|
||||
## 📦 Структура проекта
|
||||
|
||||
```
|
||||
src/main/java/creator/loki/
|
||||
├── audio/ # TTS и аудио
|
||||
├── command/ # Команды
|
||||
│ └── impl/
|
||||
│ ├── entertainment/ # Игры и взаимодействия
|
||||
│ ├── moderation/ # Модерация
|
||||
│ ├── roles/ # Кастомные роли
|
||||
│ ├── setup/ # Настройка
|
||||
│ ├── stats/ # Статистика
|
||||
│ └── utility/ # Утилиты
|
||||
├── config/ # Конфигурация
|
||||
├── core/ # Ядро бота
|
||||
├── counter/ # Счетчик участников
|
||||
├── data/ # JSON хранилище
|
||||
├── database/ # SQLite + миграция
|
||||
├── level/ # Система уровней
|
||||
├── listener/ # Event listeners
|
||||
├── voice/ # Личные каналы
|
||||
└── util/ # Утилиты
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Особенности
|
||||
|
||||
✅ **Dependency Injection** (Google Guice)
|
||||
✅ **SQLite** с автомиграцией из JSON
|
||||
✅ **Graceful shutdown** всех компонентов
|
||||
✅ **Логирование** (SLF4J + Logback)
|
||||
✅ **Кэширование** (Caffeine)
|
||||
✅ **Rate limiting** и cooldowns
|
||||
✅ **Метрики** производительности
|
||||
✅ **Модульная архитектура**
|
||||
|
||||
---
|
||||
|
||||
## 📝 Права бота
|
||||
|
||||
Необходимые права:
|
||||
- ✅ `MANAGE_CHANNELS` - создание/удаление каналов
|
||||
- ✅ `MOVE_MEMBERS` - перемещение пользователей
|
||||
- ✅ `MANAGE_PERMISSIONS` - выдача прав
|
||||
- ✅ `MANAGE_ROLES` - управление ролями
|
||||
- ✅ `BAN_MEMBERS` - баны
|
||||
- ✅ `KICK_MEMBERS` - кики
|
||||
- ✅ `MANAGE_MESSAGES` - удаление сообщений
|
||||
- ✅ `VIEW_AUDIT_LOG` - просмотр логов
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Обновления
|
||||
|
||||
### v2.0 (2026-05-11)
|
||||
- ✅ SQLite база данных
|
||||
- ✅ Автомиграция из JSON
|
||||
- ✅ Личные голосовые каналы
|
||||
- ✅ Счетчик участников
|
||||
- ✅ Логи модерации
|
||||
- ✅ Уведомления level up
|
||||
- ✅ Обработчик кнопок для войс-каналов
|
||||
|
||||
### v1.0
|
||||
- Базовая система уровней
|
||||
- Команды развлечений
|
||||
- Модерация
|
||||
- Кастомные роли
|
||||
|
||||
---
|
||||
|
||||
**Версия:** 2.0
|
||||
**Дата:** 2026-05-11
|
||||
**Автор:** Loki Bot Team
|
||||
46
README.md
Normal file
46
README.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# 🤖 Loki Discord Bot
|
||||
|
||||
Многофункциональный Discord бот с системой уровней, личными голосовыми каналами и модерацией.
|
||||
|
||||
## ⚡ Быстрый старт
|
||||
|
||||
```bash
|
||||
# Запуск
|
||||
gradle run
|
||||
|
||||
# Настройка через Discord
|
||||
/voice-setup trigger #создать-канал
|
||||
/counter-setup channel #счетчик
|
||||
/log-setup moderation #логи
|
||||
```
|
||||
|
||||
## 📚 Документация
|
||||
|
||||
- **[FEATURES.md](FEATURES.md)** - Полный список функций
|
||||
- **[TODO.md](TODO.md)** - План развития
|
||||
|
||||
## 🎯 Основные функции
|
||||
|
||||
- 🎯 Система уровней (200 XP × уровень)
|
||||
- 🎤 Личные голосовые каналы
|
||||
- 📊 Счетчик участников
|
||||
- 📝 Логи модерации
|
||||
- 🎮 Развлекательные команды
|
||||
- 🛡️ Модерация
|
||||
- 🎨 Кастомные роли
|
||||
|
||||
## 🛠️ Технологии
|
||||
|
||||
- Java 21
|
||||
- JDA 5.0.0
|
||||
- SQLite
|
||||
- Google Guice
|
||||
|
||||
## 📦 Требования
|
||||
|
||||
- Java 21+
|
||||
- Gradle 8.x
|
||||
|
||||
---
|
||||
|
||||
**Версия:** 2.0 | **Лицензия:** MIT
|
||||
275
TODO.md
Normal file
275
TODO.md
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
# TODO - План реализации функций
|
||||
|
||||
## 🎯 Основные задачи
|
||||
|
||||
### 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`
|
||||
|
||||
**Статус:** 🔴 Не начато
|
||||
|
||||
---
|
||||
|
||||
## ✅ Что уже сделано
|
||||
|
||||
### ✅ Система опыта переделана
|
||||
- Новая формула: `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` - показать конфигурацию
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Дополнительные улучшения
|
||||
|
||||
### 4. 🎨 Улучшение уведомлений о level up
|
||||
- ✅ Красивые embed сообщения при повышении уровня
|
||||
- ✅ Отправка в тот же канал где получен XP
|
||||
- ✅ Для войса - отправка в текстовый канал связанный с войсом
|
||||
- ✅ Показывать прогресс до следующего уровня
|
||||
|
||||
**Статус:** 🔴 Не начато
|
||||
|
||||
---
|
||||
|
||||
### 5. 🛠️ Команды для настройки систем (Admin Commands)
|
||||
|
||||
#### Voice System Setup:
|
||||
- `/voice-setup trigger <channel>` - установить канал-триггер
|
||||
- `/voice-setup category <category>` - установить категорию для личных каналов
|
||||
- `/voice-setup text-channel <channel>` - установить текстовый канал для уведомлений
|
||||
|
||||
#### Counter Setup:
|
||||
- `/counter-setup channel <voice-channel>` - установить канал для счетчика
|
||||
- `/counter-setup format <format>` - изменить формат отображения
|
||||
- `/counter stats` - показать статистику за период
|
||||
|
||||
**Статус:** 🔴 Не начато
|
||||
|
||||
---
|
||||
|
||||
## 📝 Приоритеты реализации
|
||||
|
||||
### Высокий приоритет (сделать в первую очередь):
|
||||
1. ⚡ Переделать систему опыта (простая формула)
|
||||
2. 🎤 Базовая система личных войс-каналов (создание/удаление)
|
||||
3. 📊 Счетчик участников
|
||||
|
||||
### Средний приоритет:
|
||||
4. 🎤 Настройки войс-каналов через кнопки
|
||||
5. 🎨 Улучшение уведомлений о level up
|
||||
6. 🎤 Система пресетов для войс-каналов
|
||||
|
||||
### Низкий приоритет:
|
||||
7. 🛠️ Админ команды для настройки
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Известные проблемы (Bugs to Fix)
|
||||
|
||||
### Критические:
|
||||
- [ ] Проверить корректность начисления XP в войсе
|
||||
- [ ] Убедиться что VoiceTracker правильно отслеживает время
|
||||
|
||||
### Некритические:
|
||||
- [ ] Удалить неиспользуемый logger в LevelSystem.java
|
||||
- [ ] Убрать ненужный @SuppressWarnings в LevelSystem.java
|
||||
|
||||
---
|
||||
|
||||
## 📚 Технические детали
|
||||
|
||||
### Зависимости:
|
||||
- JDA 5.0.0 (уже есть)
|
||||
- Guice для DI (уже есть)
|
||||
- Gson для JSON (уже есть)
|
||||
|
||||
### Конфигурация (добавить в 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}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Чеклист перед релизом
|
||||
|
||||
- [ ] Все функции протестированы
|
||||
- [ ] Логирование работает корректно
|
||||
- [ ] Нет утечек памяти (проверить scheduler'ы)
|
||||
- [ ] Graceful shutdown для всех scheduler'ов
|
||||
- [ ] Документация обновлена
|
||||
- [ ] FEATURES.md актуализирован
|
||||
|
||||
---
|
||||
|
||||
**Дата создания:** 2026-05-11
|
||||
**Версия:** 1.0
|
||||
**Автор:** Loki Bot Team
|
||||
75
build.gradle
Normal file
75
build.gradle
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
}
|
||||
|
||||
group = 'creator.loki'
|
||||
version = '1.0.0'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// JDA - Discord API
|
||||
implementation('net.dv8tion:JDA:5.0.0-beta.20') {
|
||||
exclude module: 'opus-java'
|
||||
}
|
||||
|
||||
// LavaPlayer для голосового TTS
|
||||
implementation 'dev.arbjerg:lavaplayer:2.2.2'
|
||||
|
||||
// dotenv для загрузки .env
|
||||
implementation 'io.github.cdimascio:dotenv-java:3.0.0'
|
||||
|
||||
// Logging
|
||||
implementation 'org.slf4j:slf4j-api:2.0.12'
|
||||
implementation 'ch.qos.logback:logback-classic:1.5.3'
|
||||
|
||||
// JSON
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
|
||||
// Caffeine Cache
|
||||
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
|
||||
|
||||
// Google Guice - Dependency Injection
|
||||
implementation 'com.google.inject:guice:7.0.0'
|
||||
|
||||
// SQLite Database
|
||||
implementation 'org.xerial:sqlite-jdbc:3.45.1.0'
|
||||
|
||||
// Testing
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
|
||||
testImplementation 'org.mockito:mockito-core:5.11.0'
|
||||
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass = 'creator.loki.App'
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes(
|
||||
'Main-Class': 'creator.loki.App'
|
||||
)
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
from {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
}
|
||||
10
gradle.properties
Normal file
10
gradle.properties
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Dependencies
|
||||
jdaVersion=5.0.0-beta.20
|
||||
dotenvVersion=3.0.0
|
||||
slf4jVersion=2.0.12
|
||||
logbackVersion=1.5.3
|
||||
|
||||
# Gradle
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
1
settings.gradle
Normal file
1
settings.gradle
Normal file
|
|
@ -0,0 +1 @@
|
|||
rootProject.name = 'discord-bot'
|
||||
36
src/main/java/creator/loki/App.java
Normal file
36
src/main/java/creator/loki/App.java
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package creator.loki;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import creator.loki.core.BotInitializer;
|
||||
import creator.loki.di.BotModule;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class App {
|
||||
private static final Logger logger = LoggerFactory.getLogger(App.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
logger.info("Запуск Discord бота с Dependency Injection...");
|
||||
|
||||
// Создаём Guice Injector с модулем конфигурации
|
||||
Injector injector = Guice.createInjector(new BotModule());
|
||||
|
||||
// Получаем BotInitializer с автоматически внедрёнными зависимостями
|
||||
BotInitializer initializer = injector.getInstance(BotInitializer.class);
|
||||
|
||||
// Запускаем бота
|
||||
JDA jda = initializer.start();
|
||||
|
||||
// Ждём полной загрузки
|
||||
jda.awaitReady();
|
||||
logger.info("Бот полностью готов к работе!");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Критическая ошибка при запуске бота", e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
163
src/main/java/creator/loki/audio/AudioManager.java
Normal file
163
src/main/java/creator/loki/audio/AudioManager.java
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
package creator.loki.audio;
|
||||
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
|
||||
import com.sedmelluq.discord.lavaplayer.track.playback.NonAllocatingAudioFrameBuffer;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AudioManager {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AudioManager.class);
|
||||
private static final long CLEANUP_INTERVAL_MINUTES = 10;
|
||||
private static final long INACTIVE_THRESHOLD_MINUTES = 30;
|
||||
|
||||
private final AudioPlayerManager playerManager;
|
||||
private final Map<Long, GuildMusicManager> musicManagers;
|
||||
private final Map<Long, Long> lastActivityTime; // guildId -> timestamp
|
||||
private final Map<Long, TtsQueue> ttsQueues; // guildId -> TtsQueue
|
||||
private final ScheduledExecutorService cleanupScheduler;
|
||||
|
||||
public AudioManager() {
|
||||
this.musicManagers = new ConcurrentHashMap<>();
|
||||
this.lastActivityTime = new ConcurrentHashMap<>();
|
||||
this.ttsQueues = new ConcurrentHashMap<>();
|
||||
this.playerManager = new DefaultAudioPlayerManager();
|
||||
this.playerManager.getConfiguration().setFrameBufferFactory(NonAllocatingAudioFrameBuffer::new);
|
||||
AudioSourceManagers.registerRemoteSources(playerManager);
|
||||
AudioSourceManagers.registerLocalSource(playerManager);
|
||||
|
||||
// Запускаем периодическую очистку неактивных менеджеров
|
||||
this.cleanupScheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
this.cleanupScheduler.scheduleAtFixedRate(
|
||||
this::cleanupInactiveManagers,
|
||||
CLEANUP_INTERVAL_MINUTES,
|
||||
CLEANUP_INTERVAL_MINUTES,
|
||||
TimeUnit.MINUTES
|
||||
);
|
||||
|
||||
logger.info("AudioManager инициализирован с автоочисткой каждые {} минут", CLEANUP_INTERVAL_MINUTES);
|
||||
}
|
||||
|
||||
public synchronized GuildMusicManager getGuildAudioPlayer(Guild guild) {
|
||||
long guildId = guild.getIdLong();
|
||||
GuildMusicManager musicManager = musicManagers.get(guildId);
|
||||
|
||||
if (musicManager == null) {
|
||||
musicManager = new GuildMusicManager(playerManager);
|
||||
musicManagers.put(guildId, musicManager);
|
||||
logger.debug("Создан новый GuildMusicManager для гильдии: {}", guild.getName());
|
||||
}
|
||||
|
||||
// Обновляем время последней активности
|
||||
lastActivityTime.put(guildId, System.currentTimeMillis());
|
||||
|
||||
guild.getAudioManager().setSendingHandler(musicManager.getSendHandler());
|
||||
return musicManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Очищает неактивные GuildMusicManager
|
||||
*/
|
||||
private void cleanupInactiveManagers() {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long thresholdTime = currentTime - TimeUnit.MINUTES.toMillis(INACTIVE_THRESHOLD_MINUTES);
|
||||
|
||||
musicManagers.entrySet().removeIf(entry -> {
|
||||
long guildId = entry.getKey();
|
||||
Long lastActivity = lastActivityTime.get(guildId);
|
||||
|
||||
if (lastActivity == null || lastActivity < thresholdTime) {
|
||||
GuildMusicManager manager = entry.getValue();
|
||||
|
||||
// Проверяем, что плеер не активен
|
||||
if (manager.getPlayer().getPlayingTrack() == null) {
|
||||
logger.info("Удаление неактивного GuildMusicManager для гильдии ID: {}", guildId);
|
||||
manager.getPlayer().destroy();
|
||||
lastActivityTime.remove(guildId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Принудительно удаляет GuildMusicManager для гильдии
|
||||
*/
|
||||
public synchronized void removeGuildAudioPlayer(long guildId) {
|
||||
GuildMusicManager manager = musicManagers.remove(guildId);
|
||||
if (manager != null) {
|
||||
manager.getPlayer().destroy();
|
||||
lastActivityTime.remove(guildId);
|
||||
logger.debug("GuildMusicManager удалён для гильдии ID: {}", guildId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить или создать TtsQueue для гильдии
|
||||
*/
|
||||
public TtsQueue getTtsQueue(Guild guild) {
|
||||
long guildId = guild.getIdLong();
|
||||
return ttsQueues.computeIfAbsent(guildId, id -> {
|
||||
logger.info("Создана новая TtsQueue для гильдии: {}", guild.getName());
|
||||
return new TtsQueue();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавить TTS запрос в очередь
|
||||
*/
|
||||
public void enqueueTts(Guild guild, String text, net.dv8tion.jda.api.entities.channel.middleman.MessageChannel channel) {
|
||||
GuildMusicManager musicManager = getGuildAudioPlayer(guild);
|
||||
TtsQueue queue = getTtsQueue(guild);
|
||||
|
||||
TtsRequest request = new TtsRequest(this, musicManager, guild, text, channel);
|
||||
queue.enqueue(request);
|
||||
|
||||
logger.info("TTS запрос добавлен для гильдии: {} (очередь: {})",
|
||||
guild.getName(), queue.getQueueSize());
|
||||
}
|
||||
|
||||
public AudioPlayerManager getPlayerManager() {
|
||||
return playerManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Завершает работу AudioManager
|
||||
*/
|
||||
public void shutdown() {
|
||||
logger.info("Завершение работы AudioManager...");
|
||||
|
||||
// Останавливаем все TTS очереди
|
||||
ttsQueues.values().forEach(TtsQueue::shutdown);
|
||||
ttsQueues.clear();
|
||||
|
||||
// Останавливаем scheduler
|
||||
cleanupScheduler.shutdown();
|
||||
try {
|
||||
if (!cleanupScheduler.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
cleanupScheduler.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
cleanupScheduler.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
// Уничтожаем все плееры
|
||||
musicManagers.values().forEach(manager -> manager.getPlayer().destroy());
|
||||
musicManagers.clear();
|
||||
lastActivityTime.clear();
|
||||
|
||||
logger.info("AudioManager завершён");
|
||||
}
|
||||
}
|
||||
36
src/main/java/creator/loki/audio/AudioPlayerSendHandler.java
Normal file
36
src/main/java/creator/loki/audio/AudioPlayerSendHandler.java
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package creator.loki.audio;
|
||||
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
|
||||
import com.sedmelluq.discord.lavaplayer.track.playback.MutableAudioFrame;
|
||||
import net.dv8tion.jda.api.audio.AudioSendHandler;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class AudioPlayerSendHandler implements AudioSendHandler {
|
||||
private final AudioPlayer audioPlayer;
|
||||
private final ByteBuffer buffer;
|
||||
private final MutableAudioFrame frame;
|
||||
|
||||
public AudioPlayerSendHandler(AudioPlayer audioPlayer) {
|
||||
this.audioPlayer = audioPlayer;
|
||||
this.buffer = ByteBuffer.allocate(1024);
|
||||
this.frame = new MutableAudioFrame();
|
||||
this.frame.setBuffer(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProvide() {
|
||||
return audioPlayer.provide(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer provide20MsAudio() {
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpus() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
52
src/main/java/creator/loki/audio/GuildMusicManager.java
Normal file
52
src/main/java/creator/loki/audio/GuildMusicManager.java
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package creator.loki.audio;
|
||||
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class GuildMusicManager {
|
||||
private static final Logger logger = LoggerFactory.getLogger(GuildMusicManager.class);
|
||||
private final AudioPlayer player;
|
||||
private final AudioPlayerSendHandler sendHandler;
|
||||
private Guild guild;
|
||||
private boolean autoDisconnectEnabled = false;
|
||||
|
||||
public GuildMusicManager(AudioPlayerManager manager) {
|
||||
this.player = manager.createPlayer();
|
||||
this.sendHandler = new AudioPlayerSendHandler(player);
|
||||
}
|
||||
|
||||
public void setGuild(Guild guild) {
|
||||
this.guild = guild;
|
||||
}
|
||||
|
||||
public void enableAutoDisconnect() {
|
||||
if (autoDisconnectEnabled) {
|
||||
return; // Уже включено
|
||||
}
|
||||
|
||||
autoDisconnectEnabled = true;
|
||||
player.addListener(new AudioEventAdapter() {
|
||||
@Override
|
||||
public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {
|
||||
if (endReason.mayStartNext && guild != null) {
|
||||
logger.info("TTS трек завершён, отключаемся от голосового канала");
|
||||
guild.getAudioManager().closeAudioConnection();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public AudioPlayer getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public AudioPlayerSendHandler getSendHandler() {
|
||||
return sendHandler;
|
||||
}
|
||||
}
|
||||
61
src/main/java/creator/loki/audio/TtsAudioSource.java
Normal file
61
src/main/java/creator/loki/audio/TtsAudioSource.java
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
package creator.loki.audio;
|
||||
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class TtsAudioSource {
|
||||
private static final Logger logger = LoggerFactory.getLogger(TtsAudioSource.class);
|
||||
private static final String TTS_API = "https://translate.google.com/translate_tts";
|
||||
|
||||
public static void playTts(AudioManager audioManager, GuildMusicManager musicManager,
|
||||
String text, MessageChannel channel) {
|
||||
try {
|
||||
// Генерируем URL для TTS
|
||||
String encodedText = URLEncoder.encode(text, StandardCharsets.UTF_8);
|
||||
String ttsUrl = TTS_API + "?ie=UTF-8&client=tw-ob&tl=ru&q=" + encodedText;
|
||||
|
||||
logger.info("Загрузка TTS: {}", ttsUrl);
|
||||
|
||||
// Загружаем напрямую из URL
|
||||
audioManager.getPlayerManager().loadItem(ttsUrl, new AudioLoadResultHandler() {
|
||||
@Override
|
||||
public void trackLoaded(AudioTrack track) {
|
||||
logger.info("TTS трек загружен успешно");
|
||||
musicManager.getPlayer().playTrack(track);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playlistLoaded(AudioPlaylist playlist) {
|
||||
logger.info("TTS плейлист загружен");
|
||||
if (!playlist.getTracks().isEmpty()) {
|
||||
trackLoaded(playlist.getTracks().get(0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void noMatches() {
|
||||
logger.error("TTS: Не найдено совпадений");
|
||||
channel.sendMessage("❌ Не удалось загрузить TTS").queue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFailed(FriendlyException exception) {
|
||||
logger.error("TTS: Ошибка загрузки", exception);
|
||||
channel.sendMessage("❌ Ошибка TTS: " + exception.getMessage()).queue();
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("TTS: Ошибка генерации", e);
|
||||
channel.sendMessage("❌ Ошибка генерации TTS: " + e.getMessage()).queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
176
src/main/java/creator/loki/audio/TtsQueue.java
Normal file
176
src/main/java/creator/loki/audio/TtsQueue.java
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
package creator.loki.audio;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* Очередь TTS запросов с автоматическим отключением через 30 секунд бездействия
|
||||
*/
|
||||
public class TtsQueue {
|
||||
private static final Logger logger = LoggerFactory.getLogger(TtsQueue.class);
|
||||
private static final int DISCONNECT_DELAY_SECONDS = 30;
|
||||
|
||||
private final BlockingQueue<TtsRequest> queue = new LinkedBlockingQueue<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
private final ExecutorService processor = Executors.newSingleThreadExecutor();
|
||||
|
||||
private ScheduledFuture<?> disconnectTask;
|
||||
private volatile boolean isProcessing = false;
|
||||
private volatile boolean isShutdown = false;
|
||||
|
||||
public TtsQueue() {
|
||||
startProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавить TTS запрос в очередь
|
||||
*/
|
||||
public void enqueue(TtsRequest request) {
|
||||
if (isShutdown) {
|
||||
logger.warn("TtsQueue уже завершена, запрос отклонён");
|
||||
return;
|
||||
}
|
||||
|
||||
queue.offer(request);
|
||||
cancelDisconnectTimer();
|
||||
logger.info("TTS запрос добавлен в очередь. Размер очереди: {}", queue.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Запустить обработчик очереди
|
||||
*/
|
||||
private void startProcessor() {
|
||||
processor.submit(() -> {
|
||||
while (!isShutdown) {
|
||||
try {
|
||||
TtsRequest request = queue.poll(1, TimeUnit.SECONDS);
|
||||
if (request != null) {
|
||||
processRequest(request);
|
||||
} else if (!isProcessing && queue.isEmpty()) {
|
||||
scheduleDisconnect(request != null ? request : getLastRequest());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка обработки TTS запроса", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработать один TTS запрос
|
||||
*/
|
||||
private void processRequest(TtsRequest request) {
|
||||
isProcessing = true;
|
||||
cancelDisconnectTimer();
|
||||
|
||||
try {
|
||||
logger.info("Обработка TTS: {}", request.getText());
|
||||
request.process();
|
||||
|
||||
// Ждём завершения воспроизведения
|
||||
waitForPlaybackEnd(request);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка воспроизведения TTS", e);
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ждать завершения воспроизведения
|
||||
*/
|
||||
private void waitForPlaybackEnd(TtsRequest request) {
|
||||
try {
|
||||
// Ждём максимум 30 секунд
|
||||
for (int i = 0; i < 30; i++) {
|
||||
if (!request.isPlaying()) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Запланировать отключение через 30 секунд
|
||||
*/
|
||||
private void scheduleDisconnect(TtsRequest lastRequest) {
|
||||
if (disconnectTask != null && !disconnectTask.isDone()) {
|
||||
return; // Уже запланировано
|
||||
}
|
||||
|
||||
disconnectTask = scheduler.schedule(() -> {
|
||||
if (queue.isEmpty() && !isProcessing && lastRequest != null) {
|
||||
logger.info("30 секунд бездействия, отключаемся от голосового канала");
|
||||
lastRequest.disconnect();
|
||||
}
|
||||
}, DISCONNECT_DELAY_SECONDS, TimeUnit.SECONDS);
|
||||
|
||||
logger.info("Запланировано отключение через {} секунд", DISCONNECT_DELAY_SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Отменить таймер отключения
|
||||
*/
|
||||
private void cancelDisconnectTimer() {
|
||||
if (disconnectTask != null && !disconnectTask.isDone()) {
|
||||
disconnectTask.cancel(false);
|
||||
logger.debug("Таймер отключения отменён");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить последний запрос (для отключения)
|
||||
*/
|
||||
private TtsRequest getLastRequest() {
|
||||
return queue.peek();
|
||||
}
|
||||
|
||||
/**
|
||||
* Завершить работу очереди
|
||||
*/
|
||||
public void shutdown() {
|
||||
isShutdown = true;
|
||||
cancelDisconnectTimer();
|
||||
|
||||
scheduler.shutdown();
|
||||
processor.shutdown();
|
||||
|
||||
try {
|
||||
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
if (!processor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
processor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
scheduler.shutdownNow();
|
||||
processor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
logger.info("TtsQueue завершена");
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить размер очереди
|
||||
*/
|
||||
public int getQueueSize() {
|
||||
return queue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, обрабатывается ли запрос
|
||||
*/
|
||||
public boolean isProcessing() {
|
||||
return isProcessing;
|
||||
}
|
||||
}
|
||||
158
src/main/java/creator/loki/audio/TtsRequest.java
Normal file
158
src/main/java/creator/loki/audio/TtsRequest.java
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
package creator.loki.audio;
|
||||
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Один TTS запрос
|
||||
*/
|
||||
public class TtsRequest {
|
||||
private static final Logger logger = LoggerFactory.getLogger(TtsRequest.class);
|
||||
private static final String TTS_API = "https://translate.google.com/translate_tts";
|
||||
|
||||
private final AudioManager audioManager;
|
||||
private final GuildMusicManager musicManager;
|
||||
private final Guild guild;
|
||||
private final String text;
|
||||
private final MessageChannel channel;
|
||||
|
||||
private final CountDownLatch loadLatch = new CountDownLatch(1);
|
||||
private volatile boolean isPlaying = false;
|
||||
private volatile AudioTrack currentTrack;
|
||||
|
||||
public TtsRequest(AudioManager audioManager, GuildMusicManager musicManager,
|
||||
Guild guild, String text, MessageChannel channel) {
|
||||
this.audioManager = audioManager;
|
||||
this.musicManager = musicManager;
|
||||
this.guild = guild;
|
||||
this.text = text;
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработать запрос (загрузить и воспроизвести)
|
||||
*/
|
||||
public void process() {
|
||||
try {
|
||||
String encodedText = URLEncoder.encode(text, StandardCharsets.UTF_8);
|
||||
String ttsUrl = TTS_API + "?ie=UTF-8&client=tw-ob&tl=ru&q=" + encodedText;
|
||||
|
||||
logger.info("Загрузка TTS: {}", ttsUrl);
|
||||
|
||||
audioManager.getPlayerManager().loadItem(ttsUrl, new AudioLoadResultHandler() {
|
||||
@Override
|
||||
public void trackLoaded(AudioTrack track) {
|
||||
logger.info("TTS трек загружен: {}", text);
|
||||
currentTrack = track;
|
||||
isPlaying = true;
|
||||
|
||||
AudioPlayer player = musicManager.getPlayer();
|
||||
player.playTrack(track);
|
||||
|
||||
loadLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playlistLoaded(AudioPlaylist playlist) {
|
||||
if (!playlist.getTracks().isEmpty()) {
|
||||
trackLoaded(playlist.getTracks().get(0));
|
||||
} else {
|
||||
loadFailed(new FriendlyException("Плейлист пуст",
|
||||
FriendlyException.Severity.COMMON, null));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void noMatches() {
|
||||
logger.error("TTS: Не найдено совпадений для '{}'", text);
|
||||
channel.sendMessage("❌ Не удалось загрузить TTS").queue();
|
||||
loadLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFailed(FriendlyException exception) {
|
||||
logger.error("TTS: Ошибка загрузки '{}'", text, exception);
|
||||
channel.sendMessage("❌ Ошибка TTS: " + exception.getMessage()).queue();
|
||||
loadLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
// Ждём загрузки (максимум 10 секунд)
|
||||
if (!loadLatch.await(10, TimeUnit.SECONDS)) {
|
||||
logger.warn("TTS: Таймаут загрузки для '{}'", text);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ждём завершения воспроизведения
|
||||
waitForPlaybackEnd();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("TTS: Ошибка обработки '{}'", text, e);
|
||||
channel.sendMessage("❌ Ошибка TTS: " + e.getMessage()).queue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ждать завершения воспроизведения
|
||||
*/
|
||||
private void waitForPlaybackEnd() {
|
||||
AudioPlayer player = musicManager.getPlayer();
|
||||
|
||||
// Ждём максимум 30 секунд
|
||||
for (int i = 0; i < 30; i++) {
|
||||
AudioTrack playingTrack = player.getPlayingTrack();
|
||||
|
||||
if (playingTrack == null || playingTrack != currentTrack) {
|
||||
isPlaying = false;
|
||||
logger.info("TTS воспроизведение завершено: {}", text);
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
isPlaying = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отключиться от голосового канала
|
||||
*/
|
||||
public void disconnect() {
|
||||
if (guild != null) {
|
||||
guild.getAudioManager().closeAudioConnection();
|
||||
logger.info("Отключились от голосового канала в гильдии: {}", guild.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, воспроизводится ли трек
|
||||
*/
|
||||
public boolean isPlaying() {
|
||||
return isPlaying;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public Guild getGuild() {
|
||||
return guild;
|
||||
}
|
||||
}
|
||||
16
src/main/java/creator/loki/command/Command.java
Normal file
16
src/main/java/creator/loki/command/Command.java
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package creator.loki.command;
|
||||
|
||||
public interface Command {
|
||||
String getName();
|
||||
String getDescription();
|
||||
|
||||
default String getUsage() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
default String getCategory() {
|
||||
return "Общие";
|
||||
}
|
||||
|
||||
void execute(CommandContext context);
|
||||
}
|
||||
44
src/main/java/creator/loki/command/CommandContext.java
Normal file
44
src/main/java/creator/loki/command/CommandContext.java
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package creator.loki.command;
|
||||
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CommandContext {
|
||||
private final Message message;
|
||||
private final List<String> args;
|
||||
private final MessageChannel channel;
|
||||
private final Member author;
|
||||
private final Guild guild;
|
||||
|
||||
public CommandContext(Message message, List<String> args, MessageChannel channel, Member author, Guild guild) {
|
||||
this.message = message;
|
||||
this.args = args;
|
||||
this.channel = channel;
|
||||
this.author = author;
|
||||
this.guild = guild;
|
||||
}
|
||||
|
||||
public Message getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public List<String> getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public MessageChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public Member getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public Guild getGuild() {
|
||||
return guild;
|
||||
}
|
||||
}
|
||||
29
src/main/java/creator/loki/command/SlashCommand.java
Normal file
29
src/main/java/creator/loki/command/SlashCommand.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package creator.loki.command;
|
||||
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
/**
|
||||
* Интерфейс для slash команд
|
||||
*/
|
||||
public interface SlashCommand {
|
||||
/**
|
||||
* Выполняет slash команду
|
||||
*/
|
||||
void execute(SlashCommandInteractionEvent event);
|
||||
|
||||
/**
|
||||
* Возвращает данные команды для регистрации в Discord
|
||||
*/
|
||||
CommandData getCommandData();
|
||||
|
||||
/**
|
||||
* Название команды
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Описание команды
|
||||
*/
|
||||
String getDescription();
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
package creator.loki.command.impl.entertainment.games;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Boykisser метр с детерминированным результатом
|
||||
*/
|
||||
public class BoykisserCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public BoykisserCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "boykisser";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "На сколько ты boykisser (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "boykisser [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "boykisser")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "boykisser");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
if (target.getUser().isBot()) {
|
||||
context.getChannel().sendMessage("❌ Боты не целуют мальчиков!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "boykisser", 5);
|
||||
|
||||
// Детерминированный процент
|
||||
int boykisserLevel = getDeterministicValue(target.getId(), 777, 0, 100);
|
||||
|
||||
// Прогресс-бар с поцелуями
|
||||
StringBuilder bar = new StringBuilder();
|
||||
int filled = boykisserLevel / 10;
|
||||
for (int i = 0; i < filled; i++) {
|
||||
bar.append("💋");
|
||||
}
|
||||
for (int i = filled; i < 10; i++) {
|
||||
bar.append("⬜");
|
||||
}
|
||||
|
||||
// Комментарий
|
||||
String comment;
|
||||
Color color;
|
||||
if (boykisserLevel >= 90) {
|
||||
comment = "😳 ЛЕГЕНДАРНЫЙ BOYKISSER!";
|
||||
color = new Color(255, 105, 180);
|
||||
} else if (boykisserLevel >= 70) {
|
||||
comment = "💕 Настоящий boykisser";
|
||||
color = new Color(255, 182, 193);
|
||||
} else if (boykisserLevel >= 50) {
|
||||
comment = "😊 Любит целовать мальчиков";
|
||||
color = new Color(255, 192, 203);
|
||||
} else if (boykisserLevel >= 30) {
|
||||
comment = "🙂 Иногда целует";
|
||||
color = new Color(176, 224, 230);
|
||||
} else if (boykisserLevel >= 10) {
|
||||
comment = "😐 Редко целует";
|
||||
color = new Color(135, 206, 235);
|
||||
} else {
|
||||
comment = "😶 Не boykisser";
|
||||
color = new Color(128, 128, 128);
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("💋 Boykisser Метр")
|
||||
.setDescription(target.getAsMention())
|
||||
.addField("Уровень", bar.toString() + "\n**" + boykisserLevel + "%** - " + comment, false)
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.setColor(color)
|
||||
.setFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package creator.loki.command.impl.entertainment.games;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Random;
|
||||
|
||||
public class CoinCommand implements Command {
|
||||
private final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "coin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Подбросить монетку";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "coin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
boolean isHeads = random.nextBoolean();
|
||||
String result = isHeads ? "Орёл" : "Решка";
|
||||
String emoji = isHeads ? "🦅" : "🪙";
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle(emoji + " Подбрасываю монетку...")
|
||||
.setDescription("**Результат:** " + result)
|
||||
.setColor(isHeads ? new Color(255, 215, 0) : new Color(192, 192, 192))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package creator.loki.command.impl.entertainment.games;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Random;
|
||||
|
||||
public class DiceCommand implements Command {
|
||||
private final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "dice";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Бросить кубик";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "dice [N]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
int maxValue = 6;
|
||||
|
||||
if (!context.getArgs().isEmpty()) {
|
||||
try {
|
||||
maxValue = Integer.parseInt(context.getArgs().get(0));
|
||||
if (maxValue < 2 || maxValue > 100) {
|
||||
context.getChannel().sendMessage("Число должно быть от 2 до 100").queue();
|
||||
return;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
context.getChannel().sendMessage("Укажи корректное число").queue();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int result = random.nextInt(maxValue) + 1;
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🎲 Бросаю кубик...")
|
||||
.setDescription("**Результат:** " + result + " (из " + maxValue + ")")
|
||||
.setColor(new Color(46, 204, 113))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package creator.loki.command.impl.entertainment.games;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Random;
|
||||
|
||||
public class EightBallCommand implements Command {
|
||||
private final Random random = new Random();
|
||||
|
||||
private static final String[] ANSWERS = {
|
||||
"Да, определённо",
|
||||
"Бесспорно",
|
||||
"Без сомнений",
|
||||
"Можешь быть уверен",
|
||||
"Мне кажется — да",
|
||||
"Вероятнее всего",
|
||||
"Хорошие перспективы",
|
||||
"Знаки говорят — да",
|
||||
"Да",
|
||||
"Пока не ясно, попробуй снова",
|
||||
"Спроси позже",
|
||||
"Лучше не рассказывать",
|
||||
"Сейчас нельзя предсказать",
|
||||
"Сконцентрируйся и спроси опять",
|
||||
"Даже не думай",
|
||||
"Мой ответ — нет",
|
||||
"По моим данным — нет",
|
||||
"Перспективы не очень хорошие",
|
||||
"Весьма сомнительно"
|
||||
};
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "8ball";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Магический шар предсказаний";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "8ball <вопрос>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (context.getArgs().isEmpty()) {
|
||||
context.getChannel().sendMessage("Задай вопрос!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String question = String.join(" ", context.getArgs());
|
||||
String answer = ANSWERS[random.nextInt(ANSWERS.length)];
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🎱 Магический шар")
|
||||
.addField("Вопрос:", question, false)
|
||||
.addField("Ответ:", answer, false)
|
||||
.setColor(new Color(52, 73, 94))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package creator.loki.command.impl.entertainment.games;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Fat метр - проверка на жирность
|
||||
*/
|
||||
public class FatCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public FatCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "fat";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Проверка на жирность (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "fat [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "fat")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "fat");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "fat", 5);
|
||||
|
||||
// Детерминированный процент от 0 до 100
|
||||
int fatLevel = getDeterministicValue(target.getId(), 999, 0, 100);
|
||||
|
||||
// Прогресс-бар
|
||||
StringBuilder bar = new StringBuilder();
|
||||
int filled = fatLevel / 10;
|
||||
for (int i = 0; i < filled; i++) {
|
||||
bar.append("🟩");
|
||||
}
|
||||
for (int i = filled; i < 10; i++) {
|
||||
bar.append("⬜");
|
||||
}
|
||||
|
||||
// Комментарий
|
||||
String comment;
|
||||
Color color;
|
||||
if (fatLevel >= 90) {
|
||||
comment = "🐷 СВИНОТА!";
|
||||
color = new Color(255, 0, 0);
|
||||
} else if (fatLevel >= 70) {
|
||||
comment = "🍔 Очень жирный";
|
||||
color = new Color(255, 100, 0);
|
||||
} else if (fatLevel >= 50) {
|
||||
comment = "🍕 Жирноват";
|
||||
color = new Color(255, 165, 0);
|
||||
} else if (fatLevel >= 30) {
|
||||
comment = "👍 Нормальный вес";
|
||||
color = new Color(0, 200, 0);
|
||||
} else if (fatLevel >= 10) {
|
||||
comment = "🥗 Худой";
|
||||
color = new Color(100, 200, 255);
|
||||
} else {
|
||||
comment = "💀 Очень худой";
|
||||
color = new Color(150, 150, 150);
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🐷 Fat Метр")
|
||||
.setDescription(target.getAsMention())
|
||||
.addField("Жирность", bar.toString() + "\n**" + fatLevel + "%** - " + comment, false)
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.setColor(color)
|
||||
.setFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
package creator.loki.command.impl.entertainment.games;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Gay-метр с детерминированным результатом
|
||||
*/
|
||||
public class GayCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public GayCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "gay";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Гей-метр (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "gay [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "gay")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "gay");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
if (target.getUser().isBot()) {
|
||||
context.getChannel().sendMessage("❌ Боты не имеют ориентации!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "gay", 5);
|
||||
|
||||
// Детерминированный процент
|
||||
int gayLevel = getDeterministicValue(target.getId(), 456, 0, 100);
|
||||
|
||||
// Прогресс-бар с радугой
|
||||
String[] rainbow = {"🟥", "🟧", "🟨", "🟩", "🟦", "🟪"};
|
||||
StringBuilder bar = new StringBuilder();
|
||||
int filled = Math.min(gayLevel / 17 + 1, 6);
|
||||
for (int i = 0; i < filled; i++) {
|
||||
bar.append(rainbow[i]);
|
||||
}
|
||||
for (int i = filled; i < 6; i++) {
|
||||
bar.append("⬜");
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🏳️🌈 Гей-метр")
|
||||
.setDescription(target.getAsMention())
|
||||
.addField("Уровень", bar.toString() + "\n**" + gayLevel + "%**", false)
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.setColor(new Color(255, 0, 255))
|
||||
.setFooter("💡 Это просто мем, не воспринимайте серьезно")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package creator.loki.command.impl.entertainment.games;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GlazbobaCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "glazboba";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Расширенная информация о пользователе";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "glazboba <@user>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
context.getChannel().sendMessage("Укажи пользователя через @").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
|
||||
// Анимация загрузки
|
||||
context.getChannel().sendMessage("🔍 Сканирую данные...").queue(loadingMsg -> {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
loadingMsg.editMessage("📊 Анализирую профиль...").queue();
|
||||
Thread.sleep(1000);
|
||||
loadingMsg.editMessage("🔐 Взламываю Discord...").queue();
|
||||
Thread.sleep(1000);
|
||||
|
||||
// Собираем информацию
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
|
||||
String createdAt = target.getUser().getTimeCreated().format(formatter);
|
||||
String joinedAt = target.getTimeJoined().format(formatter);
|
||||
String status = target.getOnlineStatus().name();
|
||||
String nickname = target.getNickname() != null ? target.getNickname() : "Нет";
|
||||
String roles = target.getRoles().stream()
|
||||
.map(role -> role.getName())
|
||||
.limit(10)
|
||||
.collect(Collectors.joining(", "));
|
||||
if (roles.isEmpty()) roles = "Нет ролей";
|
||||
|
||||
String activities = target.getActivities().isEmpty()
|
||||
? "Нет активности"
|
||||
: target.getActivities().get(0).getName();
|
||||
|
||||
boolean isBot = target.getUser().isBot();
|
||||
boolean isBoosting = target.isBoosting();
|
||||
|
||||
loadingMsg.editMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("👤 Информация о " + target.getEffectiveName())
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.addField("🆔 ID", target.getId(), true)
|
||||
.addField("📛 Никнейм", nickname, true)
|
||||
.addField("🤖 Бот", isBot ? "Да" : "Нет", true)
|
||||
.addField("📅 Аккаунт создан", createdAt, false)
|
||||
.addField("📥 Присоединился", joinedAt, false)
|
||||
.addField("🟢 Статус", status, true)
|
||||
.addField("🎮 Активность", activities, true)
|
||||
.addField("💎 Бустер", isBoosting ? "Да" : "Нет", true)
|
||||
.addField("🎭 Роли", roles, false)
|
||||
.setColor(target.getColor() != null ? target.getColor() : Color.BLUE)
|
||||
.setFooter("Запросил: " + context.getAuthor().getUser().getName())
|
||||
.build()
|
||||
).queue();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
loadingMsg.editMessage("❌ Ошибка загрузки").queue();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package creator.loki.command.impl.entertainment.games;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* IQ тест с детерминированным результатом
|
||||
*/
|
||||
public class IqCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public IqCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "iq";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Тест IQ (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "iq [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "iq")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "iq");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "iq", 5);
|
||||
|
||||
int iq;
|
||||
if (target.getUser().isBot()) {
|
||||
iq = 9000; // Боты умные!
|
||||
} else if (target.getId().equals("1414975197269987471")) {
|
||||
iq = 1000; // Специальное значение
|
||||
} else {
|
||||
// Детерминированный IQ от 0 до 140
|
||||
iq = getDeterministicValue(target.getId(), 123, 0, 140);
|
||||
}
|
||||
|
||||
String level;
|
||||
Color color;
|
||||
if (iq >= 140) {
|
||||
level = "🧠 Гений!";
|
||||
color = new Color(255, 215, 0);
|
||||
} else if (iq >= 120) {
|
||||
level = "🎓 Очень умный";
|
||||
color = new Color(100, 255, 150);
|
||||
} else if (iq >= 100) {
|
||||
level = "📚 Средний";
|
||||
color = new Color(52, 152, 219);
|
||||
} else if (iq >= 80) {
|
||||
level = "📖 Ниже среднего";
|
||||
color = new Color(255, 165, 0);
|
||||
} else {
|
||||
level = "🤔 Низкий";
|
||||
color = new Color(231, 76, 60);
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🧠 Тест IQ")
|
||||
.setDescription("Результат для " + target.getAsMention())
|
||||
.addField("IQ Уровень", "**" + iq + "** - " + level, false)
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.setColor(color)
|
||||
.setFooter("💡 Научно доказано вашим ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package creator.loki.command.impl.entertainment.games;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
public class MemeCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "meme";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Случайный мем";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "meme";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
context.getChannel().sendMessage("🔍 Ищу мем...").queue(loadingMsg -> {
|
||||
try {
|
||||
URL url = new URL("https://meme-api.com/gimme");
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("User-Agent", "Mozilla/5.0");
|
||||
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
in.close();
|
||||
|
||||
JsonObject json = JsonParser.parseString(response.toString()).getAsJsonObject();
|
||||
String memeUrl = json.get("url").getAsString();
|
||||
String title = json.get("title").getAsString();
|
||||
String subreddit = json.get("subreddit").getAsString();
|
||||
|
||||
loadingMsg.editMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle(title)
|
||||
.setImage(memeUrl)
|
||||
.setColor(new Color(255, 69, 0))
|
||||
.setFooter("r/" + subreddit)
|
||||
.build()
|
||||
).queue();
|
||||
|
||||
} catch (Exception e) {
|
||||
loadingMsg.editMessage("❌ Не удалось загрузить мем: " + e.getMessage()).queue();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
package creator.loki.command.impl.entertainment.games;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Mustache метр - проверка на усатость
|
||||
*/
|
||||
public class MustacheCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public MustacheCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "mustache";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Проверка на усатость (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "mustache [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "mustache")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "mustache");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "mustache", 5);
|
||||
|
||||
int mustacheLevel;
|
||||
|
||||
// Специальные значения
|
||||
if (target.getUser().isBot() || target.getId().equals("1414975197269987471")) {
|
||||
mustacheLevel = 0; // Боты и специальный ID = 0%
|
||||
} else if (target.getId().equals("678283217094180915")) {
|
||||
mustacheLevel = 1000; // Специальный ID = 1000%
|
||||
} else {
|
||||
// Детерминированный процент от 0 до 100
|
||||
mustacheLevel = getDeterministicValue(target.getId(), 888, 0, 100);
|
||||
}
|
||||
|
||||
// Прогресс-бар с усами
|
||||
StringBuilder bar = new StringBuilder();
|
||||
int filled = Math.min(mustacheLevel / 10, 10);
|
||||
for (int i = 0; i < filled; i++) {
|
||||
bar.append("🧔");
|
||||
}
|
||||
for (int i = filled; i < 10; i++) {
|
||||
bar.append("⬜");
|
||||
}
|
||||
|
||||
// Комментарий
|
||||
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);
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🧔 Mustache Метр")
|
||||
.setDescription(target.getAsMention())
|
||||
.addField("Усатость", bar.toString() + "\n**" + mustacheLevel + "%** - " + comment, false)
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.setColor(color)
|
||||
.setFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
package creator.loki.command.impl.entertainment.games;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* PP размер с детерминированным результатом
|
||||
*/
|
||||
public class PpCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public PpCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "pp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Измеритель pp (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "pp [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "pp")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "pp");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
if (target.getUser().isBot()) {
|
||||
context.getChannel().sendMessage("❌ У ботов нет pp!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "pp", 5);
|
||||
|
||||
// Специальное значение для конкретного пользователя
|
||||
int size;
|
||||
if (target.getId().equals("1414975197269987471")) {
|
||||
size = 100;
|
||||
} else {
|
||||
// Детерминированный размер от 0 до 30 см
|
||||
size = getDeterministicValue(target.getId(), 6969, 0, 30);
|
||||
}
|
||||
|
||||
// Визуализация
|
||||
StringBuilder ppVisual = new StringBuilder("8");
|
||||
for (int i = 0; i < size; i++) {
|
||||
ppVisual.append("=");
|
||||
}
|
||||
ppVisual.append("D");
|
||||
|
||||
// Определяем уровень
|
||||
String comment;
|
||||
Color color;
|
||||
if (size >= 25) {
|
||||
comment = "😳 НЕВЕРОЯТНО!";
|
||||
color = new Color(255, 215, 0);
|
||||
} else if (size >= 20) {
|
||||
comment = "😮 Впечатляет";
|
||||
color = new Color(100, 255, 150);
|
||||
} else if (size >= 15) {
|
||||
comment = "👍 Выше среднего";
|
||||
color = new Color(52, 152, 219);
|
||||
} else if (size >= 10) {
|
||||
comment = "🙂 Средний";
|
||||
color = new Color(128, 128, 128);
|
||||
} else if (size >= 5) {
|
||||
comment = "😐 Ниже среднего";
|
||||
color = new Color(255, 165, 0);
|
||||
} else {
|
||||
comment = "💀 Микро";
|
||||
color = new Color(231, 76, 60);
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🍆 PP Размер")
|
||||
.setDescription(target.getAsMention())
|
||||
.addField("Размер", "```" + ppVisual.toString() + "```\n**" + size + " см** - " + comment, false)
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.setColor(color)
|
||||
.setFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package creator.loki.command.impl.entertainment.games;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Оценка пользователя из 10 с детерминированным результатом
|
||||
*/
|
||||
public class RateCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public RateCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "rate";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Оценить пользователя из 10 (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "rate [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "rate")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "rate");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "rate", 5);
|
||||
|
||||
// Детерминированная оценка от 1 до 10
|
||||
int rating = getDeterministicValue(target.getId(), 420, 1, 10);
|
||||
|
||||
// Визуализация звездами
|
||||
StringBuilder stars = new StringBuilder();
|
||||
for (int i = 0; i < rating; i++) {
|
||||
stars.append("⭐");
|
||||
}
|
||||
for (int i = rating; i < 10; i++) {
|
||||
stars.append("☆");
|
||||
}
|
||||
|
||||
// Комментарий
|
||||
Map<Integer, String> comments = new HashMap<>();
|
||||
comments.put(10, "💯 ИДЕАЛЬНО!");
|
||||
comments.put(9, "🔥 Почти идеально!");
|
||||
comments.put(8, "😍 Отлично!");
|
||||
comments.put(7, "👍 Хорошо!");
|
||||
comments.put(6, "🙂 Неплохо");
|
||||
comments.put(5, "😐 Средне");
|
||||
comments.put(4, "😕 Ниже среднего");
|
||||
comments.put(3, "😬 Так себе");
|
||||
comments.put(2, "💀 Плохо");
|
||||
comments.put(1, "🗿 Ужасно");
|
||||
|
||||
String comment = comments.getOrDefault(rating, "🤔");
|
||||
Color color = rating >= 8 ? new Color(255, 215, 0) : new Color(52, 152, 219);
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("⭐ Оценка")
|
||||
.setDescription(target.getAsMention())
|
||||
.addField("Рейтинг", stars.toString() + "\n**" + rating + "/10** - " + comment, false)
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.setColor(color)
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package creator.loki.command.impl.entertainment.interaction;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class ComplimentCommand implements Command {
|
||||
private final Random random = new Random();
|
||||
|
||||
private static final String[] COMPLIMENTS = {
|
||||
"Ты как идеальный код — красивый и без багов",
|
||||
"Твоя улыбка ярче, чем RGB подсветка",
|
||||
"Ты как хороший коммит — делаешь всё лучше",
|
||||
"Твой вайб как у Python — простой и приятный",
|
||||
"Ты как успешный деплой — всё работает отлично",
|
||||
"Твоя энергия как у SSD — быстрая и эффективная",
|
||||
"Ты как open source проект — полезный для всех",
|
||||
"Твоя харизма как у TypeScript — добавляет уверенности",
|
||||
"Ты как хороший API — понятный и надёжный",
|
||||
"Твоё присутствие как кофе для программиста — необходимо"
|
||||
};
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "compliment";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Сделать комплимент пользователю";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "compliment <@user>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
context.getChannel().sendMessage("Укажи пользователя через @").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
String compliment = COMPLIMENTS[random.nextInt(COMPLIMENTS.length)];
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("💖 Комплимент")
|
||||
.setDescription(target.getAsMention() + ", " + compliment)
|
||||
.setColor(new Color(255, 182, 193))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package creator.loki.command.impl.entertainment.interaction;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import creator.loki.util.NekosLifeApi;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Обниматься с пользователем с GIF
|
||||
*/
|
||||
public class CuddleCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public CuddleCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "cuddle";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Обниматься с пользователем";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "cuddle <@user>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "cuddle")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "cuddle");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
context.getChannel().sendMessage("❌ Укажи пользователя через @").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "cuddle", 5);
|
||||
|
||||
String gifUrl = NekosLifeApi.fetchImage("cuddle");
|
||||
|
||||
if (gifUrl == null) {
|
||||
context.getChannel().sendMessage("❌ Не удалось получить изображение!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setDescription("🥰 " + context.getAuthor().getAsMention() + " обнимается с " + target.getAsMention() + "! 💕")
|
||||
.setImage(gifUrl)
|
||||
.setColor(new Color(255, 0, 255))
|
||||
.setFooter("Powered by nekos.life")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package creator.loki.command.impl.entertainment.interaction;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import creator.loki.util.NekosLifeApi;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Покормить пользователя с GIF
|
||||
*/
|
||||
public class FeedCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public FeedCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "feed";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Покормить пользователя";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "feed <@user>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "feed")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "feed");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
context.getChannel().sendMessage("❌ Укажи пользователя через @").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "feed", 5);
|
||||
|
||||
String gifUrl = NekosLifeApi.fetchImage("feed");
|
||||
|
||||
if (gifUrl == null) {
|
||||
context.getChannel().sendMessage("❌ Не удалось получить изображение!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setDescription("🍽️ " + context.getAuthor().getAsMention() + " кормит " + target.getAsMention() + "! 😋")
|
||||
.setImage(gifUrl)
|
||||
.setColor(new Color(255, 200, 100))
|
||||
.setFooter("Powered by nekos.life")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package creator.loki.command.impl.entertainment.interaction;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.NekosLifeApi;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
|
||||
public class HugCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "hug";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Обнять пользователя";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "hug <@user>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
context.getChannel().sendMessage("❌ Укажи пользователя через @").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
|
||||
// Получаем GIF из nekos.life API
|
||||
String gifUrl = NekosLifeApi.fetchImage("hug");
|
||||
|
||||
if (gifUrl == null) {
|
||||
context.getChannel().sendMessage("❌ Не удалось получить изображение!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setDescription("🤗 " + context.getAuthor().getAsMention() + " обнял " + target.getAsMention() + " 💕")
|
||||
.setImage(gifUrl)
|
||||
.setColor(new Color(255, 182, 193))
|
||||
.setFooter("Powered by nekos.life")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package creator.loki.command.impl.entertainment.interaction;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import creator.loki.util.NekosLifeApi;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Поцелуй пользователя с GIF
|
||||
*/
|
||||
public class KissCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public KissCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "kiss";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Поцеловать пользователя";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "kiss <@user>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "kiss")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "kiss");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
|
||||
if (mentioned.isEmpty()) {
|
||||
context.getChannel().sendMessage("❌ Укажи кого поцеловать! Использование: `!kiss @user`").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
|
||||
if (target.getId().equals(context.getAuthor().getId())) {
|
||||
context.getChannel().sendMessage("❌ Нельзя целовать самого себя!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.getUser().isBot()) {
|
||||
context.getChannel().sendMessage("❌ Боты не умеют целоваться!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "kiss", 5);
|
||||
|
||||
// Получаем GIF из nekos.life API
|
||||
String gifUrl = NekosLifeApi.fetchImage("kiss");
|
||||
|
||||
if (gifUrl == null) {
|
||||
context.getChannel().sendMessage("❌ Не удалось получить изображение!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setDescription("💋 " + context.getAuthor().getAsMention() + " целует " + target.getAsMention() + " ❤️")
|
||||
.setImage(gifUrl)
|
||||
.setColor(new Color(255, 105, 180))
|
||||
.setFooter("Powered by nekos.life")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package creator.loki.command.impl.entertainment.interaction;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PatCommand implements Command {
|
||||
private static final int COOLDOWN_SECONDS = 5;
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public PatCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "pat";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Погладить пользователя";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "pat <@user> <количество 1-7>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
String userId = context.getAuthor().getId();
|
||||
|
||||
// Проверка cooldown
|
||||
if (cooldownManager.isOnCooldown(userId, "pat")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(userId, "pat");
|
||||
context.getChannel().sendMessage(
|
||||
"⏰ Подожди " + remaining + " сек."
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка аргументов
|
||||
if (context.getArgs().size() < 2) {
|
||||
context.getChannel().sendMessage(
|
||||
"❌ Используй: `!pat <@user> <количество 1-7>`"
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Получение упомянутого пользователя
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
context.getChannel().sendMessage("❌ Укажи пользователя через @").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
|
||||
// Парсинг количества
|
||||
int count;
|
||||
try {
|
||||
count = Integer.parseInt(context.getArgs().get(1));
|
||||
} catch (NumberFormatException e) {
|
||||
context.getChannel().sendMessage("❌ Укажи число от 1 до 7").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Валидация количества
|
||||
if (count < 1 || count > 7) {
|
||||
context.getChannel().sendMessage("❌ Количество должно быть от 1 до 7").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(userId, "pat", COOLDOWN_SECONDS);
|
||||
|
||||
// Отправляем каждое "pat-pat" отдельным сообщением
|
||||
for (int i = 0; i < count; i++) {
|
||||
context.getChannel().sendMessage("pat-pat " + target.getAsMention()).queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package creator.loki.command.impl.entertainment.interaction;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import creator.loki.util.NekosLifeApi;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Тыкнуть пользователя с GIF
|
||||
*/
|
||||
public class PokeCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public PokeCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "poke";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Тыкнуть пользователя";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "poke <@user>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "poke")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "poke");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
context.getChannel().sendMessage("❌ Укажи пользователя через @").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "poke", 5);
|
||||
|
||||
String gifUrl = NekosLifeApi.fetchImage("poke");
|
||||
|
||||
if (gifUrl == null) {
|
||||
context.getChannel().sendMessage("❌ Не удалось получить изображение!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setDescription("👉 " + context.getAuthor().getAsMention() + " тыкает в " + target.getAsMention() + "! 😏")
|
||||
.setImage(gifUrl)
|
||||
.setColor(new Color(0, 128, 128))
|
||||
.setFooter("Powered by nekos.life")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package creator.loki.command.impl.entertainment.interaction;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class RoastCommand implements Command {
|
||||
private final Random random = new Random();
|
||||
|
||||
private static final String[] ROASTS = {
|
||||
"Ты как Internet Explorer — медленный и никому не нужный",
|
||||
"Твой код работает? Это баг, исправь его",
|
||||
"Ты пишешь код как будто тебе за каждую строку платят",
|
||||
"Твоя логика как у JavaScript: непредсказуемая",
|
||||
"Ты как HTML без CSS — работаешь, но выглядишь ужасно",
|
||||
"Твой IQ как температура в Антарктиде — ниже нуля",
|
||||
"Ты как баг в продакшене — неожиданный и нежеланный",
|
||||
"Твои шутки как deprecated код — устарели",
|
||||
"Ты как NULL pointer — бесполезный и вызываешь ошибки",
|
||||
"Твой уровень как индекс массива в Python — может быть отрицательным"
|
||||
};
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "roast";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Дружеский рофл над пользователем";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "roast <@user>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
context.getChannel().sendMessage("Укажи пользователя через @").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
String roast = ROASTS[random.nextInt(ROASTS.length)];
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🔥 Roast")
|
||||
.setDescription(target.getAsMention() + ", " + roast)
|
||||
.setColor(new Color(231, 76, 60))
|
||||
.setFooter("Это шутка, не обижайся!")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
package creator.loki.command.impl.entertainment.interaction;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ShipCommand implements Command {
|
||||
private final JsonDataStore dataStore;
|
||||
|
||||
public ShipCommand(JsonDataStore dataStore) {
|
||||
this.dataStore = dataStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ship";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Процент совместимости двух пользователей";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "ship <@user1> <@user2>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.size() < 2) {
|
||||
context.getChannel().sendMessage("Укажи двух пользователей через @").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member user1 = mentioned.get(0);
|
||||
Member user2 = mentioned.get(1);
|
||||
|
||||
// Создаём уникальный ключ для пары (сортируем ID чтобы порядок не имел значения)
|
||||
String pairKey = createPairKey(user1.getId(), user2.getId());
|
||||
|
||||
// Получаем или создаём процент
|
||||
Type type = new TypeToken<Map<String, Integer>>(){}.getType();
|
||||
Map<String, Integer> shipPairs = dataStore.getData("shipPairs", type);
|
||||
if (shipPairs == null) {
|
||||
shipPairs = new HashMap<>();
|
||||
}
|
||||
|
||||
int percentage;
|
||||
if (shipPairs.containsKey(pairKey)) {
|
||||
percentage = shipPairs.get(pairKey);
|
||||
} else {
|
||||
// Генерируем процент на основе хеша ID (чтобы был стабильный)
|
||||
percentage = Math.abs((user1.getId() + user2.getId()).hashCode()) % 101;
|
||||
shipPairs.put(pairKey, percentage);
|
||||
dataStore.setData("shipPairs", shipPairs);
|
||||
}
|
||||
|
||||
// Генерируем сердечки
|
||||
String hearts = generateHearts(percentage);
|
||||
String message = generateMessage(percentage);
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("💕 Ship-метр")
|
||||
.setDescription(
|
||||
user1.getAsMention() + " 💖 " + user2.getAsMention() + "\n\n" +
|
||||
hearts + " **" + percentage + "%**\n\n" +
|
||||
message
|
||||
)
|
||||
.setColor(getColorByPercentage(percentage))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private String createPairKey(String id1, String id2) {
|
||||
return id1.compareTo(id2) < 0 ? id1 + "-" + id2 : id2 + "-" + id1;
|
||||
}
|
||||
|
||||
private String generateHearts(int percentage) {
|
||||
int fullHearts = percentage / 10;
|
||||
StringBuilder hearts = new StringBuilder();
|
||||
for (int i = 0; i < fullHearts; i++) {
|
||||
hearts.append("❤️");
|
||||
}
|
||||
for (int i = fullHearts; i < 10; i++) {
|
||||
hearts.append("🖤");
|
||||
}
|
||||
return hearts.toString();
|
||||
}
|
||||
|
||||
private String generateMessage(int percentage) {
|
||||
if (percentage >= 90) return "Идеальная пара! 💑";
|
||||
if (percentage >= 70) return "Отличная совместимость! 💕";
|
||||
if (percentage >= 50) return "Неплохие шансы! 💖";
|
||||
if (percentage >= 30) return "Есть потенциал... 💔";
|
||||
return "Лучше остаться друзьями 💀";
|
||||
}
|
||||
|
||||
private Color getColorByPercentage(int percentage) {
|
||||
if (percentage >= 70) return new Color(255, 105, 180); // Розовый
|
||||
if (percentage >= 40) return new Color(255, 182, 193); // Светло-розовый
|
||||
return new Color(128, 128, 128); // Серый
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package creator.loki.command.impl.entertainment.interaction;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import creator.loki.util.NekosLifeApi;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Шлепнуть пользователя с GIF
|
||||
*/
|
||||
public class SlapCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public SlapCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "slap";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Шлепнуть пользователя";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "slap <@user>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "slap")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "slap");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
context.getChannel().sendMessage("❌ Укажи пользователя через @").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "slap", 5);
|
||||
|
||||
String gifUrl = NekosLifeApi.fetchImage("slap");
|
||||
|
||||
if (gifUrl == null) {
|
||||
context.getChannel().sendMessage("❌ Не удалось получить изображение!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setDescription("👋 " + context.getAuthor().getAsMention() + " шлепает " + target.getAsMention() + "! 😠")
|
||||
.setImage(gifUrl)
|
||||
.setColor(new Color(255, 165, 0))
|
||||
.setFooter("Powered by nekos.life")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package creator.loki.command.impl.entertainment.interaction;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import creator.loki.util.NekosLifeApi;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Пощекотать пользователя с GIF
|
||||
*/
|
||||
public class TickleCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public TickleCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "tickle";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Пощекотать пользователя";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "tickle <@user>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "tickle")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "tickle");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
context.getChannel().sendMessage("❌ Укажи пользователя через @").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "tickle", 5);
|
||||
|
||||
String gifUrl = NekosLifeApi.fetchImage("tickle");
|
||||
|
||||
if (gifUrl == null) {
|
||||
context.getChannel().sendMessage("❌ Не удалось получить изображение!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setDescription("🤗 " + context.getAuthor().getAsMention() + " щекочет " + target.getAsMention() + "!")
|
||||
.setImage(gifUrl)
|
||||
.setColor(new Color(0, 255, 0))
|
||||
.setFooter("Powered by nekos.life")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
package creator.loki.command.impl.moderation;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BanCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ban";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Банит пользователя на сервере";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "ban <@user> [причина]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Модерация";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (context.getGuild() == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка прав модератора
|
||||
if (context.getAuthor() == null || !context.getAuthor().hasPermission(Permission.BAN_MEMBERS)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Недостаточно прав", "У вас нет прав на бан пользователей")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка прав бота
|
||||
if (!context.getGuild().getSelfMember().hasPermission(Permission.BAN_MEMBERS)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Недостаточно прав", "У бота нет прав на бан пользователей")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Получение упомянутого пользователя
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Укажите пользователя через @")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
|
||||
// Нельзя забанить самого себя
|
||||
if (target.getId().equals(context.getAuthor().getId())) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Вы не можете забанить самого себя")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Нельзя забанить бота
|
||||
if (target.getUser().isBot() && target.getId().equals(context.getGuild().getSelfMember().getId())) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Я не могу забанить самого себя")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка иерархии ролей
|
||||
if (!context.getAuthor().canInteract(target)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Недостаточно прав", "Вы не можете забанить этого пользователя (роль выше вашей)")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.getGuild().getSelfMember().canInteract(target)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Недостаточно прав", "Я не могу забанить этого пользователя (роль выше моей)")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Причина бана
|
||||
String reason = context.getArgs().size() > 1
|
||||
? String.join(" ", context.getArgs().subList(1, context.getArgs().size()))
|
||||
: "Причина не указана";
|
||||
|
||||
// Бан
|
||||
context.getGuild().ban(target, 0, java.util.concurrent.TimeUnit.DAYS)
|
||||
.reason(reason)
|
||||
.queue(
|
||||
success -> {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.success(
|
||||
"Пользователь забанен",
|
||||
"**Пользователь:** " + target.getUser().getAsTag() + "\n" +
|
||||
"**Причина:** " + reason + "\n" +
|
||||
"**Модератор:** " + context.getAuthor().getAsMention()
|
||||
)
|
||||
).queue();
|
||||
},
|
||||
error -> {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Не удалось забанить: " + error.getMessage())
|
||||
).queue();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package creator.loki.command.impl.moderation;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ClearCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "clear";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Удаляет указанное количество сообщений";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "clear <количество>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка прав
|
||||
if (context.getAuthor() == null || !context.getAuthor().hasPermission(Permission.MESSAGE_MANAGE)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Недостаточно прав",
|
||||
"У вас нет прав на управление сообщениями"
|
||||
)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка аргументов
|
||||
if (context.getArgs().isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Неверное количество",
|
||||
"Укажите число от 1 до 100"
|
||||
)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
int amount;
|
||||
try {
|
||||
amount = Integer.parseInt(context.getArgs().get(0));
|
||||
} catch (NumberFormatException e) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Неверное количество",
|
||||
"Укажите число от 1 до 100"
|
||||
)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (amount < 1 || amount > 100) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Неверное количество",
|
||||
"Укажите число от 1 до 100"
|
||||
)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Удаление сообщений
|
||||
if (!(context.getChannel() instanceof TextChannel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TextChannel channel = (TextChannel) context.getChannel();
|
||||
|
||||
channel.getHistory().retrievePast(amount + 1).queue(messages -> {
|
||||
channel.deleteMessages(messages).queue(
|
||||
success -> {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.success(
|
||||
"Сообщения удалены",
|
||||
"Удалено сообщений: " + messages.size()
|
||||
)
|
||||
).queue(msg -> msg.delete().queueAfter(5, TimeUnit.SECONDS));
|
||||
},
|
||||
error -> {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Ошибка удаления",
|
||||
"Не удалось удалить сообщения: " + error.getMessage()
|
||||
)
|
||||
).queue();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package creator.loki.command.impl.moderation;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class KickCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "kick";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Кикает пользователя с сервера";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "kick <@user> [причина]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Модерация";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (context.getGuild() == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.getAuthor() == null || !context.getAuthor().hasPermission(Permission.KICK_MEMBERS)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Недостаточно прав", "У вас нет прав на кик пользователей")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.getGuild().getSelfMember().hasPermission(Permission.KICK_MEMBERS)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Недостаточно прав", "У бота нет прав на кик пользователей")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Укажите пользователя через @")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
|
||||
if (target.getId().equals(context.getAuthor().getId())) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Вы не можете кикнуть самого себя")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.getUser().isBot() && target.getId().equals(context.getGuild().getSelfMember().getId())) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Я не могу кикнуть самого себя")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.getAuthor().canInteract(target)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Недостаточно прав", "Вы не можете кикнуть этого пользователя")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.getGuild().getSelfMember().canInteract(target)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Недостаточно прав", "Я не могу кикнуть этого пользователя")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String reason = context.getArgs().size() > 1
|
||||
? String.join(" ", context.getArgs().subList(1, context.getArgs().size()))
|
||||
: "Причина не указана";
|
||||
|
||||
context.getGuild().kick(target)
|
||||
.reason(reason)
|
||||
.queue(
|
||||
success -> {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.success(
|
||||
"Пользователь кикнут",
|
||||
"**Пользователь:** " + target.getUser().getAsTag() + "\n" +
|
||||
"**Причина:** " + reason + "\n" +
|
||||
"**Модератор:** " + context.getAuthor().getAsMention()
|
||||
)
|
||||
).queue();
|
||||
},
|
||||
error -> {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Не удалось кикнуть: " + error.getMessage())
|
||||
).queue();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
package creator.loki.command.impl.roles;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.role.CustomRoleManager;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class CreateRoleCommand implements Command {
|
||||
private static final Pattern HEX_PATTERN = Pattern.compile("^#?[0-9A-Fa-f]{6}$");
|
||||
private final CustomRoleManager roleManager;
|
||||
|
||||
public CreateRoleCommand(CustomRoleManager roleManager) {
|
||||
this.roleManager = roleManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "createrole";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Создаёт кастомную роль с указанным цветом";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "createrole <название> <#HEX цвет>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Роли";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (!RoleCommandValidator.validateGuild(context)) return;
|
||||
|
||||
Guild guild = context.getGuild();
|
||||
if (!RoleCommandValidator.validateBotPermissions(guild, context)) return;
|
||||
if (!RoleCommandValidator.validateArgumentCount(context, 2,
|
||||
"Используй: `!createrole <название> <#цвет>`\nПример: `!createrole Cool Role #FF5733`")) return;
|
||||
|
||||
RoleData roleData = parseRoleData(context);
|
||||
if (!validateRoleName(roleData.name, guild, context)) return;
|
||||
|
||||
Color color = parseColor(roleData.colorArg, context);
|
||||
if (color == null) return;
|
||||
|
||||
if (!checkUserLimit(guild, context)) return;
|
||||
|
||||
createAndAssignRole(guild, context, roleData.name, color);
|
||||
}
|
||||
|
||||
private boolean validatePermissions(Guild guild, CommandContext context) {
|
||||
if (!guild.getSelfMember().hasPermission(Permission.MANAGE_ROLES)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Недостаточно прав", "У бота нет прав на управление ролями")
|
||||
).queue();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean validateArguments(CommandContext context) {
|
||||
if (context.getArgs().size() < 2) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Неверное использование",
|
||||
"Используй: `!createrole <название> <#цвет>`\nПример: `!createrole Cool Role #FF5733`")
|
||||
).queue();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private RoleData parseRoleData(CommandContext context) {
|
||||
String colorArg = context.getArgs().get(context.getArgs().size() - 1);
|
||||
String name = String.join(" ", context.getArgs().subList(0, context.getArgs().size() - 1));
|
||||
return new RoleData(name, colorArg);
|
||||
}
|
||||
|
||||
private boolean validateRoleName(String name, Guild guild, CommandContext context) {
|
||||
if (!RoleCommandValidator.validateRoleName(name, context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!guild.getRolesByName(name, true).isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Роль уже существует", "Роль с названием `" + name + "` уже существует на сервере")
|
||||
).queue();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Color parseColor(String colorArg, CommandContext context) {
|
||||
if (!HEX_PATTERN.matcher(colorArg).matches()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Неверный цвет", "Укажите цвет в HEX формате (например: #FF5733)")
|
||||
).queue();
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
String hex = colorArg.startsWith("#") ? colorArg : "#" + colorArg;
|
||||
return Color.decode(hex);
|
||||
} catch (NumberFormatException e) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Неверный цвет", "Не удалось распознать цвет: " + colorArg)
|
||||
).queue();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkUserLimit(Guild guild, CommandContext context) {
|
||||
String userId = context.getAuthor().getId();
|
||||
String guildId = guild.getId();
|
||||
|
||||
if (roleManager.hasReachedLimit(guildId, userId)) {
|
||||
int current = roleManager.getUserRoleCount(guildId, userId);
|
||||
int max = roleManager.getMaxRolesPerUser();
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Лимит достигнут",
|
||||
"Вы достигли максимального количества кастомных ролей (" + current + "/" + max + ")\n" +
|
||||
"Удалите старую роль командой `!deleterole <название>`"
|
||||
)
|
||||
).queue();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void createAndAssignRole(Guild guild, CommandContext context, String name, Color color) {
|
||||
guild.createRole()
|
||||
.setName(name)
|
||||
.setColor(color)
|
||||
.setMentionable(true)
|
||||
.queue(
|
||||
role -> assignRoleToUser(guild, context, role),
|
||||
error -> sendCreationError(context, error)
|
||||
);
|
||||
}
|
||||
|
||||
private void assignRoleToUser(Guild guild, CommandContext context, Role role) {
|
||||
guild.addRoleToMember(context.getAuthor(), role).queue(
|
||||
success -> onRoleAssigned(guild, context, role),
|
||||
error -> sendAssignmentError(context, error)
|
||||
);
|
||||
}
|
||||
|
||||
private void onRoleAssigned(Guild guild, CommandContext context, Role role) {
|
||||
String userId = context.getAuthor().getId();
|
||||
String guildId = guild.getId();
|
||||
|
||||
roleManager.trackRole(guildId, userId, role.getId());
|
||||
|
||||
int current = roleManager.getUserRoleCount(guildId, userId);
|
||||
int max = roleManager.getMaxRolesPerUser();
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.success(
|
||||
"Роль создана",
|
||||
"Роль " + role.getAsMention() + " создана и назначена вам!\n" +
|
||||
"Ваши кастомные роли: " + current + "/" + max
|
||||
)
|
||||
).queue();
|
||||
}
|
||||
|
||||
private void sendAssignmentError(CommandContext context, Throwable error) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Роль создана, но не удалось назначить её вам: " + error.getMessage())
|
||||
).queue();
|
||||
}
|
||||
|
||||
private void sendCreationError(CommandContext context, Throwable error) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка создания", "Не удалось создать роль: " + error.getMessage())
|
||||
).queue();
|
||||
}
|
||||
|
||||
private static class RoleData {
|
||||
final String name;
|
||||
final String colorArg;
|
||||
|
||||
RoleData(String name, String colorArg) {
|
||||
this.name = name;
|
||||
this.colorArg = colorArg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
package creator.loki.command.impl.roles;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.role.CustomRoleManager;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DeleteRoleCommand implements Command {
|
||||
private final CustomRoleManager roleManager;
|
||||
|
||||
public DeleteRoleCommand(CustomRoleManager roleManager) {
|
||||
this.roleManager = roleManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "deleterole";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Удаляет вашу кастомную роль";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "deleterole <название>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Роли";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
Guild guild = context.getGuild();
|
||||
if (guild == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка прав бота
|
||||
if (!guild.getSelfMember().hasPermission(Permission.MANAGE_ROLES)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Недостаточно прав", "У бота нет прав на управление ролями")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка аргументов
|
||||
if (context.getArgs().isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Неверное использование", "Используй: `!deleterole <название>`")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Название роли
|
||||
String roleName = String.join(" ", context.getArgs());
|
||||
|
||||
// Поиск роли
|
||||
List<Role> roles = guild.getRolesByName(roleName, true);
|
||||
if (roles.isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Роль не найдена", "Роль с названием `" + roleName + "` не найдена на сервере")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Role role = roles.get(0);
|
||||
String userId = context.getAuthor().getId();
|
||||
String guildId = guild.getId();
|
||||
|
||||
// Проверка владения
|
||||
if (!roleManager.isOwnedBy(guildId, userId, role.getId())) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Нет прав",
|
||||
"Вы можете удалять только свои кастомные роли\n" +
|
||||
"Используй `!myroles` чтобы посмотреть список своих ролей"
|
||||
)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка позиции (бот может удалить роль)
|
||||
if (!guild.getSelfMember().canInteract(role)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Невозможно удалить",
|
||||
"Роль находится выше роли бота в иерархии. Переместите роль бота выше в настройках сервера."
|
||||
)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Удаление роли
|
||||
String roleId = role.getId();
|
||||
String mention = role.getAsMention();
|
||||
|
||||
role.delete().queue(
|
||||
success -> {
|
||||
// Удаляем из отслеживания
|
||||
roleManager.untrackRole(guildId, userId, roleId);
|
||||
|
||||
int remaining = roleManager.getUserRoleCount(guildId, userId);
|
||||
int max = roleManager.getMaxRolesPerUser();
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.success(
|
||||
"Роль удалена",
|
||||
"Роль " + mention + " успешно удалена\n" +
|
||||
"Ваши кастомные роли: " + remaining + "/" + max
|
||||
)
|
||||
).queue();
|
||||
},
|
||||
error -> {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка удаления", "Не удалось удалить роль: " + error.getMessage())
|
||||
).queue();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
package creator.loki.command.impl.roles;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.role.CustomRoleManager;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class EditRoleCommand implements Command {
|
||||
private static final Pattern HEX_PATTERN = Pattern.compile("^#?[0-9A-Fa-f]{6}$");
|
||||
private final CustomRoleManager roleManager;
|
||||
|
||||
public EditRoleCommand(CustomRoleManager roleManager) {
|
||||
this.roleManager = roleManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "editrole";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Изменяет цвет вашей кастомной роли";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "editrole <название> <новый #цвет>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Роли";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
Guild guild = context.getGuild();
|
||||
if (guild == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!guild.getSelfMember().hasPermission(Permission.MANAGE_ROLES)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Недостаточно прав", "У бота нет прав на управление ролями")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.getArgs().size() < 2) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Неверное использование", "Используй: `!editrole <название> <#цвет>`")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Парсинг: всё кроме последнего = название, последний = цвет
|
||||
String colorArg = context.getArgs().get(context.getArgs().size() - 1);
|
||||
String roleName = String.join(" ", context.getArgs().subList(0, context.getArgs().size() - 1));
|
||||
|
||||
// Валидация HEX цвета
|
||||
if (!HEX_PATTERN.matcher(colorArg).matches()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Неверный цвет", "Укажите цвет в HEX формате (например: #FF5733)")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Color color;
|
||||
try {
|
||||
String hex = colorArg.startsWith("#") ? colorArg : "#" + colorArg;
|
||||
color = Color.decode(hex);
|
||||
} catch (NumberFormatException e) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Неверный цвет", "Не удалось распознать цвет: " + colorArg)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Поиск роли
|
||||
List<Role> roles = guild.getRolesByName(roleName, true);
|
||||
if (roles.isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Роль не найдена", "Роль с названием `" + roleName + "` не найдена")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Role role = roles.get(0);
|
||||
String userId = context.getAuthor().getId();
|
||||
String guildId = guild.getId();
|
||||
|
||||
// Проверка владения
|
||||
if (!roleManager.isOwnedBy(guildId, userId, role.getId())) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Нет прав", "Вы можете изменять только свои кастомные роли")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка позиции
|
||||
if (!guild.getSelfMember().canInteract(role)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Невозможно изменить", "Роль находится выше роли бота в иерархии")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Изменение цвета
|
||||
role.getManager().setColor(color).queue(
|
||||
success -> {
|
||||
String hex = String.format("#%06X", color.getRGB() & 0xFFFFFF);
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.success(
|
||||
"Цвет изменён",
|
||||
"Цвет роли " + role.getAsMention() + " изменён на " + hex
|
||||
)
|
||||
).queue();
|
||||
},
|
||||
error -> {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Не удалось изменить цвет: " + error.getMessage())
|
||||
).queue();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
package creator.loki.command.impl.roles;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.role.CustomRoleManager;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MyRolesCommand implements Command {
|
||||
private final CustomRoleManager roleManager;
|
||||
|
||||
public MyRolesCommand(CustomRoleManager roleManager) {
|
||||
this.roleManager = roleManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "myroles";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показывает ваши кастомные роли";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Роли";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
Guild guild = context.getGuild();
|
||||
if (guild == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String userId = context.getAuthor().getId();
|
||||
String guildId = guild.getId();
|
||||
|
||||
List<String> roleIds = roleManager.getUserRoleIds(guildId, userId);
|
||||
|
||||
if (roleIds.isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.info(
|
||||
"Нет кастомных ролей",
|
||||
"У вас пока нет кастомных ролей\n" +
|
||||
"Создайте роль командой `!createrole <название> <#цвет>`"
|
||||
)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем роли и проверяем их существование
|
||||
List<Role> existingRoles = new ArrayList<>();
|
||||
List<String> toRemove = new ArrayList<>();
|
||||
|
||||
for (String roleId : roleIds) {
|
||||
Role role = guild.getRoleById(roleId);
|
||||
if (role != null) {
|
||||
existingRoles.add(role);
|
||||
} else {
|
||||
// Роль была удалена вручную - убираем из отслеживания
|
||||
toRemove.add(roleId);
|
||||
}
|
||||
}
|
||||
|
||||
// Очистка несуществующих ролей
|
||||
for (String roleId : toRemove) {
|
||||
roleManager.untrackRole(guildId, userId, roleId);
|
||||
}
|
||||
|
||||
if (existingRoles.isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.info(
|
||||
"Нет активных ролей",
|
||||
"Ваши кастомные роли были удалены\n" +
|
||||
"Создайте новую роль командой `!createrole <название> <#цвет>`"
|
||||
)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Формируем список ролей
|
||||
StringBuilder rolesList = new StringBuilder();
|
||||
for (int i = 0; i < existingRoles.size(); i++) {
|
||||
Role role = existingRoles.get(i);
|
||||
rolesList.append((i + 1)).append(". ").append(role.getAsMention());
|
||||
|
||||
// Добавляем цвет роли
|
||||
if (role.getColor() != null) {
|
||||
String hex = String.format("#%06X", role.getColor().getRGB() & 0xFFFFFF);
|
||||
rolesList.append(" (").append(hex).append(")");
|
||||
}
|
||||
|
||||
rolesList.append("\n");
|
||||
}
|
||||
|
||||
int current = existingRoles.size();
|
||||
int max = roleManager.getMaxRolesPerUser();
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🎭 Ваши кастомные роли")
|
||||
.setDescription(rolesList.toString())
|
||||
.setColor(Color.BLUE)
|
||||
.addField("Использовано", current + "/" + max, true)
|
||||
.addField("Доступно", (max - current) + " " + (max - current == 1 ? "роль" : "ролей"), true)
|
||||
.setFooter("Удалить роль: !deleterole <название>")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package creator.loki.command.impl.roles;
|
||||
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
|
||||
/**
|
||||
* Общие валидации для команд работы с ролями (DRY)
|
||||
*/
|
||||
public class RoleCommandValidator {
|
||||
|
||||
public static boolean validateGuild(CommandContext context) {
|
||||
if (context.getGuild() == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")
|
||||
).queue();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean validateBotPermissions(Guild guild, CommandContext context) {
|
||||
if (!guild.getSelfMember().hasPermission(Permission.MANAGE_ROLES)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Недостаточно прав", "У бота нет прав на управление ролями")
|
||||
).queue();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean validateArgumentCount(CommandContext context, int minArgs, String usage) {
|
||||
if (context.getArgs().size() < minArgs) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Неверное использование", usage)
|
||||
).queue();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean validateRoleName(String name, CommandContext context) {
|
||||
if (name.isBlank()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Неверное название", "Название роли не может быть пустым")
|
||||
).queue();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name.length() > 100) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Слишком длинное название", "Максимальная длина названия — 100 символов")
|
||||
).queue();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name.contains("@everyone") || name.contains("@here")) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Запрещённое название", "Название не может содержать @everyone или @here")
|
||||
).queue();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
package creator.loki.command.impl.setup;
|
||||
|
||||
import creator.loki.database.ConfigRepository;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Команда настройки счетчика участников
|
||||
*/
|
||||
public class CounterSetupCommand extends ListenerAdapter {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CounterSetupCommand.class);
|
||||
private final ConfigRepository configRepo;
|
||||
|
||||
public CounterSetupCommand(ConfigRepository configRepo) {
|
||||
this.configRepo = configRepo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
|
||||
if (!"counter-setup".equals(event.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка прав администратора
|
||||
Member member = event.getMember();
|
||||
if (member == null || !member.hasPermission(Permission.ADMINISTRATOR)) {
|
||||
event.reply("❌ У вас нет прав для использования этой команды!").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String subcommand = event.getSubcommandName();
|
||||
if (subcommand == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (subcommand) {
|
||||
case "channel" -> handleChannel(event);
|
||||
case "format" -> handleFormat(event);
|
||||
case "info" -> handleInfo(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить канал счетчика
|
||||
*/
|
||||
private void handleChannel(SlashCommandInteractionEvent event) {
|
||||
VoiceChannel channel = event.getOption("channel").getAsChannel().asVoiceChannel();
|
||||
|
||||
configRepo.set("counter.channel_id", channel.getId());
|
||||
|
||||
// Сразу обновляем название канала
|
||||
int memberCount = event.getGuild().getMemberCount();
|
||||
String format = configRepo.get("counter.format");
|
||||
if (format == null) {
|
||||
format = "【👥】Участники: {count}";
|
||||
configRepo.set("counter.format", format);
|
||||
}
|
||||
|
||||
String newName = format.replace("{count}", String.valueOf(memberCount));
|
||||
channel.getManager().setName(newName).queue();
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("✅ Канал счетчика установлен")
|
||||
.setDescription("Канал " + channel.getAsMention() + " теперь отображает количество участников")
|
||||
.addField("Текущее количество", String.valueOf(memberCount), false)
|
||||
.setColor(Color.GREEN);
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
logger.info("Установлен канал счетчика: {} ({})", channel.getName(), channel.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить формат отображения
|
||||
*/
|
||||
private void handleFormat(SlashCommandInteractionEvent event) {
|
||||
String format = event.getOption("format").getAsString();
|
||||
|
||||
if (!format.contains("{count}")) {
|
||||
event.reply("❌ Формат должен содержать `{count}`!").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
configRepo.set("counter.format", format);
|
||||
|
||||
// Обновляем канал если он установлен
|
||||
String channelId = configRepo.get("counter.channel_id");
|
||||
if (channelId != null) {
|
||||
VoiceChannel channel = event.getGuild().getVoiceChannelById(channelId);
|
||||
if (channel != null) {
|
||||
int memberCount = event.getGuild().getMemberCount();
|
||||
String newName = format.replace("{count}", String.valueOf(memberCount));
|
||||
channel.getManager().setName(newName).queue();
|
||||
}
|
||||
}
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("✅ Формат обновлен")
|
||||
.setDescription("Новый формат: `" + format + "`")
|
||||
.addField("Пример", format.replace("{count}", "123"), false)
|
||||
.setColor(Color.GREEN);
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
logger.info("Установлен формат счетчика: {}", format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Показать текущую конфигурацию
|
||||
*/
|
||||
private void handleInfo(SlashCommandInteractionEvent event) {
|
||||
String channelId = configRepo.get("counter.channel_id");
|
||||
String format = configRepo.get("counter.format");
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("📊 Конфигурация счетчика участников")
|
||||
.setColor(Color.BLUE);
|
||||
|
||||
if (channelId != null) {
|
||||
embed.addField("Канал", "<#" + channelId + ">", false);
|
||||
} else {
|
||||
embed.addField("Канал", "❌ Не установлен", false);
|
||||
}
|
||||
|
||||
if (format != null) {
|
||||
embed.addField("Формат", "`" + format + "`", false);
|
||||
embed.addField("Пример", format.replace("{count}", String.valueOf(event.getGuild().getMemberCount())), false);
|
||||
} else {
|
||||
embed.addField("Формат", "❌ Не установлен", false);
|
||||
}
|
||||
|
||||
event.replyEmbeds(embed.build()).setEphemeral(true).queue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Зарегистрировать команду в Discord
|
||||
*/
|
||||
public static void register(net.dv8tion.jda.api.JDA jda) {
|
||||
jda.updateCommands().addCommands(
|
||||
Commands.slash("counter-setup", "Настройка счетчика участников")
|
||||
.addSubcommands(
|
||||
new SubcommandData("channel", "Установить канал для счетчика")
|
||||
.addOption(OptionType.CHANNEL, "channel", "Голосовой канал", true),
|
||||
new SubcommandData("format", "Установить формат отображения")
|
||||
.addOption(OptionType.STRING, "format", "Формат (используйте {count})", true),
|
||||
new SubcommandData("info", "Показать текущую конфигурацию")
|
||||
)
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
package creator.loki.command.impl.setup;
|
||||
|
||||
import creator.loki.database.ConfigRepository;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Команда настройки логирования
|
||||
*/
|
||||
public class LogSetupCommand extends ListenerAdapter {
|
||||
private static final Logger logger = LoggerFactory.getLogger(LogSetupCommand.class);
|
||||
private final ConfigRepository configRepo;
|
||||
|
||||
public LogSetupCommand(ConfigRepository configRepo) {
|
||||
this.configRepo = configRepo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
|
||||
if (!"log-setup".equals(event.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Member member = event.getMember();
|
||||
if (member == null || !member.hasPermission(Permission.ADMINISTRATOR)) {
|
||||
event.reply("❌ У вас нет прав для использования этой команды!").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String subcommand = event.getSubcommandName();
|
||||
if (subcommand == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (subcommand) {
|
||||
case "moderation" -> handleModeration(event);
|
||||
case "levelup" -> handleLevelUp(event);
|
||||
case "info" -> handleInfo(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleModeration(SlashCommandInteractionEvent event) {
|
||||
TextChannel channel = event.getOption("channel").getAsChannel().asTextChannel();
|
||||
|
||||
configRepo.set("moderation.log_channel_id", channel.getId());
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("✅ Канал логов модерации установлен")
|
||||
.setDescription("Логи модерации будут отправляться в " + channel.getAsMention())
|
||||
.addField("Что логируется",
|
||||
"• Удаление сообщений\n" +
|
||||
"• Баны/разбаны\n" +
|
||||
"• Кики\n" +
|
||||
"• Муты/размуты\n" +
|
||||
"• Отключение микрофона/звука", false)
|
||||
.setColor(Color.GREEN);
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
logger.info("Установлен канал логов модерации: {} ({})", channel.getName(), channel.getId());
|
||||
}
|
||||
|
||||
private void handleLevelUp(SlashCommandInteractionEvent event) {
|
||||
TextChannel channel = event.getOption("channel").getAsChannel().asTextChannel();
|
||||
|
||||
configRepo.set("levelup.notification_channel_id", channel.getId());
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("✅ Канал уведомлений о level up установлен")
|
||||
.setDescription("Уведомления о повышении уровня будут отправляться в " + channel.getAsMention())
|
||||
.setColor(Color.GREEN);
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
logger.info("Установлен канал уведомлений level up: {} ({})", channel.getName(), channel.getId());
|
||||
}
|
||||
|
||||
private void handleInfo(SlashCommandInteractionEvent event) {
|
||||
String moderationChannelId = configRepo.get("moderation.log_channel_id");
|
||||
String levelupChannelId = configRepo.get("levelup.notification_channel_id");
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("📋 Конфигурация логирования")
|
||||
.setColor(Color.BLUE);
|
||||
|
||||
if (moderationChannelId != null) {
|
||||
embed.addField("Логи модерации", "<#" + moderationChannelId + ">", false);
|
||||
} else {
|
||||
embed.addField("Логи модерации", "❌ Не установлен", false);
|
||||
}
|
||||
|
||||
if (levelupChannelId != null) {
|
||||
embed.addField("Уведомления level up", "<#" + levelupChannelId + ">", false);
|
||||
} else {
|
||||
embed.addField("Уведомления level up", "❌ Не установлен", false);
|
||||
}
|
||||
|
||||
event.replyEmbeds(embed.build()).setEphemeral(true).queue();
|
||||
}
|
||||
|
||||
public static void register(net.dv8tion.jda.api.JDA jda) {
|
||||
jda.updateCommands().addCommands(
|
||||
Commands.slash("log-setup", "Настройка логирования")
|
||||
.addSubcommands(
|
||||
new SubcommandData("moderation", "Установить канал для логов модерации")
|
||||
.addOption(OptionType.CHANNEL, "channel", "Текстовый канал", true),
|
||||
new SubcommandData("levelup", "Установить канал для уведомлений о level up")
|
||||
.addOption(OptionType.CHANNEL, "channel", "Текстовый канал", true),
|
||||
new SubcommandData("info", "Показать текущую конфигурацию")
|
||||
)
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
package creator.loki.command.impl.setup;
|
||||
|
||||
import creator.loki.database.ConfigRepository;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.Category;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Команда настройки системы личных голосовых каналов
|
||||
*/
|
||||
public class VoiceSetupCommand extends ListenerAdapter {
|
||||
private static final Logger logger = LoggerFactory.getLogger(VoiceSetupCommand.class);
|
||||
private final ConfigRepository configRepo;
|
||||
|
||||
public VoiceSetupCommand(ConfigRepository configRepo) {
|
||||
this.configRepo = configRepo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
|
||||
if (!"voice-setup".equals(event.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка прав администратора
|
||||
Member member = event.getMember();
|
||||
if (member == null || !member.hasPermission(Permission.ADMINISTRATOR)) {
|
||||
event.reply("❌ У вас нет прав для использования этой команды!").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String subcommand = event.getSubcommandName();
|
||||
if (subcommand == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (subcommand) {
|
||||
case "trigger" -> handleTrigger(event);
|
||||
case "category" -> handleCategory(event);
|
||||
case "text-channel" -> handleTextChannel(event);
|
||||
case "info" -> handleInfo(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить канал-триггер
|
||||
*/
|
||||
private void handleTrigger(SlashCommandInteractionEvent event) {
|
||||
VoiceChannel channel = event.getOption("channel").getAsChannel().asVoiceChannel();
|
||||
|
||||
configRepo.set("voice.trigger_channel_id", channel.getId());
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("✅ Канал-триггер установлен")
|
||||
.setDescription("Теперь при входе в " + channel.getAsMention() + " будет создаваться личный канал")
|
||||
.setColor(Color.GREEN);
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
logger.info("Установлен канал-триггер: {} ({})", channel.getName(), channel.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить категорию для личных каналов
|
||||
*/
|
||||
private void handleCategory(SlashCommandInteractionEvent event) {
|
||||
Category category = event.getOption("category").getAsChannel().asCategory();
|
||||
|
||||
configRepo.set("voice.category_id", category.getId());
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("✅ Категория установлена")
|
||||
.setDescription("Личные каналы будут создаваться в категории: **" + category.getName() + "**")
|
||||
.setColor(Color.GREEN);
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
logger.info("Установлена категория: {} ({})", category.getName(), category.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить текстовый канал для уведомлений
|
||||
*/
|
||||
private void handleTextChannel(SlashCommandInteractionEvent event) {
|
||||
TextChannel channel = event.getOption("channel").getAsChannel().asTextChannel();
|
||||
|
||||
configRepo.set("voice.text_channel_id", channel.getId());
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("✅ Текстовый канал установлен")
|
||||
.setDescription("Панели управления будут отправляться в " + channel.getAsMention())
|
||||
.setColor(Color.GREEN);
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
logger.info("Установлен текстовый канал: {} ({})", channel.getName(), channel.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Показать текущую конфигурацию
|
||||
*/
|
||||
private void handleInfo(SlashCommandInteractionEvent event) {
|
||||
String triggerId = configRepo.get("voice.trigger_channel_id");
|
||||
String categoryId = configRepo.get("voice.category_id");
|
||||
String textChannelId = configRepo.get("voice.text_channel_id");
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("🎤 Конфигурация системы личных каналов")
|
||||
.setColor(Color.BLUE);
|
||||
|
||||
if (triggerId != null) {
|
||||
embed.addField("Канал-триггер", "<#" + triggerId + ">", false);
|
||||
} else {
|
||||
embed.addField("Канал-триггер", "❌ Не установлен", false);
|
||||
}
|
||||
|
||||
if (categoryId != null) {
|
||||
Category category = event.getGuild().getCategoryById(categoryId);
|
||||
embed.addField("Категория", category != null ? category.getName() : "❌ Не найдена", false);
|
||||
} else {
|
||||
embed.addField("Категория", "❌ Не установлена", false);
|
||||
}
|
||||
|
||||
if (textChannelId != null) {
|
||||
embed.addField("Текстовый канал", "<#" + textChannelId + ">", false);
|
||||
} else {
|
||||
embed.addField("Текстовый канал", "❌ Не установлен", false);
|
||||
}
|
||||
|
||||
event.replyEmbeds(embed.build()).setEphemeral(true).queue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Зарегистрировать команду в Discord
|
||||
*/
|
||||
public static void register(net.dv8tion.jda.api.JDA jda) {
|
||||
jda.updateCommands().addCommands(
|
||||
Commands.slash("voice-setup", "Настройка системы личных голосовых каналов")
|
||||
.addSubcommands(
|
||||
new SubcommandData("trigger", "Установить канал-триггер")
|
||||
.addOption(OptionType.CHANNEL, "channel", "Голосовой канал", true),
|
||||
new SubcommandData("category", "Установить категорию для личных каналов")
|
||||
.addOption(OptionType.CHANNEL, "category", "Категория", true),
|
||||
new SubcommandData("text-channel", "Установить текстовый канал для панелей управления")
|
||||
.addOption(OptionType.CHANNEL, "channel", "Текстовый канал", true),
|
||||
new SubcommandData("info", "Показать текущую конфигурацию")
|
||||
)
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package creator.loki.command.impl.stats;
|
||||
|
||||
import creator.loki.level.LevelSystem;
|
||||
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
|
||||
/**
|
||||
* Обработчик кнопок таблицы лидеров (Single Responsibility)
|
||||
*/
|
||||
public class LeaderboardButtonListener extends ListenerAdapter {
|
||||
private final LevelSystem levelSystem;
|
||||
|
||||
public LeaderboardButtonListener(LevelSystem levelSystem) {
|
||||
this.levelSystem = levelSystem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onButtonInteraction(ButtonInteractionEvent event) {
|
||||
String buttonId = event.getComponentId();
|
||||
|
||||
if (!buttonId.startsWith("lb_")) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.deferEdit().queue();
|
||||
|
||||
switch (buttonId) {
|
||||
case "lb_level" -> showLevelLeaderboard(event);
|
||||
case "lb_messages" -> showMessageLeaderboard(event);
|
||||
case "lb_voice" -> showVoiceLeaderboard(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void showLevelLeaderboard(ButtonInteractionEvent event) {
|
||||
var top = levelSystem.getTopByLevel(10);
|
||||
var embed = LeaderboardFormatter.formatLevelLeaderboard(top);
|
||||
event.getHook().editOriginalEmbeds(embed).queue();
|
||||
}
|
||||
|
||||
private void showMessageLeaderboard(ButtonInteractionEvent event) {
|
||||
var top = levelSystem.getTopByMessages(10);
|
||||
var embed = LeaderboardFormatter.formatMessageLeaderboard(top);
|
||||
event.getHook().editOriginalEmbeds(embed).queue();
|
||||
}
|
||||
|
||||
private void showVoiceLeaderboard(ButtonInteractionEvent event) {
|
||||
var top = levelSystem.getTopByVoice(10);
|
||||
var embed = LeaderboardFormatter.formatVoiceLeaderboard(top);
|
||||
event.getHook().editOriginalEmbeds(embed).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package creator.loki.command.impl.stats;
|
||||
|
||||
import net.dv8tion.jda.api.interactions.components.buttons.Button;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Кнопки для таблицы лидеров
|
||||
*/
|
||||
public class LeaderboardButtons {
|
||||
|
||||
public static List<Button> getButtons() {
|
||||
return List.of(
|
||||
Button.primary("lb_level", "📊 Уровни"),
|
||||
Button.success("lb_messages", "💬 Сообщения"),
|
||||
Button.secondary("lb_voice", "🎤 Войс")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package creator.loki.command.impl.stats;
|
||||
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.level.UserStats;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Форматирование таблиц лидеров (DRY принцип)
|
||||
*/
|
||||
public class LeaderboardFormatter {
|
||||
|
||||
public static MessageEmbed formatLevelLeaderboard(List<Map.Entry<String, UserStats>> top) {
|
||||
return formatLeaderboard(
|
||||
top,
|
||||
"🏆 Топ-10 по уровням",
|
||||
new Color(255, 215, 0),
|
||||
entry -> {
|
||||
UserStats stats = entry.getValue();
|
||||
return "Уровень **" + stats.getLevel() + "** (" + stats.getTotalXP() + " XP)";
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static MessageEmbed formatMessageLeaderboard(List<Map.Entry<String, UserStats>> top) {
|
||||
return formatLeaderboard(
|
||||
top,
|
||||
"💬 Топ-10 по сообщениям",
|
||||
new Color(52, 152, 219),
|
||||
entry -> "**" + entry.getValue().getMessageCount() + "** сообщений"
|
||||
);
|
||||
}
|
||||
|
||||
public static MessageEmbed formatVoiceLeaderboard(List<Map.Entry<String, UserStats>> top) {
|
||||
return formatLeaderboard(
|
||||
top,
|
||||
"🎤 Топ-10 по времени в войсе",
|
||||
new Color(155, 89, 182),
|
||||
entry -> {
|
||||
long minutes = entry.getValue().getVoiceMinutes();
|
||||
long hours = minutes / 60;
|
||||
long mins = minutes % 60;
|
||||
return "**" + hours + "ч " + mins + "м** в войсе";
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static MessageEmbed formatLeaderboard(
|
||||
List<Map.Entry<String, UserStats>> top,
|
||||
String title,
|
||||
Color color,
|
||||
Function<Map.Entry<String, UserStats>, String> statFormatter
|
||||
) {
|
||||
StringBuilder description = new StringBuilder();
|
||||
int position = 1;
|
||||
|
||||
for (var entry : top) {
|
||||
String emoji = getPositionEmoji(position);
|
||||
String userId = entry.getKey();
|
||||
String stats = statFormatter.apply(entry);
|
||||
|
||||
description.append(emoji)
|
||||
.append(" **#").append(position).append("** <@").append(userId).append(">")
|
||||
.append(" - ").append(stats).append("\n");
|
||||
|
||||
position++;
|
||||
}
|
||||
|
||||
return EmbedFactory.create()
|
||||
.setTitle(title)
|
||||
.setDescription(description.toString())
|
||||
.setColor(color)
|
||||
.setFooter("Выбери категорию ниже")
|
||||
.build();
|
||||
}
|
||||
|
||||
private static String getPositionEmoji(int position) {
|
||||
return switch (position) {
|
||||
case 1 -> "🥇";
|
||||
case 2 -> "🥈";
|
||||
case 3 -> "🥉";
|
||||
default -> "⭐";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package creator.loki.command.impl.stats;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MinusRepCommand implements Command {
|
||||
private final JsonDataStore dataStore;
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public MinusRepCommand(JsonDataStore dataStore, CooldownManager cooldownManager) {
|
||||
this.dataStore = dataStore;
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "-rep";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Забрать -1 репутацию у пользователя";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "-rep <@user>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
|
||||
if (mentioned.isEmpty()) {
|
||||
context.getChannel().sendMessage("Укажи пользователя через @").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
|
||||
// Нельзя забирать у себя
|
||||
if (target.getId().equals(context.getAuthor().getId())) {
|
||||
context.getChannel().sendMessage("Нельзя забирать репутацию у самого себя!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка cooldown (24 часа = 86400 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "-rep")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "-rep");
|
||||
long hours = remaining / 3600;
|
||||
long minutes = (remaining % 3600) / 60;
|
||||
context.getChannel().sendMessage(
|
||||
"Ты сможешь забрать репутацию через " + hours + "ч " + minutes + "м"
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем репутацию
|
||||
Type type = new TypeToken<Map<String, Integer>>(){}.getType();
|
||||
Map<String, Integer> reputation = dataStore.getData("reputation", type);
|
||||
if (reputation == null) {
|
||||
reputation = new HashMap<>();
|
||||
}
|
||||
|
||||
// Забираем -1
|
||||
int currentRep = reputation.getOrDefault(target.getId(), 0);
|
||||
reputation.put(target.getId(), currentRep - 1);
|
||||
dataStore.setData("reputation", reputation);
|
||||
|
||||
// Применяем cooldown 24 часа
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "-rep", 86400);
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("⭐❌ Репутация")
|
||||
.setDescription(
|
||||
context.getAuthor().getAsMention() + " забрал репутацию у " + target.getAsMention() + " **(-1)**\n\n" +
|
||||
"Текущая репутация: **" + (currentRep - 1) + "**"
|
||||
)
|
||||
.setColor(new Color(231, 76, 60))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package creator.loki.command.impl.stats;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.level.LevelSystem;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Команда таблицы лидеров (рефакторинг: 241 → 50 строк)
|
||||
*/
|
||||
public class NewLeaderboardCommand implements Command {
|
||||
private final LevelSystem levelSystem;
|
||||
|
||||
public NewLeaderboardCommand(LevelSystem levelSystem) {
|
||||
this.levelSystem = levelSystem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "leaderboard";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Таблица лидеров (уровни, сообщения, войс)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "leaderboard";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Статистика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
var top = levelSystem.getTopByLevel(10);
|
||||
|
||||
if (top.isEmpty()) {
|
||||
context.getChannel().sendMessage("❌ Пока нет статистики!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = LeaderboardFormatter.formatLevelLeaderboard(top);
|
||||
var buttons = LeaderboardButtons.getButtons();
|
||||
|
||||
context.getChannel().sendMessageEmbeds(embed)
|
||||
.setActionRow(buttons)
|
||||
.queue(message -> {
|
||||
// Удаляем кнопки через 5 минут
|
||||
message.editMessageComponents().queueAfter(5, TimeUnit.MINUTES);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package creator.loki.command.impl.stats;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.level.LevelSystem;
|
||||
import creator.loki.level.UserStats;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Команда для просмотра уровня и статистики
|
||||
*/
|
||||
public class RankCommand implements Command {
|
||||
private final LevelSystem levelSystem;
|
||||
|
||||
public RankCommand(LevelSystem levelSystem) {
|
||||
this.levelSystem = levelSystem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "rank";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Посмотреть свой уровень и статистику";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "rank [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Статистика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
Member target;
|
||||
|
||||
if (!context.getMessage().getMentions().getMembers().isEmpty()) {
|
||||
target = context.getMessage().getMentions().getMembers().get(0);
|
||||
} else {
|
||||
target = context.getAuthor();
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
context.getChannel().sendMessage("❌ Пользователь не найден!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String userId = target.getId();
|
||||
UserStats stats = levelSystem.getUserStats(userId);
|
||||
LevelSystem.LevelProgress progress = levelSystem.getProgress(userId);
|
||||
|
||||
// Прогресс-бар
|
||||
int barLength = 20;
|
||||
int filled = (int) (progress.getPercentage() / 100.0 * barLength);
|
||||
StringBuilder progressBar = new StringBuilder();
|
||||
for (int i = 0; i < barLength; i++) {
|
||||
progressBar.append(i < filled ? "█" : "░");
|
||||
}
|
||||
|
||||
long hours = stats.getVoiceMinutes() / 60;
|
||||
long minutes = stats.getVoiceMinutes() % 60;
|
||||
|
||||
var embed = EmbedFactory.create()
|
||||
.setTitle("📊 Статистика " + target.getEffectiveName())
|
||||
.setThumbnail(target.getEffectiveAvatarUrl())
|
||||
.addField("🎯 Уровень", "**" + stats.getLevel() + "**", true)
|
||||
.addField("⭐ Всего XP", "**" + stats.getTotalXP() + "**", true)
|
||||
.addField("📈 Прогресс",
|
||||
progressBar.toString() + " **" + String.format("%.1f", progress.getPercentage()) + "%**\n" +
|
||||
progress.getCurrentLevelXP() + " / " + progress.getXpForNextLevel() + " XP до следующего уровня",
|
||||
false)
|
||||
.addField("💬 Сообщений", "**" + stats.getMessageCount() + "**", true)
|
||||
.addField("🎤 Время в войсе", "**" + hours + "ч " + minutes + "м**", true)
|
||||
.setColor(new Color(46, 204, 113))
|
||||
.build();
|
||||
|
||||
context.getChannel().sendMessageEmbeds(embed).queue();
|
||||
}
|
||||
}
|
||||
119
src/main/java/creator/loki/command/impl/stats/RepCommand.java
Normal file
119
src/main/java/creator/loki/command/impl/stats/RepCommand.java
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
package creator.loki.command.impl.stats;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class RepCommand implements Command {
|
||||
private final JsonDataStore dataStore;
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public RepCommand(JsonDataStore dataStore, CooldownManager cooldownManager) {
|
||||
this.dataStore = dataStore;
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "rep";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Дать +1 репутацию пользователю";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "rep <@user>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
|
||||
// Если нет упоминаний - показать свою репутацию
|
||||
if (mentioned.isEmpty()) {
|
||||
showReputation(context, context.getAuthor());
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
|
||||
// Нельзя давать себе
|
||||
if (target.getId().equals(context.getAuthor().getId())) {
|
||||
context.getChannel().sendMessage("Нельзя давать репутацию самому себе!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка cooldown (24 часа = 86400 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "rep")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "rep");
|
||||
long hours = remaining / 3600;
|
||||
long minutes = (remaining % 3600) / 60;
|
||||
context.getChannel().sendMessage(
|
||||
"Ты сможешь дать репутацию через " + hours + "ч " + minutes + "м"
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем репутацию
|
||||
Type type = new TypeToken<Map<String, Integer>>(){}.getType();
|
||||
Map<String, Integer> reputation = dataStore.getData("reputation", type);
|
||||
if (reputation == null) {
|
||||
reputation = new HashMap<>();
|
||||
}
|
||||
|
||||
// Добавляем +1
|
||||
int currentRep = reputation.getOrDefault(target.getId(), 0);
|
||||
reputation.put(target.getId(), currentRep + 1);
|
||||
dataStore.setData("reputation", reputation);
|
||||
|
||||
// Применяем cooldown 24 часа
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "rep", 86400);
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("⭐ Репутация")
|
||||
.setDescription(
|
||||
context.getAuthor().getAsMention() + " дал репутацию " + target.getAsMention() + " **(+1)**\n\n" +
|
||||
"Текущая репутация: **" + (currentRep + 1) + "**"
|
||||
)
|
||||
.setColor(new Color(241, 196, 15))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private void showReputation(CommandContext context, Member member) {
|
||||
Type type = new TypeToken<Map<String, Integer>>(){}.getType();
|
||||
Map<String, Integer> reputation = dataStore.getData("reputation", type);
|
||||
|
||||
int rep = 0;
|
||||
if (reputation != null) {
|
||||
rep = reputation.getOrDefault(member.getId(), 0);
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("⭐ Репутация")
|
||||
.setDescription(member.getAsMention() + " имеет **" + rep + "** репутации")
|
||||
.setColor(new Color(241, 196, 15))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
114
src/main/java/creator/loki/command/impl/stats/RepTopCommand.java
Normal file
114
src/main/java/creator/loki/command/impl/stats/RepTopCommand.java
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
package creator.loki.command.impl.stats;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Топ пользователей по репутации
|
||||
*/
|
||||
public class RepTopCommand implements Command {
|
||||
|
||||
private final JsonDataStore dataStore;
|
||||
|
||||
public RepTopCommand(JsonDataStore dataStore) {
|
||||
this.dataStore = dataStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "reptop";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Топ-10 пользователей по репутации";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "reptop";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
Map<String, Integer> reputation = dataStore.getReputation();
|
||||
|
||||
if (reputation.isEmpty()) {
|
||||
context.getChannel().sendMessage("❌ Пока никто не имеет репутации!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Сортируем по репутации и берём топ-10
|
||||
var topUsers = reputation.entrySet().stream()
|
||||
.sorted((e1, e2) -> Integer.compare(e2.getValue(), e1.getValue()))
|
||||
.limit(10)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
StringBuilder description = new StringBuilder();
|
||||
int position = 1;
|
||||
|
||||
for (var entry : topUsers) {
|
||||
String userId = entry.getKey();
|
||||
int rep = entry.getValue();
|
||||
|
||||
// Получаем пользователя
|
||||
context.getGuild().retrieveMemberById(userId).queue(
|
||||
member -> {
|
||||
// Эмодзи для топ-3
|
||||
String emoji = switch (position) {
|
||||
case 1 -> "🥇";
|
||||
case 2 -> "🥈";
|
||||
case 3 -> "🥉";
|
||||
default -> "⭐";
|
||||
};
|
||||
|
||||
description.append(emoji)
|
||||
.append(" **#")
|
||||
.append(position)
|
||||
.append("** ")
|
||||
.append(member.getAsMention())
|
||||
.append(" - **")
|
||||
.append(rep)
|
||||
.append("** репутации\n");
|
||||
},
|
||||
error -> {
|
||||
// Если пользователь не найден
|
||||
description.append("⭐ **#")
|
||||
.append(position)
|
||||
.append("** <@")
|
||||
.append(userId)
|
||||
.append("> - **")
|
||||
.append(rep)
|
||||
.append("** репутации\n");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Отправляем embed после небольшой задержки, чтобы все пользователи загрузились
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🏆 Топ-10 по репутации")
|
||||
.setDescription(description.toString())
|
||||
.setColor(new Color(255, 215, 0))
|
||||
.setFooter("Используй !rep <@user> чтобы дать репутацию")
|
||||
.build()
|
||||
).queue();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package creator.loki.command.impl.stats;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.OnlineStatus;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class StatsCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "stats";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Статистика сервера";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "stats";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (context.getGuild() == null) {
|
||||
context.getChannel().sendMessage("Команда доступна только на сервере").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Guild guild = context.getGuild();
|
||||
|
||||
// Подсчёт статистики
|
||||
int totalMembers = guild.getMemberCount();
|
||||
long onlineMembers = guild.getMembers().stream()
|
||||
.filter(m -> m.getOnlineStatus() == OnlineStatus.ONLINE)
|
||||
.count();
|
||||
long inVoice = guild.getMembers().stream()
|
||||
.filter(m -> m.getVoiceState() != null && m.getVoiceState().inAudioChannel())
|
||||
.count();
|
||||
int textChannels = guild.getTextChannels().size();
|
||||
int voiceChannels = guild.getVoiceChannels().size();
|
||||
int roles = guild.getRoles().size();
|
||||
int bots = (int) guild.getMembers().stream()
|
||||
.filter(m -> m.getUser().isBot())
|
||||
.count();
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("📊 Статистика сервера")
|
||||
.setThumbnail(guild.getIconUrl())
|
||||
.addField("👥 Участников", String.valueOf(totalMembers), true)
|
||||
.addField("🟢 Онлайн", String.valueOf(onlineMembers), true)
|
||||
.addField("🔊 В войсе", String.valueOf(inVoice), true)
|
||||
.addField("💬 Текстовых каналов", String.valueOf(textChannels), true)
|
||||
.addField("🎤 Голосовых каналов", String.valueOf(voiceChannels), true)
|
||||
.addField("🎭 Ролей", String.valueOf(roles), true)
|
||||
.addField("🤖 Ботов", String.valueOf(bots), true)
|
||||
.addField("👤 Людей", String.valueOf(totalMembers - bots), true)
|
||||
.setColor(new Color(52, 152, 219))
|
||||
.setFooter("Сервер: " + guild.getName())
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package creator.loki.command.impl.utility;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
|
||||
public class AvatarCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "avatar";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показывает аватар пользователя";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "avatar [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
User targetUser;
|
||||
|
||||
// Если упомянут пользователь - показываем его аватар
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (!mentioned.isEmpty()) {
|
||||
targetUser = mentioned.get(0).getUser();
|
||||
} else {
|
||||
// Иначе показываем аватар автора команды
|
||||
targetUser = context.getAuthor().getUser();
|
||||
}
|
||||
|
||||
String avatarUrl = targetUser.getEffectiveAvatarUrl() + "?size=512";
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🖼️ Аватар " + targetUser.getName())
|
||||
.setImage(avatarUrl)
|
||||
.setColor(Color.BLUE)
|
||||
.setFooter("ID: " + targetUser.getId())
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
113
src/main/java/creator/loki/command/impl/utility/HelpCommand.java
Normal file
113
src/main/java/creator/loki/command/impl/utility/HelpCommand.java
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
package creator.loki.command.impl.utility;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.core.CommandRegistry;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class HelpCommand implements Command {
|
||||
private final CommandRegistry registry;
|
||||
private final BotConfig config;
|
||||
|
||||
private static final Map<String, String> CATEGORY_EMOJIS = new HashMap<>();
|
||||
private static final Map<String, Color> CATEGORY_COLORS = new HashMap<>();
|
||||
|
||||
static {
|
||||
CATEGORY_EMOJIS.put("Общие", "📋");
|
||||
CATEGORY_EMOJIS.put("Роли", "🎭");
|
||||
CATEGORY_EMOJIS.put("Развлечения", "🎮");
|
||||
CATEGORY_EMOJIS.put("Модерация", "🛡️");
|
||||
|
||||
CATEGORY_COLORS.put("Общие", new Color(52, 152, 219));
|
||||
CATEGORY_COLORS.put("Роли", new Color(155, 89, 182));
|
||||
CATEGORY_COLORS.put("Развлечения", new Color(241, 196, 15));
|
||||
CATEGORY_COLORS.put("Модерация", new Color(231, 76, 60));
|
||||
}
|
||||
|
||||
public HelpCommand(CommandRegistry registry, BotConfig config) {
|
||||
this.registry = registry;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "help";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показывает список всех команд";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
var commands = registry.getAllCommands();
|
||||
|
||||
// Группируем команды по категориям
|
||||
Map<String, List<Command>> commandsByCategory = commands.stream()
|
||||
.collect(Collectors.groupingBy(Command::getCategory));
|
||||
|
||||
// Создаём красивый embed
|
||||
EmbedFactory embed = EmbedFactory.create()
|
||||
.setTitle("📚 Справка по командам")
|
||||
.setDescription(
|
||||
"**Префикс:** `" + config.getPrefix() + "`\n" +
|
||||
"**Всего команд:** " + commands.size() + "\n\n" +
|
||||
"Используй `" + config.getPrefix() + "<команда>` для выполнения"
|
||||
)
|
||||
.setColor(new Color(88, 101, 242))
|
||||
.setThumbnail(context.getGuild() != null ?
|
||||
context.getGuild().getIconUrl() : null);
|
||||
|
||||
// Добавляем команды по категориям
|
||||
commandsByCategory.forEach((category, cmds) -> {
|
||||
String emoji = CATEGORY_EMOJIS.getOrDefault(category, "📌");
|
||||
StringBuilder commandList = new StringBuilder();
|
||||
|
||||
int count = 0;
|
||||
int part = 1;
|
||||
|
||||
for (Command cmd : cmds) {
|
||||
String cmdText = "`" + config.getPrefix() + cmd.getUsage() + "`\n" +
|
||||
cmd.getDescription() + "\n\n";
|
||||
|
||||
// Если добавление команды превысит лимит, создаём новое поле
|
||||
if (commandList.length() + cmdText.length() > 1000) {
|
||||
embed.addField(
|
||||
emoji + " " + category + (part > 1 ? " (" + part + ")" : ""),
|
||||
commandList.toString().trim(),
|
||||
false
|
||||
);
|
||||
commandList = new StringBuilder();
|
||||
part++;
|
||||
}
|
||||
|
||||
commandList.append(cmdText);
|
||||
count++;
|
||||
}
|
||||
|
||||
// Добавляем оставшиеся команды
|
||||
if (commandList.length() > 0) {
|
||||
embed.addField(
|
||||
emoji + " " + category + (part > 1 ? " (" + part + ")" : ""),
|
||||
commandList.toString().trim(),
|
||||
false
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
embed.setFooter(
|
||||
"Запросил: " + context.getAuthor().getUser().getName(),
|
||||
context.getAuthor().getUser().getAvatarUrl()
|
||||
);
|
||||
|
||||
context.getChannel().sendMessageEmbeds(embed.build()).queue();
|
||||
}
|
||||
}
|
||||
116
src/main/java/creator/loki/command/impl/utility/InfoCommand.java
Normal file
116
src/main/java/creator/loki/command/impl/utility/InfoCommand.java
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
package creator.loki.command.impl.utility;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class InfoCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "info";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Информация о сервере или пользователе";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "info [server|user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
String type = context.getArgs().isEmpty() ? "server" : context.getArgs().get(0).toLowerCase();
|
||||
|
||||
switch (type) {
|
||||
case "server":
|
||||
showServerInfo(context);
|
||||
break;
|
||||
case "user":
|
||||
showUserInfo(context);
|
||||
break;
|
||||
default:
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Неверный аргумент",
|
||||
"Используй: `!info [server|user]`"
|
||||
)
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
||||
private void showServerInfo(CommandContext context) {
|
||||
Guild guild = context.getGuild();
|
||||
if (guild == null) return;
|
||||
|
||||
String iconUrl = guild.getIconUrl();
|
||||
if (iconUrl == null) iconUrl = "";
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🏰 Информация о сервере")
|
||||
.setColor(Color.BLUE)
|
||||
.setThumbnail(iconUrl)
|
||||
.addField("Название", guild.getName(), true)
|
||||
.addField("ID", guild.getId(), true)
|
||||
.addField("Владелец", guild.getOwner() != null ? guild.getOwner().getAsMention() : "Неизвестно", true)
|
||||
.addField("Участников", String.valueOf(guild.getMemberCount()), true)
|
||||
.addField("Каналов", String.valueOf(guild.getChannels().size()), true)
|
||||
.addField("Ролей", String.valueOf(guild.getRoles().size()), true)
|
||||
.addField(
|
||||
"Создан",
|
||||
guild.getTimeCreated().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")),
|
||||
false
|
||||
)
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private void showUserInfo(CommandContext context) {
|
||||
Member member = context.getAuthor();
|
||||
if (member == null) return;
|
||||
|
||||
User user = member.getUser();
|
||||
|
||||
String roles = member.getRoles().stream()
|
||||
.map(Role::getAsMention)
|
||||
.collect(Collectors.joining(", "));
|
||||
if (roles.isEmpty()) {
|
||||
roles = "Нет ролей";
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("👤 Информация о пользователе")
|
||||
.setColor(Color.CYAN)
|
||||
.setThumbnail(user.getEffectiveAvatarUrl())
|
||||
.addField("Имя", user.getName(), true)
|
||||
.addField("ID", user.getId(), true)
|
||||
.addField("Никнейм", member.getNickname() != null ? member.getNickname() : "Не установлен", true)
|
||||
.addField("Бот?", user.isBot() ? "Да" : "Нет", true)
|
||||
.addField(
|
||||
"Присоединился",
|
||||
member.getTimeJoined().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")),
|
||||
false
|
||||
)
|
||||
.addField(
|
||||
"Роли (" + member.getRoles().size() + ")",
|
||||
roles,
|
||||
false
|
||||
)
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package creator.loki.command.impl.utility;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.metrics.MetricsCollector;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class MetricsCommand implements Command {
|
||||
private final MetricsCollector metrics;
|
||||
|
||||
public MetricsCommand(MetricsCollector metrics) {
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext ctx) {
|
||||
// Только администраторы могут видеть метрики
|
||||
if (ctx.getAuthor() == null || !ctx.getAuthor().hasPermission(Permission.ADMINISTRATOR)) {
|
||||
ctx.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("❌ Недостаточно прав")
|
||||
.setDescription("Эта команда доступна только администраторам.")
|
||||
.setColor(Color.RED)
|
||||
.build()
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String stats = metrics.getFullStats();
|
||||
|
||||
ctx.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("📊 Метрики бота")
|
||||
.setDescription("```\n" + stats + "\n```")
|
||||
.setColor(new Color(88, 101, 242))
|
||||
.setFooter("Используй !metrics reset для сброса")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "metrics";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показывает метрики и статистику бота (только для администраторов)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "!metrics";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Система";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package creator.loki.command.impl.utility;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class PingCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ping";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Проверка задержки бота";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
context.getChannel().sendMessage("Измеряю задержку...").queue(message -> {
|
||||
long ping = System.currentTimeMillis() - startTime;
|
||||
long gatewayPing = context.getMessage().getJDA().getGatewayPing();
|
||||
|
||||
message.editMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🏓 Pong!")
|
||||
.setColor(Color.GREEN)
|
||||
.addField("Задержка API", ping + "ms", true)
|
||||
.addField("Задержка Gateway", gatewayPing + "ms", true)
|
||||
.build()
|
||||
).queue();
|
||||
});
|
||||
}
|
||||
}
|
||||
103
src/main/java/creator/loki/command/impl/utility/PollCommand.java
Normal file
103
src/main/java/creator/loki/command/impl/utility/PollCommand.java
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package creator.loki.command.impl.utility;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import net.dv8tion.jda.api.entities.emoji.Emoji;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class PollCommand implements Command {
|
||||
private static final String[] EMOJI_NUMBERS = {
|
||||
"1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"
|
||||
};
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "poll";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Создаёт голосование с реакциями";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "poll <вопрос> | <вариант1> | <вариант2> ...";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (context.getArgs().isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Неверное использование",
|
||||
"Используй: `!poll <вопрос> | <вариант1> | <вариант2> ...`\n" +
|
||||
"Пример: `!poll Какой язык лучше? | Java | Python | JavaScript`"
|
||||
)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Объединяем все аргументы и разделяем по |
|
||||
String fullText = String.join(" ", context.getArgs());
|
||||
String[] parts = fullText.split("\\|");
|
||||
|
||||
if (parts.length < 3) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Недостаточно вариантов",
|
||||
"Укажите вопрос и минимум 2 варианта ответа, разделённых символом `|`"
|
||||
)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (parts.length > 11) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Слишком много вариантов",
|
||||
"Максимум 10 вариантов ответа"
|
||||
)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String question = parts[0].trim();
|
||||
String[] options = new String[parts.length - 1];
|
||||
for (int i = 1; i < parts.length; i++) {
|
||||
options[i - 1] = parts[i].trim();
|
||||
}
|
||||
|
||||
// Формируем описание с вариантами
|
||||
StringBuilder description = new StringBuilder();
|
||||
for (int i = 0; i < options.length; i++) {
|
||||
description.append(EMOJI_NUMBERS[i])
|
||||
.append(" ")
|
||||
.append(options[i])
|
||||
.append("\n");
|
||||
}
|
||||
|
||||
// Создаём embed
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("📊 " + question)
|
||||
.setDescription(description.toString())
|
||||
.setColor(Color.ORANGE)
|
||||
.setFooter("Голосование от " + context.getAuthor().getUser().getName())
|
||||
.build()
|
||||
).queue(message -> {
|
||||
// Добавляем реакции
|
||||
for (int i = 0; i < options.length; i++) {
|
||||
message.addReaction(Emoji.fromUnicode(EMOJI_NUMBERS[i])).queue();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package creator.loki.command.impl.utility;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.profile.ProfileData;
|
||||
import creator.loki.profile.ProfileGenerator;
|
||||
import creator.loki.profile.ProfileService;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.utils.FileUpload;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Команда для отображения профиля пользователя
|
||||
*/
|
||||
public class ProfileCommand implements Command {
|
||||
private final ProfileService profileService;
|
||||
private final ProfileGenerator profileGenerator;
|
||||
|
||||
public ProfileCommand(JsonDataStore dataStore) {
|
||||
this.profileService = new ProfileService(dataStore);
|
||||
this.profileGenerator = new ProfileGenerator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "profile";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показать профиль пользователя";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "profile [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Общие";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (context.getGuild() == null) {
|
||||
context.getChannel().sendMessage("Команда доступна только на сервере").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Определяем целевого пользователя
|
||||
Member target;
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
target = context.getAuthor();
|
||||
} else {
|
||||
target = mentioned.get(0);
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
context.getChannel().sendMessage("Пользователь не найден").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Отправляем сообщение о генерации
|
||||
context.getChannel().sendMessage("🎨 Генерирую профиль...").queue(loadingMsg -> {
|
||||
try {
|
||||
// Получаем данные профиля
|
||||
ProfileData profileData = profileService.getProfileData(target, context.getGuild());
|
||||
|
||||
// Генерируем изображение
|
||||
byte[] imageBytes = profileGenerator.generateProfile(profileData);
|
||||
|
||||
// Отправляем изображение
|
||||
loadingMsg.delete().queue();
|
||||
context.getChannel().sendFiles(
|
||||
FileUpload.fromData(imageBytes, "profile.png")
|
||||
).queue();
|
||||
|
||||
} catch (Exception e) {
|
||||
loadingMsg.editMessage("❌ Ошибка генерации профиля: " + e.getMessage()).queue();
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
138
src/main/java/creator/loki/command/impl/utility/SwapCommand.java
Normal file
138
src/main/java/creator/loki/command/impl/utility/SwapCommand.java
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package creator.loki.command.impl.utility;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.GuildVoiceState;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SwapCommand implements Command {
|
||||
private final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "swap";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Перекидывает пользователя между случайными голосовыми каналами";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "swap <@user> <количество 1-5>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (context.getGuild() == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка прав
|
||||
if (context.getAuthor() == null || !context.getAuthor().hasPermission(Permission.VOICE_MOVE_OTHERS)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Недостаточно прав", "У тебя нет прав на перемещение пользователей")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.getGuild().getSelfMember().hasPermission(Permission.VOICE_MOVE_OTHERS)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Недостаточно прав", "У бота нет прав на перемещение пользователей")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка аргументов
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty() || context.getArgs().size() < 2) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Используй: `!swap <@user> <количество 1-5>`")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
int swapCount;
|
||||
|
||||
try {
|
||||
swapCount = Integer.parseInt(context.getArgs().get(1));
|
||||
if (swapCount < 1 || swapCount > 5) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Количество должно быть от 1 до 5")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка, что пользователь в войсе
|
||||
GuildVoiceState voiceState = target.getVoiceState();
|
||||
if (voiceState == null || !voiceState.inAudioChannel()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", target.getUser().getName() + " не в голосовом канале")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
VoiceChannel originalChannel = voiceState.getChannel().asVoiceChannel();
|
||||
Guild guild = context.getGuild();
|
||||
|
||||
// Получаем все голосовые каналы кроме текущего
|
||||
List<VoiceChannel> voiceChannels = guild.getVoiceChannels().stream()
|
||||
.filter(vc -> !vc.getId().equals(originalChannel.getId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (voiceChannels.isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Недостаточно голосовых каналов для swap")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.success(
|
||||
"🔄 Swap запущен!",
|
||||
"Перекидываю " + target.getAsMention() + " между каналами " + swapCount + " раз(а)"
|
||||
)
|
||||
).queue();
|
||||
|
||||
// Запускаем swap в отдельном потоке
|
||||
new Thread(() -> {
|
||||
try {
|
||||
for (int i = 0; i < swapCount; i++) {
|
||||
// Перекидываем в случайный канал
|
||||
VoiceChannel randomChannel = voiceChannels.get(random.nextInt(voiceChannels.size()));
|
||||
guild.moveVoiceMember(target, randomChannel).queue();
|
||||
Thread.sleep(800);
|
||||
|
||||
// Возвращаем обратно
|
||||
guild.moveVoiceMember(target, originalChannel).queue();
|
||||
Thread.sleep(800);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Не удалось выполнить swap: " + e.getMessage())
|
||||
).queue();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
106
src/main/java/creator/loki/command/impl/utility/TtsCommand.java
Normal file
106
src/main/java/creator/loki/command/impl/utility/TtsCommand.java
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
package creator.loki.command.impl.utility;
|
||||
|
||||
import creator.loki.audio.AudioManager;
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.GuildVoiceState;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
|
||||
public class TtsCommand implements Command {
|
||||
private final CooldownManager cooldownManager;
|
||||
private final AudioManager audioManager;
|
||||
|
||||
public TtsCommand(CooldownManager cooldownManager, AudioManager audioManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
this.audioManager = audioManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "tts";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Бот заходит в голосовой канал и произносит текст";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "tts <текст>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (context.getGuild() == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.getArgs().isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Укажи текст для TTS")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member member = context.getAuthor();
|
||||
if (member == null) return;
|
||||
|
||||
// Проверка cooldown (15 секунд)
|
||||
if (cooldownManager.isOnCooldown(member.getId(), "tts")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(member.getId(), "tts");
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Подожди!",
|
||||
"Ты сможешь использовать TTS через " + remaining + " сек."
|
||||
)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка, что пользователь в голосовом канале
|
||||
GuildVoiceState voiceState = member.getVoiceState();
|
||||
if (voiceState == null || !voiceState.inAudioChannel()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Ты должен быть в голосовом канале")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
VoiceChannel voiceChannel = voiceState.getChannel().asVoiceChannel();
|
||||
String text = String.join(" ", context.getArgs());
|
||||
|
||||
if (text.length() > 200) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Слишком длинно", "Максимум 200 символов")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Подключаемся к голосовому каналу (если ещё не подключены)
|
||||
net.dv8tion.jda.api.managers.AudioManager audioManagerJda = context.getGuild().getAudioManager();
|
||||
if (!audioManagerJda.isConnected()) {
|
||||
audioManagerJda.openAudioConnection(voiceChannel);
|
||||
}
|
||||
|
||||
// Добавляем TTS в очередь
|
||||
audioManager.enqueueTts(context.getGuild(), text, context.getChannel());
|
||||
|
||||
cooldownManager.applyCooldown(member.getId(), "tts", 15);
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.success("TTS", "🔊 Добавлено в очередь: " + text)
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package creator.loki.command.slash;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Slash команда /avatar
|
||||
*/
|
||||
public class AvatarSlashCommand implements SlashCommand {
|
||||
|
||||
@Override
|
||||
public void execute(SlashCommandInteractionEvent event) {
|
||||
User targetUser = event.getOption("user") != null
|
||||
? event.getOption("user").getAsUser()
|
||||
: event.getUser();
|
||||
|
||||
String avatarUrl = targetUser.getEffectiveAvatarUrl() + "?size=512";
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("🖼️ Аватар " + targetUser.getName())
|
||||
.setImage(avatarUrl)
|
||||
.setColor(new Color(88, 101, 242))
|
||||
.setFooter("Запросил: " + event.getUser().getName());
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandData getCommandData() {
|
||||
return Commands.slash("avatar", "Показывает аватар пользователя")
|
||||
.addOption(OptionType.USER, "user", "Пользователь (по умолчанию - вы)", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "avatar";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показывает аватар пользователя";
|
||||
}
|
||||
}
|
||||
103
src/main/java/creator/loki/command/slash/BanSlashCommand.java
Normal file
103
src/main/java/creator/loki/command/slash/BanSlashCommand.java
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package creator.loki.command.slash;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
public class BanSlashCommand implements SlashCommand {
|
||||
|
||||
@Override
|
||||
public void execute(SlashCommandInteractionEvent event) {
|
||||
if (event.getGuild() == null) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member author = event.getMember();
|
||||
if (author == null || !author.hasPermission(Permission.BAN_MEMBERS)) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "У вас нет прав на бан пользователей")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event.getGuild().getSelfMember().hasPermission(Permission.BAN_MEMBERS)) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "У бота нет прав на бан пользователей")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = event.getOption("user").getAsMember();
|
||||
if (target == null) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Пользователь не найден")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.getId().equals(author.getId())) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Вы не можете забанить самого себя")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.getUser().isBot() && target.getId().equals(event.getGuild().getSelfMember().getId())) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Я не могу забанить самого себя")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!author.canInteract(target)) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "Вы не можете забанить этого пользователя")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event.getGuild().getSelfMember().canInteract(target)) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "Я не могу забанить этого пользователя")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String reason = event.getOption("reason") != null
|
||||
? event.getOption("reason").getAsString()
|
||||
: "Причина не указана";
|
||||
|
||||
int deleteDays = event.getOption("delete_days") != null
|
||||
? event.getOption("delete_days").getAsInt()
|
||||
: 0;
|
||||
|
||||
event.deferReply().queue();
|
||||
|
||||
event.getGuild().ban(target, deleteDays, java.util.concurrent.TimeUnit.DAYS)
|
||||
.reason(reason)
|
||||
.queue(
|
||||
success -> event.getHook().editOriginalEmbeds(
|
||||
EmbedTemplates.success(
|
||||
"Пользователь забанен",
|
||||
"**Пользователь:** " + target.getUser().getAsTag() + "\n" +
|
||||
"**Причина:** " + reason + "\n" +
|
||||
"**Удалено сообщений за дней:** " + deleteDays + "\n" +
|
||||
"**Модератор:** " + author.getAsMention()
|
||||
)
|
||||
).queue(),
|
||||
error -> event.getHook().editOriginalEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Не удалось забанить: " + error.getMessage())
|
||||
).queue()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandData getCommandData() {
|
||||
return Commands.slash("ban", "Банит пользователя на сервере")
|
||||
.addOption(OptionType.USER, "user", "Пользователь для бана", true)
|
||||
.addOption(OptionType.STRING, "reason", "Причина бана", false)
|
||||
.addOption(OptionType.INTEGER, "delete_days", "Удалить сообщения за N дней (0-7)", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ban";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Банит пользователя на сервере";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package creator.loki.command.slash;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
public class ClearSlashCommand implements SlashCommand {
|
||||
|
||||
@Override
|
||||
public void execute(SlashCommandInteractionEvent event) {
|
||||
if (event.getGuild() == null) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member author = event.getMember();
|
||||
if (author == null || !author.hasPermission(Permission.MESSAGE_MANAGE)) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "У вас нет прав на управление сообщениями")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event.getGuild().getSelfMember().hasPermission(Permission.MESSAGE_MANAGE)) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "У бота нет прав на управление сообщениями")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
int amount = event.getOption("amount").getAsInt();
|
||||
|
||||
if (amount < 1 || amount > 100) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Количество должно быть от 1 до 100")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
event.deferReply(true).queue();
|
||||
|
||||
TextChannel channel = event.getChannel().asTextChannel();
|
||||
channel.getHistory().retrievePast(amount).queue(
|
||||
messages -> {
|
||||
channel.purgeMessages(messages);
|
||||
event.getHook().editOriginalEmbeds(
|
||||
EmbedTemplates.success(
|
||||
"Сообщения удалены",
|
||||
"Удалено сообщений: " + messages.size()
|
||||
)
|
||||
).queue();
|
||||
},
|
||||
error -> event.getHook().editOriginalEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Не удалось удалить сообщения: " + error.getMessage())
|
||||
).queue()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandData getCommandData() {
|
||||
return Commands.slash("clear", "Удаляет указанное количество сообщений")
|
||||
.addOption(OptionType.INTEGER, "amount", "Количество сообщений (1-100)", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "clear";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Удаляет указанное количество сообщений";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package creator.loki.command.slash;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Random;
|
||||
|
||||
public class CoinSlashCommand implements SlashCommand {
|
||||
private static final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public void execute(SlashCommandInteractionEvent event) {
|
||||
boolean isHeads = random.nextBoolean();
|
||||
String result = isHeads ? "Орёл 🦅" : "Решка 🪙";
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("🪙 Подбрасывание монетки")
|
||||
.setDescription("Результат: **" + result + "**")
|
||||
.setColor(isHeads ? new Color(255, 215, 0) : new Color(192, 192, 192));
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandData getCommandData() {
|
||||
return Commands.slash("coin", "Подбросить монетку");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "coin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Подбросить монетку";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package creator.loki.command.slash;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Random;
|
||||
|
||||
public class DiceSlashCommand implements SlashCommand {
|
||||
private static final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public void execute(SlashCommandInteractionEvent event) {
|
||||
int result = random.nextInt(6) + 1;
|
||||
String emoji = getDiceEmoji(result);
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("🎲 Бросок кубика")
|
||||
.setDescription("Выпало: **" + result + "** " + emoji)
|
||||
.setColor(new Color(88, 101, 242));
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
}
|
||||
|
||||
private String getDiceEmoji(int number) {
|
||||
return switch (number) {
|
||||
case 1 -> "⚀";
|
||||
case 2 -> "⚁";
|
||||
case 3 -> "⚂";
|
||||
case 4 -> "⚃";
|
||||
case 5 -> "⚄";
|
||||
case 6 -> "⚅";
|
||||
default -> "🎲";
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandData getCommandData() {
|
||||
return Commands.slash("dice", "Бросить кубик");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "dice";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Бросить кубик";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package creator.loki.command.slash;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Random;
|
||||
|
||||
public class EightBallSlashCommand implements SlashCommand {
|
||||
private static final Random random = new Random();
|
||||
private static final String[] ANSWERS = {
|
||||
"Бесспорно ✅",
|
||||
"Предрешено ✅",
|
||||
"Никаких сомнений ✅",
|
||||
"Определённо да ✅",
|
||||
"Можешь быть уверен в этом ✅",
|
||||
"Мне кажется — да ✅",
|
||||
"Вероятнее всего ✅",
|
||||
"Хорошие перспективы ✅",
|
||||
"Знаки говорят — да ✅",
|
||||
"Да ✅",
|
||||
"Пока не ясно 🤔",
|
||||
"Спроси позже 🤔",
|
||||
"Лучше не рассказывать 🤔",
|
||||
"Сейчас нельзя предсказать 🤔",
|
||||
"Сконцентрируйся и спроси опять 🤔",
|
||||
"Даже не думай ❌",
|
||||
"Мой ответ — нет ❌",
|
||||
"По моим данным — нет ❌",
|
||||
"Перспективы не очень хорошие ❌",
|
||||
"Весьма сомнительно ❌"
|
||||
};
|
||||
|
||||
@Override
|
||||
public void execute(SlashCommandInteractionEvent event) {
|
||||
var questionOption = event.getOption("question");
|
||||
if (questionOption == null) {
|
||||
event.reply("Задайте вопрос!").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String question = questionOption.getAsString();
|
||||
String answer = ANSWERS[random.nextInt(ANSWERS.length)];
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("🎱 Магический шар")
|
||||
.addField("Вопрос", question, false)
|
||||
.addField("Ответ", answer, false)
|
||||
.setColor(new Color(88, 101, 242));
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandData getCommandData() {
|
||||
return Commands.slash("8ball", "Задать вопрос магическому шару")
|
||||
.addOption(OptionType.STRING, "question", "Ваш вопрос", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "8ball";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Задать вопрос магическому шару";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package creator.loki.command.slash;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class HugSlashCommand implements SlashCommand {
|
||||
|
||||
@Override
|
||||
public void execute(SlashCommandInteractionEvent event) {
|
||||
var targetOption = event.getOption("user");
|
||||
if (targetOption == null) {
|
||||
event.reply("Укажите пользователя!").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
var target = targetOption.getAsUser();
|
||||
var author = event.getUser();
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setDescription(author.getAsMention() + " обнимает " + target.getAsMention() + " 🤗")
|
||||
.setColor(new Color(255, 182, 193))
|
||||
.setImage("https://media.tenor.com/images/hug.gif");
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandData getCommandData() {
|
||||
return Commands.slash("hug", "Обнять пользователя")
|
||||
.addOption(OptionType.USER, "user", "Кого обнять", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "hug";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Обнять пользователя";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
package creator.loki.command.slash;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
public class KickSlashCommand implements SlashCommand {
|
||||
|
||||
@Override
|
||||
public void execute(SlashCommandInteractionEvent event) {
|
||||
if (event.getGuild() == null) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member author = event.getMember();
|
||||
if (author == null || !author.hasPermission(Permission.KICK_MEMBERS)) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "У вас нет прав на кик пользователей")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event.getGuild().getSelfMember().hasPermission(Permission.KICK_MEMBERS)) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "У бота нет прав на кик пользователей")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = event.getOption("user").getAsMember();
|
||||
if (target == null) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Пользователь не найден")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.getId().equals(author.getId())) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Вы не можете кикнуть самого себя")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.getUser().isBot() && target.getId().equals(event.getGuild().getSelfMember().getId())) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Я не могу кикнуть самого себя")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!author.canInteract(target)) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "Вы не можете кикнуть этого пользователя")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event.getGuild().getSelfMember().canInteract(target)) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Недостаточно прав", "Я не могу кикнуть этого пользователя")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String reason = event.getOption("reason") != null
|
||||
? event.getOption("reason").getAsString()
|
||||
: "Причина не указана";
|
||||
|
||||
event.deferReply().queue();
|
||||
|
||||
event.getGuild().kick(target)
|
||||
.reason(reason)
|
||||
.queue(
|
||||
success -> event.getHook().editOriginalEmbeds(
|
||||
EmbedTemplates.success(
|
||||
"Пользователь кикнут",
|
||||
"**Пользователь:** " + target.getUser().getAsTag() + "\n" +
|
||||
"**Причина:** " + reason + "\n" +
|
||||
"**Модератор:** " + author.getAsMention()
|
||||
)
|
||||
).queue(),
|
||||
error -> event.getHook().editOriginalEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Не удалось кикнуть: " + error.getMessage())
|
||||
).queue()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandData getCommandData() {
|
||||
return Commands.slash("kick", "Кикает пользователя с сервера")
|
||||
.addOption(OptionType.USER, "user", "Пользователь для кика", true)
|
||||
.addOption(OptionType.STRING, "reason", "Причина кика", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "kick";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Кикает пользователя с сервера";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package creator.loki.command.slash;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import creator.loki.metrics.MetricsCollector;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Slash команда /metrics
|
||||
*/
|
||||
public class MetricsSlashCommand implements SlashCommand {
|
||||
private final MetricsCollector metrics;
|
||||
|
||||
public MetricsSlashCommand(MetricsCollector metrics) {
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(SlashCommandInteractionEvent event) {
|
||||
// Только администраторы
|
||||
if (event.getMember() == null || !event.getMember().hasPermission(Permission.ADMINISTRATOR)) {
|
||||
event.reply("❌ Эта команда доступна только администраторам.")
|
||||
.setEphemeral(true)
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String stats = metrics.getFullStats();
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("📊 Метрики бота")
|
||||
.setDescription("```\n" + stats + "\n```")
|
||||
.setColor(new Color(88, 101, 242))
|
||||
.setFooter("Slash команда");
|
||||
|
||||
event.replyEmbeds(embed.build()).setEphemeral(true).queue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandData getCommandData() {
|
||||
return Commands.slash("metrics", "Показывает метрики и статистику бота (только для админов)");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "metrics";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показывает метрики и статистику бота";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package creator.loki.command.slash;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Slash команда /ping
|
||||
*/
|
||||
public class PingSlashCommand implements SlashCommand {
|
||||
|
||||
@Override
|
||||
public void execute(SlashCommandInteractionEvent event) {
|
||||
JDA jda = event.getJDA();
|
||||
long gatewayPing = jda.getGatewayPing();
|
||||
|
||||
event.deferReply().queue(hook -> {
|
||||
long restPing = hook.getInteraction().getJDA().getRestPing().complete();
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("🏓 Pong!")
|
||||
.setColor(new Color(88, 101, 242))
|
||||
.addField("Gateway Ping", gatewayPing + " ms", true)
|
||||
.addField("REST Ping", restPing + " ms", true)
|
||||
.setFooter("Slash команда");
|
||||
|
||||
hook.editOriginalEmbeds(embed.build()).queue();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandData getCommandData() {
|
||||
return Commands.slash("ping", "Проверяет задержку бота");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ping";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Проверяет задержку бота";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package creator.loki.command.slash;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.level.LevelSystem;
|
||||
import creator.loki.profile.ProfileData;
|
||||
import creator.loki.profile.ProfileService;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Slash команда /profile
|
||||
*/
|
||||
public class ProfileSlashCommand implements SlashCommand {
|
||||
private final ProfileService profileService;
|
||||
|
||||
public ProfileSlashCommand(JsonDataStore dataStore) {
|
||||
this.profileService = new ProfileService(dataStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(SlashCommandInteractionEvent event) {
|
||||
Member targetMember = event.getOption("user") != null
|
||||
? event.getOption("user").getAsMember()
|
||||
: event.getMember();
|
||||
|
||||
if (targetMember == null) {
|
||||
event.reply("❌ Не удалось получить информацию о пользователе.")
|
||||
.setEphemeral(true)
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
ProfileData profile = profileService.getProfileData(targetMember, event.getGuild());
|
||||
|
||||
// Создаём прогресс-бар
|
||||
int progress = (int) ((double) profile.getCurrentXp() / profile.getNextLevelXp() * 20);
|
||||
String progressBar = "▰".repeat(progress) + "▱".repeat(20 - progress);
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("📊 Профиль " + profile.getUsername())
|
||||
.setThumbnail(profile.getAvatarUrl())
|
||||
.setColor(new Color(138, 80, 255))
|
||||
.addField("🎯 Уровень", String.valueOf(profile.getLevel()), true)
|
||||
.addField("⭐ Ранг", "#" + profile.getRank(), true)
|
||||
.addField("💬 Сообщений", String.valueOf(profile.getMessagesCount()), true)
|
||||
.addField("🎤 Голос (мин)", String.valueOf(profile.getVoiceMinutes()), true)
|
||||
.addField("👍 Репутация", String.valueOf(profile.getReputation()), true)
|
||||
.addField("📈 Прогресс",
|
||||
progressBar + "\n" + profile.getCurrentXp() + " / " + profile.getNextLevelXp() + " XP",
|
||||
false)
|
||||
.setFooter("Запросил: " + event.getUser().getName());
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandData getCommandData() {
|
||||
return Commands.slash("profile", "Показывает профиль пользователя")
|
||||
.addOption(OptionType.USER, "user", "Пользователь (по умолчанию - вы)", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "profile";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показывает профиль пользователя";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package creator.loki.command.slash;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Map;
|
||||
|
||||
public class RankSlashCommand implements SlashCommand {
|
||||
private final JsonDataStore dataStore;
|
||||
|
||||
public RankSlashCommand(JsonDataStore dataStore) {
|
||||
this.dataStore = dataStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(SlashCommandInteractionEvent event) {
|
||||
String userId = event.getUser().getId();
|
||||
String levelKey = "level_" + userId;
|
||||
String xpKey = "xp_" + userId;
|
||||
|
||||
Integer level = dataStore.getData(levelKey, Integer.class);
|
||||
Integer xp = dataStore.getData(xpKey, Integer.class);
|
||||
if (level == null) level = 1;
|
||||
if (xp == null) xp = 0;
|
||||
|
||||
int xpNeeded = calculateXpForNextLevel(level);
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("📊 Ваш ранг")
|
||||
.setDescription(event.getUser().getAsMention())
|
||||
.addField("Уровень", String.valueOf(level), true)
|
||||
.addField("Опыт", xp + " / " + xpNeeded, true)
|
||||
.setColor(new Color(88, 101, 242))
|
||||
.setThumbnail(event.getUser().getAvatarUrl());
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
}
|
||||
|
||||
private int calculateXpForNextLevel(int level) {
|
||||
return 100 * level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandData getCommandData() {
|
||||
return Commands.slash("rank", "Показать ваш текущий ранг и уровень");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "rank";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показать ваш текущий ранг и уровень";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package creator.loki.command.slash;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class RepSlashCommand implements SlashCommand {
|
||||
private final JsonDataStore dataStore;
|
||||
|
||||
public RepSlashCommand(JsonDataStore dataStore) {
|
||||
this.dataStore = dataStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(SlashCommandInteractionEvent event) {
|
||||
var targetOption = event.getOption("user");
|
||||
if (targetOption == null) {
|
||||
event.reply("Укажите пользователя!").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
var target = targetOption.getAsUser();
|
||||
var author = event.getUser();
|
||||
|
||||
if (target.getId().equals(author.getId())) {
|
||||
event.reply("Вы не можете дать репутацию самому себе!").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.isBot()) {
|
||||
event.reply("Нельзя давать репутацию ботам!").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String key = "rep_" + target.getId();
|
||||
Integer currentRep = dataStore.getData(key, Integer.class);
|
||||
if (currentRep == null) currentRep = 0;
|
||||
dataStore.setData(key, currentRep + 1);
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("✨ Репутация")
|
||||
.setDescription(author.getAsMention() + " дал репутацию " + target.getAsMention())
|
||||
.addField("Новая репутация", String.valueOf(currentRep + 1), false)
|
||||
.setColor(new Color(0, 255, 0));
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandData getCommandData() {
|
||||
return Commands.slash("rep", "Дать репутацию пользователю")
|
||||
.addOption(OptionType.USER, "user", "Кому дать репутацию", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "rep";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Дать репутацию пользователю";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package creator.loki.command.slash;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Random;
|
||||
|
||||
public class ShipSlashCommand implements SlashCommand {
|
||||
private static final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public void execute(SlashCommandInteractionEvent event) {
|
||||
var user1Option = event.getOption("user1");
|
||||
var user2Option = event.getOption("user2");
|
||||
|
||||
if (user1Option == null || user2Option == null) {
|
||||
event.reply("Укажите двух пользователей!").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
var user1 = user1Option.getAsUser();
|
||||
var user2 = user2Option.getAsUser();
|
||||
|
||||
int percentage = random.nextInt(101);
|
||||
String hearts = getHearts(percentage);
|
||||
String comment = getComment(percentage);
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("💕 Ship-метр")
|
||||
.setDescription(
|
||||
user1.getAsMention() + " ❤️ " + user2.getAsMention() + "\n\n" +
|
||||
hearts + " **" + percentage + "%**\n\n" +
|
||||
comment
|
||||
)
|
||||
.setColor(getColor(percentage));
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
}
|
||||
|
||||
private String getHearts(int percentage) {
|
||||
int hearts = percentage / 10;
|
||||
return "❤️".repeat(hearts) + "🖤".repeat(10 - hearts);
|
||||
}
|
||||
|
||||
private String getComment(int percentage) {
|
||||
if (percentage >= 90) return "Идеальная пара! 💖";
|
||||
if (percentage >= 70) return "Отличная совместимость! 💕";
|
||||
if (percentage >= 50) return "Неплохие шансы! 💗";
|
||||
if (percentage >= 30) return "Есть потенциал... 💔";
|
||||
return "Лучше остаться друзьями 💔";
|
||||
}
|
||||
|
||||
private Color getColor(int percentage) {
|
||||
if (percentage >= 70) return new Color(255, 105, 180);
|
||||
if (percentage >= 40) return new Color(255, 182, 193);
|
||||
return new Color(128, 128, 128);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandData getCommandData() {
|
||||
return Commands.slash("ship", "Проверить совместимость двух пользователей")
|
||||
.addOption(OptionType.USER, "user1", "Первый пользователь", true)
|
||||
.addOption(OptionType.USER, "user2", "Второй пользователь", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ship";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Проверить совместимость двух пользователей";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package creator.loki.command.slash;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import creator.loki.audio.AudioManager;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import net.dv8tion.jda.api.entities.GuildVoiceState;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
public class TtsSlashCommand implements SlashCommand {
|
||||
private final AudioManager audioManager;
|
||||
|
||||
public TtsSlashCommand(AudioManager audioManager) {
|
||||
this.audioManager = audioManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(SlashCommandInteractionEvent event) {
|
||||
if (event.getGuild() == null) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member member = event.getMember();
|
||||
if (member == null) {
|
||||
event.reply("Ошибка получения информации о пользователе").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
GuildVoiceState voiceState = member.getVoiceState();
|
||||
if (voiceState == null || !voiceState.inAudioChannel()) {
|
||||
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Вы должны находиться в голосовом канале")).setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String text = event.getOption("text").getAsString();
|
||||
if (text.length() > 200) {
|
||||
event.reply("Текст слишком длинный! Максимум 200 символов.").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
event.reply("🔊 Воспроизведение TTS...").queue();
|
||||
audioManager.enqueueTts(event.getGuild(), text, event.getChannel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandData getCommandData() {
|
||||
return Commands.slash("tts", "Озвучить текст в голосовом канале")
|
||||
.addOption(OptionType.STRING, "text", "Текст для озвучки (макс. 200 символов)", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "tts";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Озвучить текст в голосовом канале";
|
||||
}
|
||||
}
|
||||
25
src/main/java/creator/loki/config/BotConfig.java
Normal file
25
src/main/java/creator/loki/config/BotConfig.java
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package creator.loki.config;
|
||||
|
||||
public class BotConfig {
|
||||
private final String token;
|
||||
private final String prefix;
|
||||
private final String activity;
|
||||
|
||||
public BotConfig(String token, String prefix, String activity) {
|
||||
this.token = token;
|
||||
this.prefix = prefix;
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public String getActivity() {
|
||||
return activity;
|
||||
}
|
||||
}
|
||||
39
src/main/java/creator/loki/config/ConfigLoader.java
Normal file
39
src/main/java/creator/loki/config/ConfigLoader.java
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package creator.loki.config;
|
||||
|
||||
import io.github.cdimascio.dotenv.Dotenv;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class ConfigLoader {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConfigLoader.class);
|
||||
|
||||
private ConfigLoader() {
|
||||
// Запрет создания экземпляров
|
||||
}
|
||||
|
||||
public static BotConfig load() {
|
||||
Dotenv dotenv = Dotenv.configure()
|
||||
.ignoreIfMissing()
|
||||
.load();
|
||||
|
||||
String token = dotenv.get("DISCORD_BOT_TOKEN");
|
||||
if (token == null || token.isBlank()) {
|
||||
throw new IllegalStateException(
|
||||
"Токен бота не найден! Установите переменную окружения DISCORD_BOT_TOKEN или создайте .env файл"
|
||||
);
|
||||
}
|
||||
|
||||
String prefix = dotenv.get("BOT_PREFIX");
|
||||
if (prefix == null || prefix.isBlank()) {
|
||||
prefix = "!";
|
||||
}
|
||||
|
||||
String activity = dotenv.get("BOT_ACTIVITY");
|
||||
if (activity == null || activity.isBlank()) {
|
||||
activity = "Используй !help";
|
||||
}
|
||||
|
||||
logger.info("Конфигурация загружена успешно");
|
||||
return new BotConfig(token, prefix, activity);
|
||||
}
|
||||
}
|
||||
232
src/main/java/creator/loki/core/BotInitializer.java
Normal file
232
src/main/java/creator/loki/core/BotInitializer.java
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
package creator.loki.core;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import creator.loki.audio.AudioManager;
|
||||
import creator.loki.command.impl.setup.CounterSetupCommand;
|
||||
import creator.loki.command.impl.setup.LogSetupCommand;
|
||||
import creator.loki.command.impl.setup.VoiceSetupCommand;
|
||||
import creator.loki.command.impl.stats.LeaderboardButtonListener;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.counter.CounterManager;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.database.ConfigRepository;
|
||||
import creator.loki.database.Database;
|
||||
import creator.loki.level.LevelSystem;
|
||||
import creator.loki.listener.*;
|
||||
import creator.loki.metrics.MetricsCollector;
|
||||
import creator.loki.profile.ProfileService;
|
||||
import creator.loki.role.CustomRoleManager;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import creator.loki.util.GlobalRateLimiter;
|
||||
import creator.loki.voice.VoiceChannelManager;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDABuilder;
|
||||
import net.dv8tion.jda.api.OnlineStatus;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import net.dv8tion.jda.api.requests.GatewayIntent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class BotInitializer {
|
||||
private static final Logger logger = LoggerFactory.getLogger(BotInitializer.class);
|
||||
|
||||
private final BotConfig config;
|
||||
private final CommandRegistry registry;
|
||||
private final SlashCommandRegistry slashRegistry;
|
||||
private final EventDispatcher dispatcher;
|
||||
private final JsonDataStore dataStore;
|
||||
private final CustomRoleManager roleManager;
|
||||
private final CooldownManager cooldownManager;
|
||||
private final GlobalRateLimiter rateLimiter;
|
||||
private final MetricsCollector metrics;
|
||||
private final AudioManager audioManager;
|
||||
private final LevelSystem levelSystem;
|
||||
private final VoiceTracker voiceTracker;
|
||||
private final ProfileService profileService;
|
||||
private final VoiceChannelManager voiceChannelManager;
|
||||
private final CounterManager counterManager;
|
||||
private final VoiceChannelListener voiceChannelListener;
|
||||
private final MemberCounterListener memberCounterListener;
|
||||
private final Database database;
|
||||
private final ConfigRepository configRepo;
|
||||
private final VoiceSetupCommand voiceSetupCommand;
|
||||
private final CounterSetupCommand counterSetupCommand;
|
||||
private final LogSetupCommand logSetupCommand;
|
||||
private final VoiceButtonHandler voiceButtonHandler;
|
||||
private final ModerationLogger moderationLogger;
|
||||
private final LevelUpNotifier levelUpNotifier;
|
||||
|
||||
@Inject
|
||||
public BotInitializer(
|
||||
BotConfig config,
|
||||
CommandRegistry registry,
|
||||
SlashCommandRegistry slashRegistry,
|
||||
EventDispatcher dispatcher,
|
||||
JsonDataStore dataStore,
|
||||
CustomRoleManager roleManager,
|
||||
CooldownManager cooldownManager,
|
||||
GlobalRateLimiter rateLimiter,
|
||||
MetricsCollector metrics,
|
||||
AudioManager audioManager,
|
||||
LevelSystem levelSystem,
|
||||
VoiceTracker voiceTracker,
|
||||
ProfileService profileService,
|
||||
VoiceChannelManager voiceChannelManager,
|
||||
CounterManager counterManager,
|
||||
Database database,
|
||||
ConfigRepository configRepo,
|
||||
LevelUpNotifier levelUpNotifier
|
||||
) {
|
||||
this.config = config;
|
||||
this.registry = registry;
|
||||
this.slashRegistry = slashRegistry;
|
||||
this.dispatcher = dispatcher;
|
||||
this.dataStore = dataStore;
|
||||
this.roleManager = roleManager;
|
||||
this.cooldownManager = cooldownManager;
|
||||
this.rateLimiter = rateLimiter;
|
||||
this.metrics = metrics;
|
||||
this.audioManager = audioManager;
|
||||
this.levelSystem = levelSystem;
|
||||
this.voiceTracker = voiceTracker;
|
||||
this.profileService = profileService;
|
||||
this.voiceChannelManager = voiceChannelManager;
|
||||
this.counterManager = counterManager;
|
||||
this.database = database;
|
||||
this.configRepo = configRepo;
|
||||
this.levelUpNotifier = levelUpNotifier;
|
||||
this.voiceChannelListener = new VoiceChannelListener(voiceChannelManager);
|
||||
this.memberCounterListener = new MemberCounterListener(counterManager);
|
||||
this.voiceSetupCommand = new VoiceSetupCommand(configRepo);
|
||||
this.counterSetupCommand = new CounterSetupCommand(configRepo);
|
||||
this.logSetupCommand = new LogSetupCommand(configRepo);
|
||||
this.voiceButtonHandler = new VoiceButtonHandler(voiceChannelManager);
|
||||
this.moderationLogger = new ModerationLogger(configRepo);
|
||||
}
|
||||
|
||||
public JDA start() {
|
||||
logger.info("Инициализация бота...");
|
||||
|
||||
// Регистрируем команды через специализированные регистраторы
|
||||
registerCommands();
|
||||
registerSlashCommands();
|
||||
|
||||
// Создаём JDA
|
||||
JDA jda = createJDA();
|
||||
|
||||
// Регистрируем slash команды в Discord
|
||||
registerSlashCommandsInDiscord(jda);
|
||||
|
||||
// Настраиваем graceful shutdown
|
||||
setupShutdownHook(jda);
|
||||
|
||||
return jda;
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
PrefixCommandRegistrar registrar = new PrefixCommandRegistrar(
|
||||
registry, config, dataStore, roleManager, cooldownManager,
|
||||
audioManager, levelSystem, metrics
|
||||
);
|
||||
registrar.registerAll();
|
||||
}
|
||||
|
||||
private void registerSlashCommands() {
|
||||
SlashCommandRegistrar registrar = new SlashCommandRegistrar(
|
||||
slashRegistry, dataStore, metrics, audioManager
|
||||
);
|
||||
registrar.registerAll();
|
||||
}
|
||||
|
||||
private JDA createJDA() {
|
||||
return JDABuilder.createDefault(config.getToken())
|
||||
.setStatus(OnlineStatus.ONLINE)
|
||||
.setActivity(Activity.playing(config.getActivity()))
|
||||
.enableIntents(
|
||||
GatewayIntent.GUILD_MESSAGES,
|
||||
GatewayIntent.MESSAGE_CONTENT,
|
||||
GatewayIntent.GUILD_MEMBERS,
|
||||
GatewayIntent.GUILD_VOICE_STATES
|
||||
)
|
||||
.addEventListeners(
|
||||
new ReadyListener(),
|
||||
new MessageListener(dispatcher, dataStore, levelSystem),
|
||||
new SlashCommandListener(slashRegistry, rateLimiter, metrics),
|
||||
voiceTracker,
|
||||
voiceChannelListener,
|
||||
memberCounterListener,
|
||||
voiceSetupCommand,
|
||||
counterSetupCommand,
|
||||
logSetupCommand,
|
||||
voiceButtonHandler,
|
||||
moderationLogger,
|
||||
new LeaderboardButtonListener(levelSystem)
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void registerSlashCommandsInDiscord(JDA jda) {
|
||||
try {
|
||||
jda.awaitReady();
|
||||
slashRegistry.registerCommandsInDiscord(jda);
|
||||
|
||||
// Регистрируем setup команды
|
||||
VoiceSetupCommand.register(jda);
|
||||
CounterSetupCommand.register(jda);
|
||||
LogSetupCommand.register(jda);
|
||||
|
||||
// Запускаем планировщик счетчика участников
|
||||
if (!jda.getGuilds().isEmpty()) {
|
||||
memberCounterListener.startScheduler(jda.getGuilds().get(0));
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("Прервано ожидание готовности JDA", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupShutdownHook(JDA jda) {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
logger.info("========================================");
|
||||
logger.info("Получен сигнал завершения работы бота...");
|
||||
logger.info("========================================");
|
||||
|
||||
shutdownComponents(jda);
|
||||
|
||||
logger.info("========================================");
|
||||
logger.info("Бот успешно завершён!");
|
||||
logger.info("========================================");
|
||||
}, "ShutdownHook"));
|
||||
}
|
||||
|
||||
private void shutdownComponents(JDA jda) {
|
||||
try {
|
||||
logger.info("Сохранение данных...");
|
||||
dataStore.flush();
|
||||
|
||||
logger.info("Остановка менеджеров...");
|
||||
cooldownManager.shutdown();
|
||||
rateLimiter.shutdown();
|
||||
voiceTracker.shutdown();
|
||||
memberCounterListener.shutdown();
|
||||
dispatcher.shutdown();
|
||||
audioManager.shutdown();
|
||||
|
||||
logger.info("Завершение JsonDataStore...");
|
||||
dataStore.shutdown();
|
||||
|
||||
logger.info("Закрытие базы данных...");
|
||||
database.close();
|
||||
|
||||
logger.info("Отключение JDA...");
|
||||
jda.shutdown();
|
||||
|
||||
if (!jda.awaitShutdown(java.time.Duration.ofSeconds(10))) {
|
||||
logger.warn("JDA не завершился за 10 секунд, принудительная остановка");
|
||||
jda.shutdownNow();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка при завершении работы бота", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/main/java/creator/loki/core/CommandRegistry.java
Normal file
32
src/main/java/creator/loki/core/CommandRegistry.java
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package creator.loki.core;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CommandRegistry {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CommandRegistry.class);
|
||||
private final Map<String, Command> commands = new HashMap<>();
|
||||
|
||||
public void register(Command command) {
|
||||
commands.put(command.getName().toLowerCase(), command);
|
||||
logger.debug("Зарегистрирована команда: {}", command.getName());
|
||||
}
|
||||
|
||||
public Command getCommand(String name) {
|
||||
return commands.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
public Collection<Command> getAllCommands() {
|
||||
return Collections.unmodifiableCollection(commands.values());
|
||||
}
|
||||
|
||||
public int getCommandCount() {
|
||||
return commands.size();
|
||||
}
|
||||
}
|
||||
129
src/main/java/creator/loki/core/EventDispatcher.java
Normal file
129
src/main/java/creator/loki/core/EventDispatcher.java
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package creator.loki.core;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.exception.CommandExceptionHandler;
|
||||
import creator.loki.metrics.MetricsCollector;
|
||||
import creator.loki.util.GlobalRateLimiter;
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class EventDispatcher {
|
||||
private static final Logger logger = LoggerFactory.getLogger(EventDispatcher.class);
|
||||
private final BotConfig config;
|
||||
private final CommandRegistry registry;
|
||||
private final ExecutorService executorService;
|
||||
private final GlobalRateLimiter rateLimiter;
|
||||
private final MetricsCollector metrics;
|
||||
|
||||
public EventDispatcher(BotConfig config, CommandRegistry registry, GlobalRateLimiter rateLimiter, MetricsCollector metrics) {
|
||||
this.config = config;
|
||||
this.registry = registry;
|
||||
this.executorService = Executors.newCachedThreadPool();
|
||||
this.rateLimiter = rateLimiter;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
public void handleMessage(MessageReceivedEvent event) {
|
||||
// Игнорируем ботов
|
||||
if (event.getAuthor().isBot()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String content = event.getMessage().getContentRaw();
|
||||
if (!content.startsWith(config.getPrefix())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Парсим команду и аргументы
|
||||
String[] parts = content.substring(config.getPrefix().length()).trim().split("\\s+");
|
||||
if (parts.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String commandName = parts[0].toLowerCase();
|
||||
List<String> args = parts.length > 1
|
||||
? Arrays.asList(Arrays.copyOfRange(parts, 1, parts.length))
|
||||
: List.of();
|
||||
|
||||
Command command = registry.getCommand(commandName);
|
||||
if (command == null) {
|
||||
event.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Команда не найдена",
|
||||
"Команда `" + commandName + "` не существует. Используй `" + config.getPrefix() + "help`"
|
||||
)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем глобальный rate limit
|
||||
String userId = event.getAuthor().getId();
|
||||
String guildId = event.getGuild() != null ? event.getGuild().getId() : "DM";
|
||||
|
||||
if (!rateLimiter.allow(userId, guildId)) {
|
||||
long waitTime = rateLimiter.getUserWaitTime(userId);
|
||||
event.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"⏱️ Слишком быстро!",
|
||||
"Ты отправляешь команды слишком часто. Подожди " + waitTime + " секунд."
|
||||
)
|
||||
).queue();
|
||||
logger.warn("Rate limit для пользователя {} в гильдии {}", userId, guildId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Выполняем команду асинхронно
|
||||
executorService.submit(() -> {
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
CommandContext context = new CommandContext(
|
||||
event.getMessage(),
|
||||
args,
|
||||
event.getChannel(),
|
||||
event.getMember(),
|
||||
event.getGuild()
|
||||
);
|
||||
command.execute(context);
|
||||
|
||||
// Записываем метрики
|
||||
long executionTime = System.currentTimeMillis() - startTime;
|
||||
metrics.recordCommand(commandName, executionTime);
|
||||
|
||||
logger.info("Выполнена команда: {} от {} за {} мс",
|
||||
commandName, event.getAuthor().getName(), executionTime);
|
||||
} catch (Exception e) {
|
||||
// Записываем ошибку в метрики
|
||||
metrics.recordError(commandName);
|
||||
|
||||
// Используем централизованный обработчик ошибок
|
||||
CommandExceptionHandler.handle(e, event.getChannel(), commandName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
logger.info("Завершение работы EventDispatcher...");
|
||||
executorService.shutdown();
|
||||
try {
|
||||
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
|
||||
logger.warn("ExecutorService не завершился за 10 секунд, принудительная остановка");
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("Прервано ожидание завершения ExecutorService", e);
|
||||
executorService.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
logger.info("EventDispatcher завершён");
|
||||
}
|
||||
}
|
||||
128
src/main/java/creator/loki/core/PrefixCommandRegistrar.java
Normal file
128
src/main/java/creator/loki/core/PrefixCommandRegistrar.java
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
package creator.loki.core;
|
||||
|
||||
import creator.loki.audio.AudioManager;
|
||||
import creator.loki.command.impl.entertainment.games.*;
|
||||
import creator.loki.command.impl.entertainment.interaction.*;
|
||||
import creator.loki.command.impl.moderation.*;
|
||||
import creator.loki.command.impl.roles.*;
|
||||
import creator.loki.command.impl.stats.*;
|
||||
import creator.loki.command.impl.utility.*;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.level.LevelSystem;
|
||||
import creator.loki.metrics.MetricsCollector;
|
||||
import creator.loki.profile.ProfileService;
|
||||
import creator.loki.role.CustomRoleManager;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Регистратор префиксных команд (!) - Single Responsibility
|
||||
*/
|
||||
public class PrefixCommandRegistrar {
|
||||
private static final Logger logger = LoggerFactory.getLogger(PrefixCommandRegistrar.class);
|
||||
|
||||
private final CommandRegistry registry;
|
||||
private final BotConfig config;
|
||||
private final JsonDataStore dataStore;
|
||||
private final CustomRoleManager roleManager;
|
||||
private final CooldownManager cooldownManager;
|
||||
private final AudioManager audioManager;
|
||||
private final LevelSystem levelSystem;
|
||||
private final MetricsCollector metrics;
|
||||
|
||||
public PrefixCommandRegistrar(
|
||||
CommandRegistry registry,
|
||||
BotConfig config,
|
||||
JsonDataStore dataStore,
|
||||
CustomRoleManager roleManager,
|
||||
CooldownManager cooldownManager,
|
||||
AudioManager audioManager,
|
||||
LevelSystem levelSystem,
|
||||
MetricsCollector metrics
|
||||
) {
|
||||
this.registry = registry;
|
||||
this.config = config;
|
||||
this.dataStore = dataStore;
|
||||
this.roleManager = roleManager;
|
||||
this.cooldownManager = cooldownManager;
|
||||
this.audioManager = audioManager;
|
||||
this.levelSystem = levelSystem;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
public void registerAll() {
|
||||
registerUtilityCommands();
|
||||
registerRoleCommands();
|
||||
registerInteractionCommands();
|
||||
registerGameCommands();
|
||||
registerStatsCommands();
|
||||
registerModerationCommands();
|
||||
|
||||
logger.info("Зарегистрировано префиксных команд: {}", registry.getCommandCount());
|
||||
}
|
||||
|
||||
private void registerUtilityCommands() {
|
||||
registry.register(new PingCommand());
|
||||
registry.register(new HelpCommand(registry, config));
|
||||
registry.register(new InfoCommand());
|
||||
registry.register(new ClearCommand());
|
||||
registry.register(new ProfileCommand(dataStore));
|
||||
registry.register(new MetricsCommand(metrics));
|
||||
registry.register(new AvatarCommand());
|
||||
registry.register(new PollCommand());
|
||||
registry.register(new SwapCommand());
|
||||
registry.register(new TtsCommand(cooldownManager, audioManager));
|
||||
}
|
||||
|
||||
private void registerRoleCommands() {
|
||||
registry.register(new CreateRoleCommand(roleManager));
|
||||
registry.register(new DeleteRoleCommand(roleManager));
|
||||
registry.register(new MyRolesCommand(roleManager));
|
||||
registry.register(new EditRoleCommand(roleManager));
|
||||
}
|
||||
|
||||
private void registerInteractionCommands() {
|
||||
registry.register(new PatCommand(cooldownManager));
|
||||
registry.register(new HugCommand());
|
||||
registry.register(new KissCommand(cooldownManager));
|
||||
registry.register(new SlapCommand(cooldownManager));
|
||||
registry.register(new TickleCommand(cooldownManager));
|
||||
registry.register(new PokeCommand(cooldownManager));
|
||||
registry.register(new CuddleCommand(cooldownManager));
|
||||
registry.register(new FeedCommand(cooldownManager));
|
||||
}
|
||||
|
||||
private void registerGameCommands() {
|
||||
registry.register(new EightBallCommand());
|
||||
registry.register(new CoinCommand());
|
||||
registry.register(new DiceCommand());
|
||||
registry.register(new MemeCommand());
|
||||
registry.register(new ShipCommand(dataStore));
|
||||
registry.register(new RoastCommand());
|
||||
registry.register(new ComplimentCommand());
|
||||
registry.register(new GlazbobaCommand());
|
||||
registry.register(new IqCommand(cooldownManager));
|
||||
registry.register(new GayCommand(cooldownManager));
|
||||
registry.register(new PpCommand(cooldownManager));
|
||||
registry.register(new RateCommand(cooldownManager));
|
||||
registry.register(new BoykisserCommand(cooldownManager));
|
||||
registry.register(new MustacheCommand(cooldownManager));
|
||||
registry.register(new FatCommand(cooldownManager));
|
||||
}
|
||||
|
||||
private void registerStatsCommands() {
|
||||
registry.register(new RankCommand(levelSystem));
|
||||
registry.register(new NewLeaderboardCommand(levelSystem));
|
||||
registry.register(new RepCommand(dataStore, cooldownManager));
|
||||
registry.register(new MinusRepCommand(dataStore, cooldownManager));
|
||||
registry.register(new RepTopCommand(dataStore));
|
||||
registry.register(new StatsCommand());
|
||||
}
|
||||
|
||||
private void registerModerationCommands() {
|
||||
registry.register(new BanCommand());
|
||||
registry.register(new KickCommand());
|
||||
}
|
||||
}
|
||||
59
src/main/java/creator/loki/core/SlashCommandRegistrar.java
Normal file
59
src/main/java/creator/loki/core/SlashCommandRegistrar.java
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package creator.loki.core;
|
||||
|
||||
import creator.loki.audio.AudioManager;
|
||||
import creator.loki.command.slash.*;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.metrics.MetricsCollector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Регистратор slash команд (/) - Single Responsibility
|
||||
*/
|
||||
public class SlashCommandRegistrar {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SlashCommandRegistrar.class);
|
||||
|
||||
private final creator.loki.core.SlashCommandRegistry registry;
|
||||
private final JsonDataStore dataStore;
|
||||
private final MetricsCollector metrics;
|
||||
private final AudioManager audioManager;
|
||||
|
||||
public SlashCommandRegistrar(
|
||||
creator.loki.core.SlashCommandRegistry registry,
|
||||
JsonDataStore dataStore,
|
||||
MetricsCollector metrics,
|
||||
AudioManager audioManager
|
||||
) {
|
||||
this.registry = registry;
|
||||
this.dataStore = dataStore;
|
||||
this.metrics = metrics;
|
||||
this.audioManager = audioManager;
|
||||
}
|
||||
|
||||
public void registerAll() {
|
||||
// Утилиты
|
||||
registry.register(new PingSlashCommand());
|
||||
registry.register(new AvatarSlashCommand());
|
||||
registry.register(new ProfileSlashCommand(dataStore));
|
||||
registry.register(new MetricsSlashCommand(metrics));
|
||||
registry.register(new TtsSlashCommand(audioManager));
|
||||
|
||||
// Модерация
|
||||
registry.register(new KickSlashCommand());
|
||||
registry.register(new BanSlashCommand());
|
||||
registry.register(new ClearSlashCommand());
|
||||
|
||||
// Статистика
|
||||
registry.register(new RepSlashCommand(dataStore));
|
||||
registry.register(new RankSlashCommand(dataStore));
|
||||
|
||||
// Развлечения
|
||||
registry.register(new HugSlashCommand());
|
||||
registry.register(new ShipSlashCommand());
|
||||
registry.register(new CoinSlashCommand());
|
||||
registry.register(new DiceSlashCommand());
|
||||
registry.register(new EightBallSlashCommand());
|
||||
|
||||
logger.info("Зарегистрировано slash команд: {}", registry.getCommandCount());
|
||||
}
|
||||
}
|
||||
67
src/main/java/creator/loki/core/SlashCommandRegistry.java
Normal file
67
src/main/java/creator/loki/core/SlashCommandRegistry.java
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package creator.loki.core;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Реестр для slash команд
|
||||
*/
|
||||
public class SlashCommandRegistry {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SlashCommandRegistry.class);
|
||||
private final Map<String, SlashCommand> commands = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Регистрирует slash команду
|
||||
*/
|
||||
public void register(SlashCommand command) {
|
||||
commands.put(command.getName().toLowerCase(), command);
|
||||
logger.debug("Зарегистрирована slash команда: {}", command.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает slash команду по имени
|
||||
*/
|
||||
public SlashCommand getCommand(String name) {
|
||||
return commands.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает все slash команды
|
||||
*/
|
||||
public Collection<SlashCommand> getAllCommands() {
|
||||
return Collections.unmodifiableCollection(commands.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает количество slash команд
|
||||
*/
|
||||
public int getCommandCount() {
|
||||
return commands.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Регистрирует все slash команды в Discord
|
||||
*/
|
||||
public void registerCommandsInDiscord(JDA jda) {
|
||||
List<CommandData> commandDataList = new ArrayList<>();
|
||||
|
||||
for (SlashCommand command : commands.values()) {
|
||||
commandDataList.add(command.getCommandData());
|
||||
}
|
||||
|
||||
jda.updateCommands().addCommands(commandDataList).queue(
|
||||
success -> logger.info("Зарегистрировано {} slash команд в Discord", commandDataList.size()),
|
||||
error -> logger.error("Ошибка при регистрации slash команд", error)
|
||||
);
|
||||
}
|
||||
}
|
||||
226
src/main/java/creator/loki/counter/CounterManager.java
Normal file
226
src/main/java/creator/loki/counter/CounterManager.java
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
package creator.loki.counter;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.database.ConfigRepository;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Менеджер счетчика участников
|
||||
*/
|
||||
public class CounterManager {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CounterManager.class);
|
||||
private final JsonDataStore dataStore;
|
||||
private final ConfigRepository configRepo;
|
||||
|
||||
private String counterChannelId;
|
||||
private String format = "【👥】Участники: {count}";
|
||||
|
||||
public CounterManager(JsonDataStore dataStore, ConfigRepository configRepo) {
|
||||
this.dataStore = dataStore;
|
||||
this.configRepo = configRepo;
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузить конфигурацию
|
||||
*/
|
||||
private void loadConfig() {
|
||||
this.counterChannelId = configRepo.get("counter.channel_id");
|
||||
String savedFormat = configRepo.get("counter.format");
|
||||
if (savedFormat != null) {
|
||||
this.format = savedFormat;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить данные счетчика
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> getCounterData() {
|
||||
Type type = new TypeToken<Map<String, Object>>(){}.getType();
|
||||
Map<String, Object> data = dataStore.getData("counter_data", type);
|
||||
return data != null ? data : new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить данные счетчика
|
||||
*/
|
||||
private void saveCounterData(Map<String, Object> data) {
|
||||
dataStore.setData("counter_data", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить канал счетчика
|
||||
*/
|
||||
public void setCounterChannel(String channelId) {
|
||||
configRepo.set("counter.channel_id", channelId);
|
||||
this.counterChannelId = channelId;
|
||||
logger.info("Установлен канал счетчика: {}", channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить формат отображения
|
||||
*/
|
||||
public void setFormat(String format) {
|
||||
configRepo.set("counter.format", format);
|
||||
this.format = format;
|
||||
logger.info("Установлен формат: {}", format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить текущую дневную статистику
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public DailyStats getDailyStats() {
|
||||
Map<String, Object> data = getCounterData();
|
||||
Map<String, Object> statsMap = (Map<String, Object>) data.get("daily_stats");
|
||||
|
||||
if (statsMap == null) {
|
||||
return new DailyStats();
|
||||
}
|
||||
|
||||
DailyStats stats = new DailyStats();
|
||||
stats.setDate((String) statsMap.getOrDefault("date", LocalDate.now().toString()));
|
||||
stats.setJoined(((Number) statsMap.getOrDefault("joined", 0)).intValue());
|
||||
stats.setLeft(((Number) statsMap.getOrDefault("left", 0)).intValue());
|
||||
|
||||
// Если статистика не сегодняшняя, создаем новую
|
||||
if (!stats.isToday()) {
|
||||
// Сохраняем старую в историю
|
||||
saveToHistory(stats);
|
||||
stats = new DailyStats();
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить статистику
|
||||
*/
|
||||
private void saveDailyStats(DailyStats stats) {
|
||||
Map<String, Object> data = getCounterData();
|
||||
|
||||
Map<String, Object> statsMap = new HashMap<>();
|
||||
statsMap.put("date", stats.getDate());
|
||||
statsMap.put("joined", stats.getJoined());
|
||||
statsMap.put("left", stats.getLeft());
|
||||
statsMap.put("difference", stats.getDifference());
|
||||
|
||||
data.put("daily_stats", statsMap);
|
||||
saveCounterData(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить статистику в историю
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void saveToHistory(DailyStats stats) {
|
||||
Map<String, Object> data = getCounterData();
|
||||
List<Map<String, Object>> history = (List<Map<String, Object>>) data.get("history");
|
||||
|
||||
if (history == null) {
|
||||
history = new ArrayList<>();
|
||||
}
|
||||
|
||||
Map<String, Object> statsMap = new HashMap<>();
|
||||
statsMap.put("date", stats.getDate());
|
||||
statsMap.put("joined", stats.getJoined());
|
||||
statsMap.put("left", stats.getLeft());
|
||||
statsMap.put("difference", stats.getDifference());
|
||||
|
||||
history.add(statsMap);
|
||||
|
||||
// Храним только последние 30 дней
|
||||
if (history.size() > 30) {
|
||||
history.remove(0);
|
||||
}
|
||||
|
||||
data.put("history", history);
|
||||
saveCounterData(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Зарегистрировать присоединение участника
|
||||
*/
|
||||
public void recordJoin() {
|
||||
DailyStats stats = getDailyStats();
|
||||
stats.incrementJoined();
|
||||
saveDailyStats(stats);
|
||||
logger.debug("Зарегистрировано присоединение. Всего за день: {}", stats.getJoined());
|
||||
}
|
||||
|
||||
/**
|
||||
* Зарегистрировать выход участника
|
||||
*/
|
||||
public void recordLeave() {
|
||||
DailyStats stats = getDailyStats();
|
||||
stats.incrementLeft();
|
||||
saveDailyStats(stats);
|
||||
logger.debug("Зарегистрирован выход. Всего за день: {}", stats.getLeft());
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить счетчик участников
|
||||
*/
|
||||
public void updateCounter(Guild guild) {
|
||||
if (counterChannelId == null || counterChannelId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
VoiceChannel channel = guild.getVoiceChannelById(counterChannelId);
|
||||
if (channel == null) {
|
||||
logger.warn("Канал счетчика не найден: {}", counterChannelId);
|
||||
return;
|
||||
}
|
||||
|
||||
int memberCount = guild.getMemberCount();
|
||||
String newName = format.replace("{count}", String.valueOf(memberCount));
|
||||
|
||||
// Обновляем название канала только если оно изменилось
|
||||
if (!channel.getName().equals(newName)) {
|
||||
channel.getManager().setName(newName).queue(
|
||||
success -> logger.debug("Обновлен счетчик участников: {}", memberCount),
|
||||
error -> logger.error("Ошибка при обновлении счетчика", error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить историю статистики
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<DailyStats> getHistory(int days) {
|
||||
Map<String, Object> data = getCounterData();
|
||||
List<Map<String, Object>> historyMaps = (List<Map<String, Object>>) data.get("history");
|
||||
|
||||
if (historyMaps == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
List<DailyStats> history = new ArrayList<>();
|
||||
int start = Math.max(0, historyMaps.size() - days);
|
||||
|
||||
for (int i = start; i < historyMaps.size(); i++) {
|
||||
Map<String, Object> statsMap = historyMaps.get(i);
|
||||
DailyStats stats = new DailyStats();
|
||||
stats.setDate((String) statsMap.get("date"));
|
||||
stats.setJoined(((Number) statsMap.get("joined")).intValue());
|
||||
stats.setLeft(((Number) statsMap.get("left")).intValue());
|
||||
history.add(stats);
|
||||
}
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getCounterChannelId() { return counterChannelId; }
|
||||
public String getFormat() { return format; }
|
||||
}
|
||||
66
src/main/java/creator/loki/counter/DailyStats.java
Normal file
66
src/main/java/creator/loki/counter/DailyStats.java
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package creator.loki.counter;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Дневная статистика участников
|
||||
*/
|
||||
public class DailyStats {
|
||||
private String date;
|
||||
private int joined;
|
||||
private int left;
|
||||
private int difference;
|
||||
|
||||
public DailyStats() {
|
||||
this.date = LocalDate.now().toString();
|
||||
this.joined = 0;
|
||||
this.left = 0;
|
||||
this.difference = 0;
|
||||
}
|
||||
|
||||
public DailyStats(String date, int joined, int left) {
|
||||
this.date = date;
|
||||
this.joined = joined;
|
||||
this.left = left;
|
||||
this.difference = joined - left;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getDate() { return date; }
|
||||
public int getJoined() { return joined; }
|
||||
public int getLeft() { return left; }
|
||||
public int getDifference() { return difference; }
|
||||
|
||||
// Setters
|
||||
public void setDate(String date) { this.date = date; }
|
||||
public void setJoined(int joined) {
|
||||
this.joined = joined;
|
||||
updateDifference();
|
||||
}
|
||||
public void setLeft(int left) {
|
||||
this.left = left;
|
||||
updateDifference();
|
||||
}
|
||||
|
||||
// Методы
|
||||
public void incrementJoined() {
|
||||
this.joined++;
|
||||
updateDifference();
|
||||
}
|
||||
|
||||
public void incrementLeft() {
|
||||
this.left++;
|
||||
updateDifference();
|
||||
}
|
||||
|
||||
private void updateDifference() {
|
||||
this.difference = this.joined - this.left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить является ли статистика сегодняшней
|
||||
*/
|
||||
public boolean isToday() {
|
||||
return this.date.equals(LocalDate.now().toString());
|
||||
}
|
||||
}
|
||||
67
src/main/java/creator/loki/database/ConfigRepository.java
Normal file
67
src/main/java/creator/loki/database/ConfigRepository.java
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package creator.loki.database;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Репозиторий для работы с конфигурацией
|
||||
*/
|
||||
public class ConfigRepository {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConfigRepository.class);
|
||||
private final Database database;
|
||||
|
||||
public ConfigRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить значение конфигурации
|
||||
*/
|
||||
public String get(String key) {
|
||||
try (PreparedStatement stmt = database.prepare("SELECT value FROM config WHERE key = ?")) {
|
||||
stmt.setString(1, key);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
if (rs.next()) {
|
||||
return rs.getString("value");
|
||||
}
|
||||
return null;
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка получения конфигурации: {}", key, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить значение конфигурации
|
||||
*/
|
||||
public void set(String key, String value) {
|
||||
try (PreparedStatement stmt = database.prepare(
|
||||
"INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)"
|
||||
)) {
|
||||
stmt.setString(1, key);
|
||||
stmt.setString(2, value);
|
||||
stmt.executeUpdate();
|
||||
logger.info("Конфигурация обновлена: {} = {}", key, value);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка сохранения конфигурации: {}", key, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить значение конфигурации
|
||||
*/
|
||||
public void delete(String key) {
|
||||
try (PreparedStatement stmt = database.prepare("DELETE FROM config WHERE key = ?")) {
|
||||
stmt.setString(1, key);
|
||||
stmt.executeUpdate();
|
||||
logger.info("Конфигурация удалена: {}", key);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка удаления конфигурации: {}", key, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
92
src/main/java/creator/loki/database/Database.java
Normal file
92
src/main/java/creator/loki/database/Database.java
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package creator.loki.database;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
/**
|
||||
* Менеджер SQLite базы данных
|
||||
*/
|
||||
public class Database {
|
||||
private static final Logger logger = LoggerFactory.getLogger(Database.class);
|
||||
private final String dbPath;
|
||||
private Connection connection;
|
||||
|
||||
public Database(String dbPath) {
|
||||
this.dbPath = dbPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Подключиться к базе данных
|
||||
*/
|
||||
public void connect() {
|
||||
try {
|
||||
String url = "jdbc:sqlite:" + dbPath;
|
||||
connection = DriverManager.getConnection(url);
|
||||
logger.info("Подключение к базе данных: {}", dbPath);
|
||||
|
||||
// Включаем foreign keys
|
||||
try (Statement stmt = connection.createStatement()) {
|
||||
stmt.execute("PRAGMA foreign_keys = ON");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка подключения к базе данных", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить соединение
|
||||
*/
|
||||
public Connection getConnection() {
|
||||
try {
|
||||
if (connection == null || connection.isClosed()) {
|
||||
connect();
|
||||
}
|
||||
return connection;
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка проверки соединения", e);
|
||||
connect();
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполнить SQL запрос
|
||||
*/
|
||||
public void execute(String sql) {
|
||||
try (Statement stmt = getConnection().createStatement()) {
|
||||
stmt.execute(sql);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка выполнения SQL: {}", sql, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполнить SQL запрос с параметрами
|
||||
*/
|
||||
public PreparedStatement prepare(String sql) {
|
||||
try {
|
||||
return getConnection().prepareStatement(sql);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка подготовки SQL: {}", sql, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Закрыть соединение
|
||||
*/
|
||||
public void close() {
|
||||
try {
|
||||
if (connection != null && !connection.isClosed()) {
|
||||
connection.close();
|
||||
logger.info("Соединение с базой данных закрыто");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка закрытия соединения", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
191
src/main/java/creator/loki/database/DatabaseMigration.java
Normal file
191
src/main/java/creator/loki/database/DatabaseMigration.java
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
package creator.loki.database;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Миграция данных из JSON в SQLite
|
||||
*/
|
||||
public class DatabaseMigration {
|
||||
private static final Logger logger = LoggerFactory.getLogger(DatabaseMigration.class);
|
||||
private final Database database;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
public DatabaseMigration(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Мигрировать данные из JSON
|
||||
*/
|
||||
public void migrateFromJson(String jsonPath) {
|
||||
logger.info("Начало миграции данных из JSON: {}", jsonPath);
|
||||
|
||||
try (FileReader reader = new FileReader(jsonPath)) {
|
||||
Type type = new TypeToken<Map<String, Object>>(){}.getType();
|
||||
Map<String, Object> data = gson.fromJson(reader, type);
|
||||
|
||||
if (data == null) {
|
||||
logger.warn("JSON файл пустой или не существует");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMigrated()) {
|
||||
logger.info("Данные уже мигрированы, пропускаем");
|
||||
return;
|
||||
}
|
||||
|
||||
migrateUserStats(data);
|
||||
migrateReputation(data);
|
||||
migrateShipPairs(data);
|
||||
migrateCustomRoles(data);
|
||||
markMigrated();
|
||||
|
||||
logger.info("Миграция завершена успешно");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка миграции данных", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMigrated() {
|
||||
try (PreparedStatement stmt = database.prepare("SELECT value FROM config WHERE key = 'migrated'")) {
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
return rs.next() && "true".equals(rs.getString("value"));
|
||||
} catch (SQLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void markMigrated() {
|
||||
try (PreparedStatement stmt = database.prepare(
|
||||
"INSERT OR REPLACE INTO config (key, value) VALUES ('migrated', 'true')"
|
||||
)) {
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка сохранения статуса миграции", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void migrateUserStats(Map<String, Object> data) {
|
||||
Map<String, Object> userStats = (Map<String, Object>) data.get("userStats");
|
||||
if (userStats == null) return;
|
||||
|
||||
logger.info("Миграция userStats: {} записей", userStats.size());
|
||||
|
||||
String sql = """
|
||||
INSERT OR REPLACE INTO user_stats
|
||||
(user_id, level, total_xp, message_count, voice_minutes, last_message_time, last_voice_join)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(sql)) {
|
||||
for (Map.Entry<String, Object> entry : userStats.entrySet()) {
|
||||
Map<String, Object> stats = (Map<String, Object>) entry.getValue();
|
||||
|
||||
stmt.setString(1, (String) stats.get("userId"));
|
||||
stmt.setInt(2, ((Number) stats.get("level")).intValue());
|
||||
stmt.setInt(3, ((Number) stats.get("totalXP")).intValue());
|
||||
stmt.setInt(4, ((Number) stats.get("messageCount")).intValue());
|
||||
stmt.setLong(5, ((Number) stats.get("voiceMinutes")).longValue());
|
||||
stmt.setLong(6, ((Number) stats.get("lastMessageTime")).longValue());
|
||||
stmt.setLong(7, ((Number) stats.get("lastVoiceJoin")).longValue());
|
||||
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
logger.info("userStats мигрированы");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка миграции userStats", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void migrateReputation(Map<String, Object> data) {
|
||||
Map<String, Object> reputation = (Map<String, Object>) data.get("reputation");
|
||||
if (reputation == null) return;
|
||||
|
||||
logger.info("Миграция reputation: {} записей", reputation.size());
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(
|
||||
"INSERT OR REPLACE INTO reputation (user_id, reputation) VALUES (?, ?)"
|
||||
)) {
|
||||
for (Map.Entry<String, Object> entry : reputation.entrySet()) {
|
||||
stmt.setString(1, entry.getKey());
|
||||
stmt.setDouble(2, ((Number) entry.getValue()).doubleValue());
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
logger.info("reputation мигрирована");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка миграции reputation", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void migrateShipPairs(Map<String, Object> data) {
|
||||
Map<String, Object> shipPairs = (Map<String, Object>) data.get("shipPairs");
|
||||
if (shipPairs == null) return;
|
||||
|
||||
logger.info("Миграция shipPairs: {} записей", shipPairs.size());
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(
|
||||
"INSERT OR REPLACE INTO ship_pairs (user1_id, user2_id, compatibility) VALUES (?, ?, ?)"
|
||||
)) {
|
||||
for (Map.Entry<String, Object> entry : shipPairs.entrySet()) {
|
||||
String[] users = entry.getKey().split("-");
|
||||
if (users.length == 2) {
|
||||
stmt.setString(1, users[0]);
|
||||
stmt.setString(2, users[1]);
|
||||
stmt.setDouble(3, ((Number) entry.getValue()).doubleValue());
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
}
|
||||
logger.info("shipPairs мигрированы");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка миграции shipPairs", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void migrateCustomRoles(Map<String, Object> data) {
|
||||
Map<String, Object> customRoles = (Map<String, Object>) data.get("custom_roles");
|
||||
if (customRoles == null) return;
|
||||
|
||||
logger.info("Миграция custom_roles");
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(
|
||||
"INSERT OR REPLACE INTO custom_roles (guild_id, user_id, role_id) VALUES (?, ?, ?)"
|
||||
)) {
|
||||
for (Map.Entry<String, Object> guildEntry : customRoles.entrySet()) {
|
||||
String guildId = guildEntry.getKey();
|
||||
Map<String, Object> users = (Map<String, Object>) guildEntry.getValue();
|
||||
|
||||
for (Map.Entry<String, Object> userEntry : users.entrySet()) {
|
||||
String userId = userEntry.getKey();
|
||||
Object rolesObj = userEntry.getValue();
|
||||
|
||||
if (rolesObj instanceof java.util.List) {
|
||||
for (Object roleId : (java.util.List<?>) rolesObj) {
|
||||
stmt.setString(1, guildId);
|
||||
stmt.setString(2, userId);
|
||||
stmt.setString(3, roleId.toString());
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info("custom_roles мигрированы");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка миграции custom_roles", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
112
src/main/java/creator/loki/database/DatabaseSchema.java
Normal file
112
src/main/java/creator/loki/database/DatabaseSchema.java
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package creator.loki.database;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Схема базы данных - создание таблиц
|
||||
*/
|
||||
public class DatabaseSchema {
|
||||
private static final Logger logger = LoggerFactory.getLogger(DatabaseSchema.class);
|
||||
private final Database database;
|
||||
|
||||
public DatabaseSchema(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать все таблицы
|
||||
*/
|
||||
public void createTables() {
|
||||
logger.info("Создание таблиц базы данных...");
|
||||
|
||||
createConfigTable();
|
||||
createUserStatsTable();
|
||||
createReputationTable();
|
||||
createShipPairsTable();
|
||||
createCustomRolesTable();
|
||||
createVoiceChannelsTable();
|
||||
createCounterStatsTable();
|
||||
|
||||
logger.info("Таблицы созданы успешно");
|
||||
}
|
||||
|
||||
private void createConfigTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createUserStatsTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS user_stats (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
level INTEGER DEFAULT 0,
|
||||
total_xp INTEGER DEFAULT 0,
|
||||
message_count INTEGER DEFAULT 0,
|
||||
voice_minutes INTEGER DEFAULT 0,
|
||||
last_message_time INTEGER DEFAULT 0,
|
||||
last_voice_join INTEGER DEFAULT 0
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createReputationTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS reputation (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
reputation REAL DEFAULT 0.0
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createShipPairsTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS ship_pairs (
|
||||
user1_id TEXT NOT NULL,
|
||||
user2_id TEXT NOT NULL,
|
||||
compatibility REAL NOT NULL,
|
||||
PRIMARY KEY (user1_id, user2_id)
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createCustomRolesTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS custom_roles (
|
||||
guild_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
role_id TEXT NOT NULL,
|
||||
PRIMARY KEY (guild_id, user_id, role_id)
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createVoiceChannelsTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS voice_channels (
|
||||
channel_id TEXT PRIMARY KEY,
|
||||
owner_id TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
name TEXT,
|
||||
user_limit INTEGER DEFAULT 0,
|
||||
bitrate INTEGER DEFAULT 64000,
|
||||
locked INTEGER DEFAULT 0
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createCounterStatsTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS counter_stats (
|
||||
date TEXT PRIMARY KEY,
|
||||
joined INTEGER DEFAULT 0,
|
||||
left_count INTEGER DEFAULT 0,
|
||||
difference INTEGER DEFAULT 0
|
||||
)
|
||||
""");
|
||||
}
|
||||
}
|
||||
133
src/main/java/creator/loki/di/BotModule.java
Normal file
133
src/main/java/creator/loki/di/BotModule.java
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
package creator.loki.di;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.Singleton;
|
||||
import creator.loki.audio.AudioManager;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.config.ConfigLoader;
|
||||
import creator.loki.core.CommandRegistry;
|
||||
import creator.loki.core.EventDispatcher;
|
||||
import creator.loki.core.SlashCommandRegistry;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.database.ConfigRepository;
|
||||
import creator.loki.database.Database;
|
||||
import creator.loki.database.DatabaseMigration;
|
||||
import creator.loki.database.DatabaseSchema;
|
||||
import creator.loki.level.LevelSystem;
|
||||
import creator.loki.listener.LevelUpNotifier;
|
||||
import creator.loki.listener.VoiceTracker;
|
||||
import creator.loki.metrics.MetricsCollector;
|
||||
import creator.loki.profile.ProfileService;
|
||||
import creator.loki.role.CustomRoleManager;
|
||||
import creator.loki.util.CooldownManager;
|
||||
import creator.loki.util.GlobalRateLimiter;
|
||||
import creator.loki.voice.VoiceChannelManager;
|
||||
import creator.loki.counter.CounterManager;
|
||||
|
||||
/**
|
||||
* Главный модуль Guice для конфигурации Dependency Injection
|
||||
*/
|
||||
public class BotModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
// Все основные компоненты - Singleton
|
||||
bind(CommandRegistry.class).in(Singleton.class);
|
||||
bind(SlashCommandRegistry.class).in(Singleton.class);
|
||||
bind(CooldownManager.class).in(Singleton.class);
|
||||
bind(GlobalRateLimiter.class).in(Singleton.class);
|
||||
bind(MetricsCollector.class).in(Singleton.class);
|
||||
bind(AudioManager.class).in(Singleton.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
BotConfig provideBotConfig() {
|
||||
return ConfigLoader.load();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
JsonDataStore provideJsonDataStore() {
|
||||
JsonDataStore dataStore = new JsonDataStore("data/bot_data.json");
|
||||
dataStore.load();
|
||||
return dataStore;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
CustomRoleManager provideCustomRoleManager(JsonDataStore dataStore) {
|
||||
return new CustomRoleManager(dataStore);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
LevelSystem provideLevelSystem(JsonDataStore dataStore) {
|
||||
return new LevelSystem(dataStore);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
VoiceTracker provideVoiceTracker(LevelSystem levelSystem) {
|
||||
return new VoiceTracker(levelSystem);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ProfileService provideProfileService(JsonDataStore dataStore) {
|
||||
return new ProfileService(dataStore);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
EventDispatcher provideEventDispatcher(
|
||||
BotConfig config,
|
||||
CommandRegistry registry,
|
||||
GlobalRateLimiter rateLimiter,
|
||||
MetricsCollector metrics
|
||||
) {
|
||||
return new EventDispatcher(config, registry, rateLimiter, metrics);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
Database provideDatabase() {
|
||||
Database database = new Database("data/bot.db");
|
||||
database.connect();
|
||||
|
||||
// Создаем таблицы
|
||||
DatabaseSchema schema = new DatabaseSchema(database);
|
||||
schema.createTables();
|
||||
|
||||
// Мигрируем данные из JSON если нужно
|
||||
DatabaseMigration migration = new DatabaseMigration(database);
|
||||
migration.migrateFromJson("data/bot_data.json");
|
||||
|
||||
return database;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ConfigRepository provideConfigRepository(Database database) {
|
||||
return new ConfigRepository(database);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
LevelUpNotifier provideLevelUpNotifier(ConfigRepository configRepo, LevelSystem levelSystem) {
|
||||
return new LevelUpNotifier(configRepo, levelSystem);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
VoiceChannelManager provideVoiceChannelManager(JsonDataStore dataStore, ConfigRepository configRepo) {
|
||||
return new VoiceChannelManager(dataStore, configRepo);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
CounterManager provideCounterManager(JsonDataStore dataStore, ConfigRepository configRepo) {
|
||||
return new CounterManager(dataStore, configRepo);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue