From c44261953172a5c9507346719531f31d819385ce Mon Sep 17 00:00:00 2001 From: anthorob Date: Sun, 15 Mar 2026 01:41:18 -0400 Subject: [PATCH] remove harcoded html tag, parses next chunks to find track --- .../music/source/UdioAudioSourceManager.java | 116 +++++++++++++++--- 1 file changed, 102 insertions(+), 14 deletions(-) diff --git a/src/main/java/me/bobert/netneutrality/music/source/UdioAudioSourceManager.java b/src/main/java/me/bobert/netneutrality/music/source/UdioAudioSourceManager.java index b0d00b1..c5ed59a 100644 --- a/src/main/java/me/bobert/netneutrality/music/source/UdioAudioSourceManager.java +++ b/src/main/java/me/bobert/netneutrality/music/source/UdioAudioSourceManager.java @@ -5,10 +5,8 @@ package me.bobert.netneutrality.music.source; * https://discord.gg/qUa6zNFZsd */ -import com.fasterxml.jackson.core.JsonParseException; import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import com.sedmelluq.discord.lavaplayer.source.AudioSourceManager; -import com.sedmelluq.discord.lavaplayer.tools.DataFormatTools; import com.sedmelluq.discord.lavaplayer.tools.ExceptionTools; import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser; @@ -30,6 +28,7 @@ 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; @@ -38,6 +37,10 @@ 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() { @@ -120,24 +123,109 @@ public class UdioAudioSourceManager implements AudioSourceManager { } protected JsonBrowser loadTrackInfoFromPageContent(String content) throws IOException { - String startTag = "self.__next_f.push([1,\"33:"; - String endTag = "\\n\"])"; + Matcher matcher = NEXT_PUSH_PATTERN.matcher(content); + while (matcher.find()) { + String payload = matcher.group(2); + JsonBrowser trackInfo = tryExtractTrackFromNextChunk(payload); - String json = DataFormatTools.extractBetween(content, startTag, endTag); - if (json == null || json.isEmpty() || json.equals("null")) { - log.error("Track JSON not found in page"); - return null; + if (trackInfo != null) { + return trackInfo; + } } + return null; + } + + private JsonBrowser tryExtractTrackFromNextChunk(String payload) { try { - String unescapedJson = json.replace("\\", ""); - JsonBrowser parsedJson = JsonBrowser.parse(unescapedJson); + int trackKeyIndex = payload.indexOf("\\\"track\\\":{"); + if (trackKeyIndex < 0) { + return null; + } - return parsedJson.index(3).get("children").index(3).get("children").index(3).get("track"); - } catch (JsonParseException e) { - log.error("Failed to parse track JSON: {}", json); - 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) {