Compare commits

..

No commits in common. "b5b6e99946b17cbe35255724b68ce9e288951f2b" and "4cbbb36159d642a6e88d1a79cc045ac44e8a244a" have entirely different histories.

8 changed files with 4 additions and 112 deletions

View file

@ -126,7 +126,6 @@
| M12 | 🟢 **LOW** | `m!play` игнорирует неудачу подключения — «Добавлено в очередь», а звука нет | `MusicBotPlayHandler.java:36` | | M12 | 🟢 **LOW** | `m!play` игнорирует неудачу подключения — «Добавлено в очередь», а звука нет | `MusicBotPlayHandler.java:36` |
| M13 | 🟢 **LOW** | `destroy()` глотает `InterruptedException` без восстановления флага | `GuildMusicManager.java:64-66` | | M13 | 🟢 **LOW** | `destroy()` глотает `InterruptedException` без восстановления флага | `GuildMusicManager.java:64-66` |
| M14 | 🟢 **LOW** | `r!volume` рапортует успех при отсутствии радио-сессии | `RadioSubcommands.java:108-113` | | M14 | 🟢 **LOW** | `r!volume` рапортует успех при отсутствии радио-сессии | `RadioSubcommands.java:108-113` |
| M15 | 🟡 **MEDIUM** | Spotify-плейлисты не грузятся: Spotify (политика 27.11.2024) требует user-auth для `/v1/playlists/*` — client credentials получают `401 Valid user authentication required`. Одиночные треки работают (метаданные по client credentials + плейбек через scsearch-мирроринг). Варианты: OAuth refresh token юзера с scope `playlist-read-private`, либо revival browser-tokener | `lavalink/application.yml` (lavasrc) |
#### Core / database #### Core / database

View file

@ -1,19 +1,8 @@
services: services:
spotify-tokener:
build:
context: ./lavalink
dockerfile: Dockerfile.tokener
container_name: spotify-tokener
restart: unless-stopped
environment:
- SPOTIFY_TOKENER_CHROME_PATH=/usr/bin/chromium
lavalink: lavalink:
image: ghcr.io/lavalink-devs/lavalink:4-alpine image: ghcr.io/lavalink-devs/lavalink:4-alpine
container_name: loki-lavalink container_name: loki-lavalink
restart: unless-stopped restart: unless-stopped
depends_on:
- spotify-tokener
ports: ports:
- "127.0.0.1:2333:2333" - "127.0.0.1:2333:2333"
volumes: volumes:
@ -24,7 +13,6 @@ services:
JAVA_OPTS: "-Xmx1G -Xms256M" JAVA_OPTS: "-Xmx1G -Xms256M"
SPOTIFY_CLIENT_ID: ${SPOTIFY_CLIENT_ID} SPOTIFY_CLIENT_ID: ${SPOTIFY_CLIENT_ID}
SPOTIFY_CLIENT_SECRET: ${SPOTIFY_CLIENT_SECRET} SPOTIFY_CLIENT_SECRET: ${SPOTIFY_CLIENT_SECRET}
YOUTUBE_OAUTH_REFRESH_TOKEN: ${YOUTUBE_OAUTH_REFRESH_TOKEN}
healthcheck: healthcheck:
test: ["CMD-SHELL", "wget -q -O /dev/null --header=Authorization:Hch4HiVCxkKs9JPiSnrt4Q http://localhost:2333/v4/info || exit 1"] test: ["CMD-SHELL", "wget -q -O /dev/null --header=Authorization:Hch4HiVCxkKs9JPiSnrt4Q http://localhost:2333/v4/info || exit 1"]
interval: 30s interval: 30s
@ -32,16 +20,6 @@ services:
retries: 3 retries: 3
start_period: 20s start_period: 20s
lavasrc-patcher:
build:
context: ./lavalink
dockerfile: Dockerfile.config-patcher
container_name: lavasrc-patcher
depends_on:
lavalink:
condition: service_healthy
restart: "no"
bot: bot:
build: . build: .
container_name: loki-bot container_name: loki-bot

View file

@ -1,5 +0,0 @@
FROM alpine:3.20
RUN apk add --no-cache curl
COPY apply-config.sh /apply-config.sh
RUN chmod +x /apply-config.sh
CMD ["/apply-config.sh"]

View file

@ -1,6 +0,0 @@
FROM ghcr.io/topi314/spotify-tokener:master
RUN apt-get update && \
apt-get install -y --no-install-recommends chromium && \
rm -rf /var/lib/apt/lists/*
ENV SPOTIFY_TOKENER_CHROME_PATH=/usr/bin/chromium
ENV CHROMIUM_FLAGS="--no-sandbox --disable-gpu --headless"

View file

@ -6,7 +6,6 @@ plugins:
youtube: youtube:
enabled: true enabled: true
clients: clients:
- TV
- MUSIC - MUSIC
- ANDROID_VR - ANDROID_VR
- WEB - WEB
@ -14,9 +13,6 @@ plugins:
remoteCipher: remoteCipher:
url: "https://cipher.kikkia.dev/" url: "https://cipher.kikkia.dev/"
userAgent: "loki-bot" userAgent: "loki-bot"
oauth:
enabled: true
refreshToken: "${YOUTUBE_OAUTH_REFRESH_TOKEN}"
lavasrc: lavasrc:
providers: providers:
- "scsearch:%ISRC%" - "scsearch:%ISRC%"
@ -29,12 +25,9 @@ plugins:
spotify: spotify:
clientId: "${SPOTIFY_CLIENT_ID}" clientId: "${SPOTIFY_CLIENT_ID}"
clientSecret: "${SPOTIFY_CLIENT_SECRET}" clientSecret: "${SPOTIFY_CLIENT_SECRET}"
spDc: "AQDVrLVOyLLT3ZuiLoY32uH8HotvY_Lw67ay1lejf55S7O3f_O9IefxZ8XDf3pYklpwl5Klei2NeYUPRZ-cbkpQg5S3RyMjuiTmBxdAuMlugA61_wAy-bXD979aYDK44Bid6UKxLHjh0kFQZ_tX71DAUGezA2XjeLcRi_-TLcKHWKZVKPhGniUOZZEhg2Ij9t6valbqSbDOQ1t0dtQ"
countryCode: "RU" countryCode: "RU"
playlistLoadLimit: 10 playlistLoadLimit: 10
albumLoadLimit: 10 albumLoadLimit: 10
preferPartnerApi: true
customTokenEndpoint: "http://spotify-tokener:8080/api/token"
applemusic: applemusic:
countryCode: "RU" countryCode: "RU"
mediaType: "music" mediaType: "music"
@ -61,4 +54,3 @@ logging:
level: level:
root: INFO root: INFO
lavalink: INFO lavalink: INFO
com.github.topi314.lavasrc: INFO

View file

@ -1,16 +0,0 @@
#!/bin/sh
# Apply LavaSrc runtime config after Lavalink starts
until curl -sf -H "Authorization: Hch4HiVCxkKs9JPiSnrt4Q" http://lavalink:2333/v4/info >/dev/null 2>&1; do
sleep 2
done
sleep 5
curl -sf -X PATCH \
-H "Authorization: Hch4HiVCxkKs9JPiSnrt4Q" \
-H "Content-Type: application/json" \
-d '{"spotify":{"preferPartnerApi":true,"customTokenEndpoint":"http://spotify-tokener:8080/api/token","spDc":"AQDVrLVOyLLT3ZuiLoY32uH8HotvY_Lw67ay1lejf55S7O3f_O9IefxZ8XDf3pYklpwl5Klei2NeYUPRZ-cbkpQg5S3RyMjuiTmBxdAuMlugA61_wAy-bXD979aYDK44Bid6UKxLHjh0kFQZ_tX71DAUGezA2XjeLcRi_-TLcKHWKZVKPhGniUOZZEhg2Ij9t6valbqSbDOQ1t0dtQ"}}' \
http://lavalink:2333/v4/lavasrc/config
echo "LavaSrc config applied"

View file

@ -9,7 +9,6 @@ import dev.arbjerg.lavalink.client.event.ReadyEvent;
import dev.arbjerg.lavalink.client.event.StatsEvent; import dev.arbjerg.lavalink.client.event.StatsEvent;
import dev.arbjerg.lavalink.client.event.TrackEndEvent; import dev.arbjerg.lavalink.client.event.TrackEndEvent;
import dev.arbjerg.lavalink.client.event.TrackStartEvent; import dev.arbjerg.lavalink.client.event.TrackStartEvent;
import dev.arbjerg.lavalink.client.event.WebSocketClosedEvent;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -66,18 +65,5 @@ class LavalinkEventRouter {
manager.getScheduler().onTrackEnd(event.getTrack(), event.getEndReason()); manager.getScheduler().onTrackEnd(event.getTrack(), event.getEndReason());
} }
}); });
client
.on(WebSocketClosedEvent.class)
.subscribe(
event ->
LoggerDecorator.music(
logger,
"WebSocket закрыт: code="
+ event.getCode()
+ ", reason="
+ event.getReason()
+ ", byRemote="
+ event.getByRemote()));
} }
} }

View file

@ -2,9 +2,6 @@ package creator.loki.feature.music.core;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.LongConsumer; import java.util.function.LongConsumer;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -30,13 +27,6 @@ public class LavalinkManager {
private final BotConfig config; private final BotConfig config;
private final QueueRepository queueRepository; private final QueueRepository queueRepository;
private volatile LongConsumer voiceDisconnectHandler; private volatile LongConsumer voiceDisconnectHandler;
private final ScheduledExecutorService healthCheck =
Executors.newSingleThreadScheduledExecutor(
r -> {
Thread t = new Thread(r, "lavalink-health-check");
t.setDaemon(true);
return t;
});
@Inject @Inject
public LavalinkManager(BotConfig config, QueueRepository queueRepository) { public LavalinkManager(BotConfig config, QueueRepository queueRepository) {
@ -56,17 +46,6 @@ public class LavalinkManager {
this.client = new LavalinkClient(Helpers.getUserIdFromToken(botToken)); this.client = new LavalinkClient(Helpers.getUserIdFromToken(botToken));
addNode(lavalinkConfig);
new LavalinkEventRouter(client, musicManagers).register();
healthCheck.scheduleWithFixedDelay(this::checkNodeHealth, 30, 30, TimeUnit.SECONDS);
LoggerDecorator.music(
logger, "LavalinkManager инициализирован, нода: " + lavalinkConfig.getWsUri());
}
private void addNode(LavalinkConfig lavalinkConfig) {
client.addNode( client.addNode(
new NodeOptions.Builder() new NodeOptions.Builder()
.setName("main") .setName("main")
@ -74,26 +53,11 @@ public class LavalinkManager {
.setPassword(lavalinkConfig.getPassword()) .setPassword(lavalinkConfig.getPassword())
.setHttpTimeout(30000L) .setHttpTimeout(30000L)
.build()); .build());
}
private void checkNodeHealth() { new LavalinkEventRouter(client, musicManagers).register();
try {
var nodes = client.getNodes(); LoggerDecorator.music(
if (nodes.isEmpty()) { logger, "LavalinkManager инициализирован, нода: " + lavalinkConfig.getWsUri());
logger.warn("Lavalink: нет нод, пересоздаю");
addNode(config.getLavalinkConfig());
return;
}
var node = nodes.get(0);
if (!node.getAvailable()) {
logger.warn("Lavalink нода '{}' недоступна, переподключаю", node.getName());
client.removeNode(node);
addNode(config.getLavalinkConfig());
LoggerDecorator.music(logger, "Lavalink нода переподключена");
}
} catch (Exception e) {
logger.error("Ошибка health-check Lavalink", e);
}
} }
public LavalinkClient getClient() { public LavalinkClient getClient() {