fix(lavalink): drop spotify-tokener, lavasrc uses client credentials

- spotify URL resolution was broken (500 NPE): preferPartnerApi +
  customTokenEndpoint depended on dead spotify-tokener (-32000)
- remove tokener service from compose, remove dead SPOTIFY_* env from
  bot container (bot code never used them)
- spotify metadata now resolves via standard client credentials,
  playback is mirrored to SoundCloud via scsearch providers (ISRC/QUERY)
This commit is contained in:
loki5512344 2026-08-30 00:29:13 +02:00
parent 5e83356016
commit 4cbbb36159
Signed by: boba
GPG key ID: 253067914055423B
3 changed files with 57 additions and 16 deletions

57
TODO.md
View file

@ -37,6 +37,8 @@
| A47 | ✅ | BotInitializer: 162→135 строк | JdaFactory + LavalinkManagerFactory |
| A48 | ✅ | ShutdownManager: 9→3 зависимости | Lifecycle interface + Multibinder |
| A49 | ✅ | BotModule: 98→84 строки | CommandRegistrar bindings в feature модулях |
| SP1 | ✅ | Spotify не работал: lavasrc не мог получить токен (spotify-tokener упал с `-32000`, NPE 500 на loadtracks) | Удалён spotify-tokener, lavasrc → client credentials; плейбек Spotify → scsearch-мирроринг (SoundCloud) |
| PL1 | ✅ | Плейлист SoundCloud: игрался только 1 трек (REPLACED-гонка в TrackScheduler) | Локальный current-трекинг + атомарный skip + сохранение volume (`5e83356`) |
---
@ -85,6 +87,61 @@
| A39 | 🟢 **LOW** | EmbedFactory: `set*` вместо `with*` для builder | ✅ Renamed in 43 files |
| A36 | 🟢 **LOW** | RankCardGenerator: URI → URL, deprecated API | ✅ `URI.toURL()` → `new URL()` |
### Аудит 30.08.2026 — баг-хант (3 суб-агента, не исправлено)
#### Экономика / уровень / модериование / config / voice / counter
| # | Приоритет | Проблема | Файл |
|---|-----------|----------|------|
| F1 | 🔴 **CRITICAL** | `!slot`: `cashAfter` пишется из устаревшего снимка аккаунта — параллельные спины = бесплатные спины / двойные списания. Нужен атомарный `UPDATE ... SET cash = cash - ? WHERE cash >= ?` | `SlotService.java:45-46` |
| F2 | 🔴 **CRITICAL** | `transfer()` неатомарный read-check-write без транзакции — двойная трата / потеря денег при параллельных переводах | `EconomyService.java:29-41` |
| F3 | 🟠 **HIGH** | `/setup` целиком без проверок прав — любой участник меняет счётчик, логи, voice-триггер, роли за уровни | `SetupSlashHandler.java:50-105`, `SetupInteractions.java:19-71` |
| F4 | 🟠 **HIGH** | `claimDaily()` check-then-act по `last_daily` — двойной дейли при параллельных запросах | `EconomyService.java:88-93` |
| F5 | 🟠 **HIGH** | `WorkCommand` передаёт `level=0` — работы с `requiredLevel > 0` (COOK/PROGRAMMER/ASTRONAUT) недоступны никогда | `WorkCommand.java:51` |
| F6 | 🟠 **HIGH** | `LevelResetService`: фильтр `guildId + ":"` не совпадает с ключами `user_stats` — сброс уровней никого не трогает, а одноразовый флаг сгорает | `LevelResetService.java:34-43` |
| F7 | 🟠 **HIGH** | Blackjack: `hit/stand` NPE при гонке (state==null после удаления), общий несинхронизированный ArrayList карт | `BlackjackCommand.java:125`, `BlackjackService.java:52-95` |
| F8 | 🟠 **HIGH** | Блокирующие JDBC / `.complete()` на gateway-потоке JDA: 4 SQL на каждое сообщение, SQL на join/leave, REST на создание канала | `MessageListener.java:42-48`, `MemberCounterListener.java:59-74`, `FishMessageListener.java:39`, `VoiceChannelListener.java:41`, `VoiceTracker.java:78-79` |
| F9 | 🟡 **MEDIUM** | Blackjack push (равные очки) — игрок теряет ставку, ничья не обработана | `BlackjackService.java:89` |
| F10 | 🟡 **MEDIUM** | Blackjack TOCTOU `containsKey` — двойное списание ставки при параллельных `!bj`; ставка теряется при рестарте (игры только в памяти) | `BlackjackService.java:22,48` |
| F11 | 🟡 **MEDIUM** | Рыбалка: кулдаун пишется только при верном ответе — можно бесконечно перегенерировать поле без штрафа | `FishingService.java:24-30,87` |
| F12 | 🟡 **MEDIUM** | Счётчик участников: `getMemberCount()` (с ботами) vs `countMembers()` (без) — число на канале прыгает | `CounterChannelUpdater.java:22`, `CounterManager.java:81` |
| F13 | 🟡 **MEDIUM** | XP/время в войсе: read-modify-write без синхронизации — терянные апдейты при параллельных начислениях | `LevelService.java:41-49` |
| F14 | 🟡 **MEDIUM** | `getSystemChannel()` @Nullable → NPE при level-up в войсе | `VoiceTracker.java:85` |
#### Music / radio
| # | Приоритет | Проблема | Файл |
|---|-----------|----------|------|
| M1 | 🟠 **HIGH** | Утечка потоков: `removeMusicManager()` не вызывает `destroy()` — каждый автолив оставляет навсегда живой `ScheduledExecutorService` | `LavalinkManager.java:96-98`, `AutoLeaveController.java:47` |
| M2 | 🟠 **HIGH** | `m!play`: блокирующий `connectForPlayback` (sleep до ~4с) прямо на gateway-потоке music-бота | `MusicBotPlayHandler.java:36` |
| M3 | 🟠 **HIGH** | Селект-меню поиска: то же — блокирующий `connectForPlayback` в `onStringSelectInteraction` | `PlaySelectHandler.java:55` |
| M4 | 🟡 **MEDIUM** | `asVoiceChannel()` кидает `IllegalStateException` в stage-каналах — команды падают у пользователей | `VoiceHelper.java:59`, `PlaySlashHandler.java:31`, `RadioBotCommands.java:40` |
| M5 | 🟡 **MEDIUM** | Гонка `RadioManager.startRadio` — двойной `!r!play` теряет GuildMusicManager (утечка), `connect()==null` оставляет «зомби-сессию» | `RadioManager.java:64-83` |
| M6 | 🟡 **MEDIUM** | Отложенный `loadItem` оживляет уничтоженное радио (enqueue на выключенном executor → `RejectedExecutionException` глотается) | `RadioStreamLoader.java:36` |
| M7 | 🟡 **MEDIUM** | `!queue`: embed-поле >1024 символов при длинных названиях → `IllegalArgumentException`, команда падает | `QueueView.java:29,51-64` |
| M8 | 🟢 **LOW** | `Integer.parseInt` переполняется на большом «номере трека» из поиска | `PlayCommand.java:83` |
| M9 | 🟢 **LOW** | Заголовок embed поиска не обрезан до 256 символов | `SearchHandler.java:50` |
| M10 | 🟢 **LOW** | TOCTOU `link.getCachedPlayer() != null ? ...getX()` — NPE между проверкой и вызовом | `QueueView.java:40`, `VolumeControl.java:11-22` |
| M11 | 🟢 **LOW** | `FilterState.customBands` — несинхронизированный ArrayList под конкурентным доступом | `FilterState.java:19,89` |
| M12 | 🟢 **LOW** | `m!play` игнорирует неудачу подключения — «Добавлено в очередь», а звука нет | `MusicBotPlayHandler.java:36` |
| M13 | 🟢 **LOW** | `destroy()` глотает `InterruptedException` без восстановления флага | `GuildMusicManager.java:64-66` |
| M14 | 🟢 **LOW** | `r!volume` рапортует успех при отсутствии радио-сессии | `RadioSubcommands.java:108-113` |
#### Core / database
| # | Приоритет | Проблема | Файл |
|---|-----------|----------|------|
| C1 | 🟠 **HIGH** | Прокси `PreparedStatement` не разворачивает `InvocationTargetException` → SQL-ошибки вылетают как `UndeclaredThrowableException`, все `catch (SQLException)` их не ловят | `Database.java:76-89` |
| C2 | 🟡 **MEDIUM** | Переполнение long в `MuteDurationParser` (`!mute 106751991167301d` → отрицательные секунды) | `MuteDurationParser.java:55-57` |
| C3 | 🟡 **MEDIUM** | `event.getGuild()` в ЛС кидает `IllegalStateException` (JDA 6.5.0) — префикс-команды в ЛС падают | `EventDispatcher.java:81,107` |
| C4 | 🟡 **MEDIUM** | `CooldownManager` check-then-act — два параллельных `!rep`/`!rob` обходят кулдаун | `CooldownManager.java:23-57` |
| C5 | 🟡 **MEDIUM** | Миграция продолжается после частичного провала, `migrated=true` ставится всегда — тихая потеря данных | `DatabaseMigration.java:40-44`, `JsonMigration.java:43,63,88,117` |
| C6 | 🟢 **LOW** | `PRAGMA foreign_keys = ON` выполняется на одном соединении, не на всех в пуле | `Database.java:43-48` |
| C7 | 🟢 **LOW** | `JsonMigration`: NPE на битых записях легаси-JSON → бот не стартует | `JsonMigration.java:33-39` |
| C8 | 🟢 **LOW** | Lavalink-клиенты не регистрируются в ShutdownManager — websocket не закрывается при graceful shutdown | `BotInitializer.java:73-83` |
| C9 | 🟢 **LOW** | Ответ «команда не найдена» до rate-limiter — флуд несуществующими командами не ограничен | `EventDispatcher.java:63-77` |
| C10 | 🟢 **LOW** | `ApiRequestQueue`: `future.get()` без таймаута; при прерывании возвращает `null` | `ApiRequestQueue.java:38` |
---
## 📋 План рефакторинга