Add Udio music source

This commit is contained in:
anthorob committed 2024-05-09 15:32:43 -04:00
1 parent eded9bee00
commit f79ab8984a
4 files changed
+260 -2

No files matched your search

@@ -11,6 +11,7 @@ import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager;
import com.sedmelluq.discord.lavaplayer.source.vimeo.VimeoAudioSourceManager;
import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import net.dv8tion.jda.api.entities.Guild;
@@ -24,6 +25,7 @@ import netneutrality.core.context.CommandContext;
import netneutrality.db.GSettings;
import netneutrality.exceptions.CommandExecutionException;
import netneutrality.music.loadable.LoadableSong;
import netneutrality.music.source.UdioAudioSourceManager;
import netneutrality.thread.MusicPlayerAutoDisconnectThread;
import netneutrality.thread.ThreadManager;
import netneutrality.utils.Settings;
@@ -79,6 +81,7 @@ public class MusicManager {
playerManager.registerSourceManager(youtubeSource);
playerManager.registerSourceManager(SoundCloudAudioSourceManager.createDefault());
playerManager.registerSourceManager(new UdioAudioSourceManager());
}
public boolean isNotConnectedTo(VoiceChannel vChannel) {
@@ -19,6 +19,7 @@ public class SourceFinder {
Bandcamp,
Vimeo,
YTSearch,
Udio,
Nothing
}
@@ -26,12 +27,14 @@ public class SourceFinder {
private static final Pattern youtubePattern = Pattern.compile("^(?:https?://)?(?:www\\.)?(?:youtube\\.com|youtu\\.be)/.*?(?:embed|e|v|watch.*?v=)?/?([-_a-zA-Z0-9]{10,})?(?:&?index=\\d+)?(?>(?:playlist\\?|&)?list=([^#\\\\&?]{12,}))?", Pattern.CASE_INSENSITIVE);
private static final Pattern youtubeMusicPattern = Pattern.compile("^(?:https?://)?(?:www\\.)?music\\.(?:youtube\\.com|youtu\\.be)/.*?(?:embed|e|v|watch.*?v=)?/?([-_a-zA-Z0-9]{10,})?(?:&?index=\\d+)?(?>(?:playlist\\?|&)?list=([^#\\\\&?]{12,}))?", Pattern.CASE_INSENSITIVE);
private static final Pattern soundcloudPattern = Pattern.compile("^(?:https?://)?(?:www\\.)?(?:m\\.)?soundcloud\\.com/([a-zA-Z0-9-_]+)/([a-zA-Z0-9-_]+)(?:\\\\?.*|)$", Pattern.CASE_INSENSITIVE);
private static Matcher twitchMatcher;
private static final Pattern udioPattern = Pattern.compile("^https://www.udio.com/songs/[0-9A-Za-z]+$", Pattern.CASE_INSENSITIVE);
public static AudioSourceType getSource(String url) {
Matcher youtubeMatcher = youtubePattern.matcher(url);
Matcher youtubeMusicMatcher = youtubeMusicPattern.matcher(url);
Matcher soundcloudMatcher = soundcloudPattern.matcher(url);
Matcher udioMatcher = udioPattern.matcher(url);
if (youtubeMatcher.find())
return AudioSourceType.Youtube;
@@ -42,6 +45,9 @@ public class SourceFinder {
if (soundcloudMatcher.find())
return AudioSourceType.Soundcloud;
if (udioMatcher.find())
return AudioSourceType.Udio;
if (!(url.startsWith("http://") || url.startsWith("https://")))
return AudioSourceType.YTSearch;
@@ -52,7 +58,7 @@ public class SourceFinder {
String identifier;
identifier = switch (getSource(url)) {
case Youtube, YTMusic, Soundcloud -> url;
case Youtube, YTMusic, Soundcloud, Udio -> url;
case YTSearch -> "ytsearch:" + url;
default -> null;
};
@@ -0,0 +1,159 @@
package netneutrality.music.source;/*
* Copyright (c) anthorob
*
* https://xtremcommunity.com/
* https://discord.gg/CdFcyQ
*/
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;
import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools;
import com.sedmelluq.discord.lavaplayer.tools.io.HttpConfigurable;
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.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;
import static com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity.SUSPICIOUS;
public class UdioAudioSourceManager implements AudioSourceManager, HttpConfigurable {
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 final HttpInterfaceManager httpInterfaceManager;
public UdioAudioSourceManager() {
httpInterfaceManager = HttpClientTools.createDefaultThreadLocalManager();
}
public HttpInterface getHttpInterface() {
return httpInterfaceManager.getInterface();
}
@Override
public String getSourceName() {
return "udio";
}
@Override
public AudioItem loadItem(AudioPlayerManager manager, AudioReference reference) {
// start = "og:audio:url\",\"content\":\"
// end = \"}],
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);
}
}
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 config = loadConfigJsonFromPageContent(content);
if (config == null) {
throw new FriendlyException("Track information not found on the page.", SUSPICIOUS, null);
}
JsonBrowser trackInfo;
try {
trackInfo = config.index(3).get("children").index(3).get("children").index(3).get("track");
} catch (NullPointerException e) {
throw new FriendlyException("Track information not found on the page.", SUSPICIOUS, null);
}
return new UdioAudioTrack(new AudioTrackInfo(
trackInfo.get("title").text(),
trackInfo.get("artist").text(),
(long) (trackInfo.get("duration").as(Double.class) * 1000.0),
trackUrl,
false,
trackUrl
), this);
}
JsonBrowser loadConfigJsonFromPageContent(String content) throws IOException {
String configText = DataFormatTools.extractBetween(content, "self.__next_f.push([1,\"8:", "\\n");
System.out.println(configText);
if (configText != null) {
configText = configText.replace("\\", "");
System.out.println(configText);
return JsonBrowser.parse(configText);
}
return null;
}
@Override
public boolean isTrackEncodable(AudioTrack track) {
return true;
}
@Override
public void encodeTrack(AudioTrack track, DataOutput output) throws IOException {
}
@Override
public AudioTrack decodeTrack(AudioTrackInfo trackInfo, DataInput input) throws IOException {
return new UdioAudioTrack(trackInfo, this);
}
@Override
public void shutdown() {
ExceptionTools.closeWithWarnings(httpInterfaceManager);
}
@Override
public void configureRequests(Function<RequestConfig, RequestConfig> configurator) {
httpInterfaceManager.configureRequests(configurator);
}
@Override
public void configureBuilder(Consumer<HttpClientBuilder> configurator) {
httpInterfaceManager.configureBuilder(configurator);
}
}
@@ -0,0 +1,90 @@
package netneutrality.music.source;/*
* Copyright (c) anthorob
*
* https://xtremcommunity.com/
* https://discord.gg/CdFcyQ
*/
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 = LoggerFactory.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 config = loadPlayerConfig(httpInterface);
if (config == null) {
throw new FriendlyException("Track information not present on the page.", SUSPICIOUS, null);
}
JsonBrowser trackInfo;
try {
trackInfo = config.index(3).get("children").index(3).get("children").index(3).get("track");
} catch (NullPointerException e) {
throw new FriendlyException("Track information not found on the page.", SUSPICIOUS, null);
}
return trackInfo.get("song_path").text();
}
private JsonBrowser loadPlayerConfig(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.loadConfigJsonFromPageContent(IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8));
}
}
}