Add Udio music source
This commit is contained in:
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user