From ffc15e8044e93719c98ac6b2d1105212dc01e146 Mon Sep 17 00:00:00 2001 From: anthorob Date: Sat, 26 Sep 2026 02:41:02 -0400 Subject: [PATCH] Remove outdated Udio plugin references and related code --- docs/reference/udio-lavalink-plugin/README.md | 43 ---- .../UdioAudioSourceManager.java | 237 ------------------ .../UdioAudioSourceManagerTest.java | 39 --- .../udio-lavalink-plugin/UdioAudioTrack.java | 80 ------ pom.xml | 3 + 5 files changed, 3 insertions(+), 399 deletions(-) delete mode 100644 docs/reference/udio-lavalink-plugin/README.md delete mode 100644 docs/reference/udio-lavalink-plugin/UdioAudioSourceManager.java delete mode 100644 docs/reference/udio-lavalink-plugin/UdioAudioSourceManagerTest.java delete mode 100644 docs/reference/udio-lavalink-plugin/UdioAudioTrack.java diff --git a/docs/reference/udio-lavalink-plugin/README.md b/docs/reference/udio-lavalink-plugin/README.md deleted file mode 100644 index d0577df..0000000 --- a/docs/reference/udio-lavalink-plugin/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Udio source — follow-up reference - -These files are **not part of the build**. They are the original client-side Lavaplayer -implementation of the udio.com source, kept verbatim as the reference for a future port. - -| File | Was | -|---|---| -| `UdioAudioSourceManager.java` | `me.bobert.netneutrality.music.source.UdioAudioSourceManager` | -| `UdioAudioTrack.java` | `me.bobert.netneutrality.music.source.UdioAudioTrack` | -| `UdioAudioSourceManagerTest.java` | its unit test | - -## Why they were moved - -The bot no longer decodes audio itself: after the [migration to Lavalink](../../music.md), source -resolution happens on the node. A Lavaplayer `AudioSourceManager` living in the bot process can -never be used by Lavalink, and these classes cannot even compile now that the `dev.arbjerg:lavaplayer` -dependency has been dropped from `pom.xml`. - -## Status - -**Udio playback is disabled.** `SourceFinder` still classifies Udio URLs, but -`SourceFinder.getIdentifier` returns no identifier for them and `MusicManager.play` replies that -Udio is temporarily unsupported. - -## Follow-up - -Port `UdioAudioSourceManager` / `UdioAudioTrack` into a standalone **Lavalink server plugin** -(its own build and published artifact), then enable it by adding the dependency to -`lavalink/application.yml`: - -```yaml -lavalink: - plugins: - - dependency: ":udio-plugin:" -``` - -Once the node resolves `https://www.udio.com/songs/...` identifiers, the only bot-side change is to -restore `Udio` in the `SourceFinder.getIdentifier` switch and drop the "temporarily unsupported" -reply in `MusicManager.play`. - -The scraping logic itself still applies: the manager extracts metadata and a direct playback URL -from the Next.js payload (`self.__next_f.push(...)`) of the track page, and the URL must be -re-fetched right before playback because it expires. diff --git a/docs/reference/udio-lavalink-plugin/UdioAudioSourceManager.java b/docs/reference/udio-lavalink-plugin/UdioAudioSourceManager.java deleted file mode 100644 index c5ed59a..0000000 --- a/docs/reference/udio-lavalink-plugin/UdioAudioSourceManager.java +++ /dev/null @@ -1,237 +0,0 @@ -package me.bobert.netneutrality.music.source; -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; -import com.sedmelluq.discord.lavaplayer.source.AudioSourceManager; -import com.sedmelluq.discord.lavaplayer.tools.ExceptionTools; -import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; -import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser; -import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools; -import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface; -import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterfaceManager; -import com.sedmelluq.discord.lavaplayer.track.AudioItem; -import com.sedmelluq.discord.lavaplayer.track.AudioReference; -import com.sedmelluq.discord.lavaplayer.track.AudioTrack; -import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo; -import org.apache.commons.io.IOUtils; -import org.apache.http.HttpStatus; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity.SUSPICIOUS; - -public class UdioAudioSourceManager implements AudioSourceManager { - private static final Logger log = LogManager.getLogger(UdioAudioSourceManager.class); - private static final String TRACK_URL_REGEX = "^https://www.udio.com/songs/[0-9A-Za-z]+$"; - private static final Pattern trackUrlPattern = Pattern.compile(TRACK_URL_REGEX); - private static final Pattern NEXT_PUSH_PATTERN = Pattern.compile( - "self\\.__next_f\\.push\\(\\[(\\d+),\"(.*?)\"]\\)", - Pattern.DOTALL - ); - private final HttpInterfaceManager httpInterfaceManager; - - public UdioAudioSourceManager() { - httpInterfaceManager = HttpClientTools.createDefaultThreadLocalManager(); - } - - @Override - public String getSourceName() { - return "udio"; - } - - @Override - public AudioItem loadItem(AudioPlayerManager manager, AudioReference reference) { - if (!trackUrlPattern.matcher(reference.identifier).matches()) { - return null; - } - - try (HttpInterface httpInterface = httpInterfaceManager.getInterface()) { - return loadFromTrackPage(httpInterface, reference.identifier); - } catch (IOException e) { - throw new FriendlyException("Loading Udio track information failed.", SUSPICIOUS, e); - } - } - - @Override - public boolean isTrackEncodable(AudioTrack track) { - return true; - } - - @Override - public void encodeTrack(AudioTrack track, DataOutput output) { - - } - - @Override - public AudioTrack decodeTrack(AudioTrackInfo trackInfo, DataInput input) { - return new UdioAudioTrack(trackInfo, this); - } - - @Override - public void shutdown() { - ExceptionTools.closeWithWarnings(httpInterfaceManager); - } - - public HttpInterface getHttpInterface() { - return httpInterfaceManager.getInterface(); - } - - private AudioItem loadFromTrackPage(HttpInterface httpInterface, String trackUrl) throws IOException { - try (CloseableHttpResponse response = httpInterface.execute(new HttpGet(trackUrl))) { - int statusCode = response.getStatusLine().getStatusCode(); - - if (statusCode == HttpStatus.SC_NOT_FOUND) { - return AudioReference.NO_TRACK; - } else if (!HttpClientTools.isSuccessWithContent(statusCode)) { - throw new FriendlyException("Server responded with an error.", SUSPICIOUS, - new IllegalStateException("Response code is " + statusCode)); - } - - return loadTrackFromPageContent(trackUrl, IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8)); - } - } - - private AudioTrack loadTrackFromPageContent(String trackUrl, String content) throws IOException { - JsonBrowser trackInfo = loadTrackInfoFromPageContent(content); - - if (trackInfo == null) { - log.debug("Udio HTML: {}", content); - throw new FriendlyException("Udio: Failed to extract track information from web page.", SUSPICIOUS, null); - } - - String title = trackInfo.get("title").text(); - String artist = trackInfo.get("artist").text(); - long duration = (long) (trackInfo.get("duration").as(Double.class) * 1000.0); - String playbackUrl = trackInfo.get("song_path").text(); - String artworkUrl = getArtworkUrlFromImagePath(trackInfo); - - AudioTrackInfo audioTrackInfo = new AudioTrackInfo(title, artist, duration, trackUrl, false, playbackUrl, artworkUrl, null); - return new UdioAudioTrack(audioTrackInfo, this); - } - - protected JsonBrowser loadTrackInfoFromPageContent(String content) throws IOException { - Matcher matcher = NEXT_PUSH_PATTERN.matcher(content); - while (matcher.find()) { - String payload = matcher.group(2); - JsonBrowser trackInfo = tryExtractTrackFromNextChunk(payload); - - if (trackInfo != null) { - return trackInfo; - } - } - - return null; - } - - private JsonBrowser tryExtractTrackFromNextChunk(String payload) { - try { - int trackKeyIndex = payload.indexOf("\\\"track\\\":{"); - if (trackKeyIndex < 0) { - return null; - } - - int objectStart = payload.indexOf('{', trackKeyIndex); - if (objectStart < 0) { - return null; - } - - String escapedTrackJson = extractJsonObjectFromString(payload, objectStart); - if (escapedTrackJson == null) { - return null; - } - - String trackJson = unescapeJsonStringFragment(escapedTrackJson); - JsonBrowser track = JsonBrowser.parse(trackJson); - - if (isValidTrack(track)) { - return track; - } - } catch (Exception e) { - log.debug("Skipping unparsable track payload fragment", e); - } - - return null; - } - - /** - * Extracts a JSON object from a given string starting at the specified index. This method parses the string - * to locate and retrieve the JSON object, ensuring proper handling of nested structures and escaped characters. - * - * @param text The input string containing the JSON object to be extracted. - * @param startIndex The index at which to start searching for the JSON object within the input string. - * @return The extracted JSON object as a string, or {@code null} if no valid JSON object is found. - */ - private String extractJsonObjectFromString(String text, int startIndex) { - boolean escaped = false; - boolean inString = false; - int depth = 0; - - for (int i = startIndex; i < text.length(); i++) { - char c = text.charAt(i); - - if (escaped) { - escaped = false; - continue; - } - - if (c == '\\') { - escaped = true; - continue; - } - - if (c == '"') { - inString = !inString; - continue; - } - - if (inString) { - continue; - } - - if (c == '{') { - depth++; - } else if (c == '}') { - depth--; - if (depth == 0) { - return text.substring(startIndex, i + 1); - } - } - } - - return null; - } - - private String unescapeJsonStringFragment(String value) { - return value.replace("\\\"", "\"").replace("\\\\", "\\"); - } - - private boolean isValidTrack(JsonBrowser track) { - return track != null - && !track.isNull() - && !track.get("title").isNull() - && !track.get("artist").isNull() - && !track.get("duration").isNull() - && !track.get("song_path").isNull() - && !track.get("image_path").isNull(); - } - - private String getArtworkUrlFromImagePath(JsonBrowser trackInfo) { - String imagePath = trackInfo.get("image_path").text(); - String artworkOptions = "width=3840,quality=75"; - - return imagePath.replace("public", artworkOptions); - } -} diff --git a/docs/reference/udio-lavalink-plugin/UdioAudioSourceManagerTest.java b/docs/reference/udio-lavalink-plugin/UdioAudioSourceManagerTest.java deleted file mode 100644 index ff79742..0000000 --- a/docs/reference/udio-lavalink-plugin/UdioAudioSourceManagerTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package me.bobert.netneutrality.music.source; -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -import com.sedmelluq.discord.lavaplayer.track.AudioItem; -import com.sedmelluq.discord.lavaplayer.track.AudioReference; -import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - - -public class UdioAudioSourceManagerTest { - - @Test - public void loadItem() { - AudioReference reference = new AudioReference("https://www.udio.com/songs/eNmZxqpMt9MRrjFUgYDgyi", null); - - UdioAudioSourceManager udioAudioSourceManager = new UdioAudioSourceManager(); - AudioItem loaded = udioAudioSourceManager.loadItem(null, reference); - - assertNotNull(loaded); - - UdioAudioTrack audioTrack = (UdioAudioTrack) loaded; - AudioTrackInfo info = audioTrack.getInfo(); - - assertNotNull(info); - - assertNotNull(info.artworkUrl); - - String artworkOptions = "width=3840,quality=75"; - assertTrue(info.artworkUrl.contains(artworkOptions)); - - } -} diff --git a/docs/reference/udio-lavalink-plugin/UdioAudioTrack.java b/docs/reference/udio-lavalink-plugin/UdioAudioTrack.java deleted file mode 100644 index 9662b80..0000000 --- a/docs/reference/udio-lavalink-plugin/UdioAudioTrack.java +++ /dev/null @@ -1,80 +0,0 @@ -package me.bobert.netneutrality.music.source; -/* - * Copyright (c) anthorob - * - * https://discord.gg/qUa6zNFZsd - */ - -import com.sedmelluq.discord.lavaplayer.container.mp3.Mp3AudioTrack; -import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; -import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser; -import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools; -import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface; -import com.sedmelluq.discord.lavaplayer.tools.io.PersistentHttpStream; -import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo; -import com.sedmelluq.discord.lavaplayer.track.DelegatedAudioTrack; -import com.sedmelluq.discord.lavaplayer.track.playback.LocalAudioTrackExecutor; -import org.apache.commons.io.IOUtils; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.io.IOException; -import java.net.URI; -import java.nio.charset.StandardCharsets; - -import static com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity.SUSPICIOUS; - -public class UdioAudioTrack extends DelegatedAudioTrack { - - private static final Logger log = LogManager.getLogger(UdioAudioTrack.class); - - private final UdioAudioSourceManager sourceManager; - - /** - * @param trackInfo Track info - * @param sourceManager Source manager which was used to find this track - */ - public UdioAudioTrack(AudioTrackInfo trackInfo, UdioAudioSourceManager sourceManager) { - super(trackInfo); - - this.sourceManager = sourceManager; - } - - @Override - public void process(LocalAudioTrackExecutor localExecutor) throws Exception { - try (HttpInterface httpInterface = sourceManager.getHttpInterface()) { - String playbackUrl = loadPlaybackUrl(httpInterface); - - log.debug("Starting Udio track from URL: {}", playbackUrl); - - try (PersistentHttpStream stream = new PersistentHttpStream(httpInterface, new URI(playbackUrl), null)) { - processDelegate(new Mp3AudioTrack(trackInfo, stream), localExecutor); - } - } - } - - private String loadPlaybackUrl(HttpInterface httpInterface) throws IOException { - JsonBrowser trackInfo = loadTrackInfo(httpInterface); - - if (trackInfo == null) { - throw new FriendlyException("Udio: Failed to extract track information from web page.", SUSPICIOUS, null); - } - - return trackInfo.get("song_path").text(); - } - - private JsonBrowser loadTrackInfo(HttpInterface httpInterface) throws IOException { - try (CloseableHttpResponse response = httpInterface.execute(new HttpGet(trackInfo.identifier))) { - int statusCode = response.getStatusLine().getStatusCode(); - - if (!HttpClientTools.isSuccessWithContent(statusCode)) { - throw new FriendlyException("Server responded with an error.", SUSPICIOUS, - new IllegalStateException("Response code for player config is " + statusCode)); - } - - return sourceManager.loadTrackInfoFromPageContent(IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8)); - } - } -} diff --git a/pom.xml b/pom.xml index cfd3372..eb7c066 100644 --- a/pom.xml +++ b/pom.xml @@ -61,8 +61,11 @@ flyway-maven-plugin 11.20.3 + ${env.FLYWAY_URL} + ${env.FLYWAY_USER} + ${env.FLYWAY_PASSWORD}