- 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)
- TrackScheduler: track current track locally instead of relying on
async-cached LavalinkPlayer.getTrack(); playlist enqueues now queue
instead of REPLACED-storming (only last track was audible)
- TrackEndHandler: ignore REPLACED/STOPPED (intentional), advance on
FINISHED/LOAD_FAILED via nextTrack(), log CLEANUP
- TrackStarter: preserve player volume between tracks instead of
hardcoding 100; report final failure to scheduler
- skip(): start next track atomically (single player update), support
skipping several tracks; prefix skip N no longer stops playback
- QueueStore: restore queue through scheduler.enqueue
- TrackScheduler ops synchronized (queue mutated from reactor + JDA
threads)
- syncCurrent on TrackStartEvent as authoritative source
- RatingService.getRatingValue(type, userId) centralizes per-type salts/special cases
(pp 6969/0-30, iq 123/0-140, mustache 888/0-100 + id overrides, gay 456, fat 999, boykisser 777)
- Pp/Iq/Mustache/Fat/Gay/Boykisser commands now delegate to it
- RatingOverviewCommand no longer reads random generateratings DB rows; iterates
members (no bots/super-admin) and computes min/max like the real commands do
- birb: some-random-api.com is dead -> api.alexflipnote.dev/birb
- AnimalApi: drop Caffeine URL cache (30s TTL made !neko return the same photo)
- RatingsRepository.getExtremeRating(guild, type, excluded, highest) with NOT IN
- !ratingoverview (super-admin only): per-category max/min, excluding bots + super-admin
- RatingsRepositoryTest: 7 new tests for getExtremeRating
Auxiliary music/radio bots don't enable GUILD_MEMBERS, so JDA checkIntents
threw 'Cannot use MemberCachePolicy.ALL without GatewayIntent.GUILD_MEMBERS'
and the whole cluster crashed at startup. Apply member cache + chunking only
for the main bot; aux bots keep MemberCachePolicy.NONE / ChunkingFilter.NONE
(also saves memory).
lavalink/plugins/youtube-plugin-1.18.2.jar never reached the repo - the
'*.jar' ignore rule silently dropped it from git add. Add an exception and
force-add the jar so Lavalink actually loads the YouTube source.
Lavalink 4.2.2 re-downloads/verifies config-declared plugins on startup and
failed writing to the git-managed plugins bind mount (Permission denied,
container uid 322 vs root-owned files after git pull). Jars in the plugins
directory are loaded directly; remove the duplicate config block.
- COPY build/libs/*.jar instead of hardcoded discord-bot-1.0.0.jar (breaks on version bump)
- lavalink: add /v4/info healthcheck; bot waits for service_healthy before start;
lavalink now depends on spotify-tokener
- lavalink host port bound to 127.0.0.1 (bot talks over docker network; no internet exposure)
- .dockerignore: exclude .github + lavalink/ from build context
- VoiceChannelManager: read trigger_channel_id/category_id live from ConfigRepository instead of caching at construction, so !voice-setup takes effect immediately without restart
- CounterManager: read channel_id/format live from config (same stale-cache fix); count members excluding bots
- CounterSetupPrefixCommand: use bot-excluding member count
- VoiceChannelManagerTest: lenient stubbing for live config reads
- TODO: remove quests, achievements, wiki from roadmap
- LavalinkManager: httpTimeout back to 30s (stagger delays are the real fix)
- checkstyle: comprehensive ruleset from LoParkour (200-line files, complexity, naming)
- PMD: ruleset with CognitiveComplexity, CloseResource, TooManyMethods etc
- Both tools run with ignoreFailures=true (warnings only, don't block build)
- Fix onMessageDelete: search audit log for entry matching message ID
instead of blindly taking l.get(0) (was showing random moderator)
- Increase audit log fetch limit from 1 to 5 for better matching
- Fix onMessageBulkDelete: same approach
- Unify all handlers to use findEntry() for target-matching
- Remove unused handle() helper, simplify send() logic
- Add final to every method/constructor parameter that is not assigned in the method body
- Handle annotations, varargs, generics, throws clauses
- Skip record components, lambdas, catch clauses, enum constructors
- Skip parameters that are reassigned in the method body
- Add spotify-tokener service (ghcr.io/topi314/spotify-tokener:master)
- Configure lavasrc to use it via customTokenEndpoint
- Enable preferPartnerApi for playlist loading via Partner API
- spotify-tokener handles the TOTP token generation properly
- lavasrc 4.8.2 uses /tracks endpoint for playlists (4.8.3 /items
returns 401 for new Spotify apps)
- Remove preferPartnerApi (anonymous token extraction broken on
current Spotify web player)
- playlistLoadLimit stays at 10 (1000 tracks)
RadioManager creates GuildMusicManager with null queueRepository (radio has no
queue persistence). Constructor called loadQueueAsync() which dereferenced null,
throwing NPE after voice connect — bot joined voice but track never loaded.
- remove 30s+30s VoiceHelper.waitForCondition that blocked JDA event thread
- remove abort on voice/player timeout (TrackScheduler retries sendTrack itself)
- remove openAudioConnection() that conflicted with Lavalink voice interceptor
- preserve stream URL across stopRadio/restart instead of losing it
- pass null queueRepository to GuildMusicManager (radio has no queue persistence)
- run startRadio in virtual thread from RadioBotListener/RadioCommand