diff --git a/src/main/java/codes/ztereohype/ztereomusic/ZtereoMUSIC.java b/src/main/java/codes/ztereohype/ztereomusic/ZtereoMUSIC.java index 3e21163..123a969 100644 --- a/src/main/java/codes/ztereohype/ztereomusic/ZtereoMUSIC.java +++ b/src/main/java/codes/ztereohype/ztereomusic/ZtereoMUSIC.java @@ -6,6 +6,7 @@ import codes.ztereohype.ztereomusic.command.commands.*; import codes.ztereohype.ztereomusic.database.Config; import codes.ztereohype.ztereomusic.listeners.AloneDisconnectListener; import codes.ztereohype.ztereomusic.listeners.CommandListener; +import codes.ztereohype.ztereomusic.networking.SpotifyApiHelper; import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers; @@ -52,7 +53,8 @@ public class ZtereoMUSIC { EnumSet intents = EnumSet.of( GatewayIntent.GUILD_MESSAGES, - GatewayIntent.GUILD_VOICE_STATES + GatewayIntent.GUILD_VOICE_STATES, + GatewayIntent.GUILD_EMOJIS ); ztereoMUSIC.setConfig(Config.loadFrom("./config.json5")); @@ -102,6 +104,7 @@ public class ZtereoMUSIC { this.setPlayerManager(new DefaultAudioPlayerManager()); AudioSourceManagers.registerRemoteSources(this.getPlayerManager()); AudioSourceManagers.registerLocalSource(this.getPlayerManager()); + SpotifyApiHelper.startTokenTimer(); } private void setListeners() { diff --git a/src/main/java/codes/ztereohype/ztereomusic/command/commands/Play.java b/src/main/java/codes/ztereohype/ztereomusic/command/commands/Play.java index bebf6be..d25742d 100644 --- a/src/main/java/codes/ztereohype/ztereomusic/command/commands/Play.java +++ b/src/main/java/codes/ztereohype/ztereomusic/command/commands/Play.java @@ -7,9 +7,9 @@ import codes.ztereohype.ztereomusic.audio.TrackManagers; import codes.ztereohype.ztereomusic.command.Command; import codes.ztereohype.ztereomusic.command.CommandMeta; import codes.ztereohype.ztereomusic.command.permissions.VoiceChecks; +import codes.ztereohype.ztereomusic.networking.SpotifyApiHelper; import codes.ztereohype.ztereomusic.networking.YoutubeSearch; import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; -import lombok.Getter; import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; @@ -20,8 +20,9 @@ import java.util.regex.Pattern; public class Play implements Command { private static final Pattern URL_PATTERN = Pattern.compile("^(http|https)://([a-z]+\\.[a-z]+)+/\\S+$", Pattern.CASE_INSENSITIVE); + private static final Pattern SPOTIFY_URL_PATTERN = Pattern.compile("^(?:https://open\\.spotify\\.com/(track|playlist)/)(\\S+(?:\\?si=\\S+))$"); - private @Getter final CommandMeta meta; + private final CommandMeta meta; public Play() { this.meta = CommandMeta.builder() @@ -35,10 +36,10 @@ public class Play implements Command { .build(); } -// @Override -// public CommandMeta getMeta() { -// return this.meta; -// } + @Override + public CommandMeta getMeta() { + return this.meta; + } public void execute(MessageReceivedEvent messageEvent, String[] args) { Member author = Objects.requireNonNull(messageEvent.getMember()); @@ -64,11 +65,25 @@ public class Play implements Command { String mergedArgs = String.join(" ", args); Matcher matchedUrls = URL_PATTERN.matcher(mergedArgs); + Matcher matchedSpotifyUrl = SPOTIFY_URL_PATTERN.matcher(mergedArgs); boolean urlFound = matchedUrls.find(); + boolean spotifyUrlFound = matchedSpotifyUrl.find(); + + if (spotifyUrlFound) { + Optional songSearchQuery = SpotifyApiHelper.query(mergedArgs, messageChannel); + + if (songSearchQuery.isPresent()) { + mergedArgs = songSearchQuery.get(); + } else { + return; // SpotifyApiHelper takes care of answering why it failed + } + } String identifier; - if (!urlFound) { + // spotify urls need to be queried through youtube + if (!urlFound | spotifyUrlFound) { Optional query = YoutubeSearch.query(mergedArgs); + if (query.isPresent()) { identifier = query.get(); } else { @@ -76,7 +91,6 @@ public class Play implements Command { return; } } else { - // set identifier to the parsed url identifier = mergedArgs; } diff --git a/src/main/java/codes/ztereohype/ztereomusic/networking/SpotifyApiHelper.java b/src/main/java/codes/ztereohype/ztereomusic/networking/SpotifyApiHelper.java new file mode 100644 index 0000000..195fc4c --- /dev/null +++ b/src/main/java/codes/ztereohype/ztereomusic/networking/SpotifyApiHelper.java @@ -0,0 +1,120 @@ +package codes.ztereohype.ztereomusic.networking; + +import codes.ztereohype.ztereomusic.ZtereoMUSIC; +import lombok.SneakyThrows; +import net.dv8tion.jda.api.entities.MessageChannel; +import net.shadew.json.Json; +import net.shadew.json.JsonPath; +import net.shadew.json.JsonSyntaxException; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Optional; +import java.util.Timer; +import java.util.TimerTask; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SpotifyApiHelper { + private static final String CLIENT_ID = ZtereoMUSIC.getInstance().getConfig().getPropreties().get("spotify_client_id"); + private static final String CLIENT_SECRET = ZtereoMUSIC.getInstance().getConfig().getPropreties().get("spotify_client_secret"); + private static final Pattern IDENTIFIER_PATTERN = Pattern.compile("(?:(?<=https://open\\.spotify\\.com/track/)|(?<=https://open\\.spotify\\.com/playlist/))(\\S+(?=\\?si=\\S))"); + + private static String spotifyToken; + + private static final Json JSON = Json.json(); + + public static void startTokenTimer() { + Timer timer = new Timer(); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + spotifyToken = null; //remove old outdated token + Optional parsedToken = getToken(); + + if (parsedToken.isEmpty()) { + System.out.println("ERROR: Couldn't get a Spotify token. Spotify features will not work!"); + return; + } + + spotifyToken = parsedToken.get(); + } + }, 0, 3599*1000); + } + + @SneakyThrows + private static Optional getToken() { + HttpClient client = HttpClient.newHttpClient(); + + HttpRequest request = HttpRequest.newBuilder( + URI.create("https://accounts.spotify.com/api/token?grant_type=client_credentials")) + .POST(HttpRequest.BodyPublishers.ofString("")) + .header("Authorization", "Basic " + Base64.getEncoder().encodeToString((CLIENT_ID + ":" + CLIENT_SECRET).getBytes(StandardCharsets.UTF_8.toString()))) + .header("Content-Type", "application/x-www-form-urlencoded") + .build(); + + JsonPath tokenPath = JsonPath.parse("access_token"); + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + String results = JSON.parse(response.body()).query(tokenPath).asString(); + return Optional.ofNullable(results); + } catch (IOException | InterruptedException | JsonSyntaxException e) { + e.printStackTrace(); + return Optional.empty(); + } + } + + public static Optional query(String songUrl, MessageChannel messageChannel) { + if (spotifyToken == null) { + System.out.println("Null Spotify token detected"); + messageChannel.sendMessage("I don't have a spotify token for now. Try again later.").queue(); + return Optional.empty(); + } + + if (songUrl.contains("/playlist/")) { + messageChannel.sendMessage("Playlists aren't supported for now, please send the individual song links.").queue(); + } + + Matcher matchedSpotifyIdentifier = IDENTIFIER_PATTERN.matcher(songUrl); + String spotifyIdentifier; + if (matchedSpotifyIdentifier.find()) { + spotifyIdentifier = matchedSpotifyIdentifier.group(); + } else { + messageChannel.sendMessage("Could not parse Spotify link. Try entering the song title directly.").queue(); + return Optional.empty(); + } + + String query = "https://api.spotify.com/v1/tracks?ids=" + + spotifyIdentifier + "&market=ES"; //espaƱaaaa + + HttpClient client = HttpClient.newHttpClient(); + + HttpRequest request = HttpRequest.newBuilder( + URI.create(query)) + .GET() + .header("Authorization", "Bearer " + spotifyToken) + .header("Content-Type", "application/json") + .build(); + + JsonPath titlePath = JsonPath.parse("tracks[0].name"); + JsonPath authorPath = JsonPath.parse("tracks[0].artists[0].name"); + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + String title = JSON.parse(response.body()).query(titlePath).asString(); + String author = JSON.parse(response.body()).query(authorPath).asString(); + String songSearchQuery = title + " " + author; + + return Optional.of(songSearchQuery); + } catch (IOException | InterruptedException | JsonSyntaxException e) { + e.printStackTrace(); + messageChannel.sendMessage("Something wrong happened with the spotify request.").queue(); + return Optional.empty(); + } + } +}