Compare commits
No commits in common. 'master' and 'main' have entirely different histories.
19 changed files with 853 additions and 133 deletions
@ -0,0 +1,16 @@ |
|||||||
|
# MusicBot for Discord |
||||||
|
|
||||||
|
This is a basic Discord bot that will use the Youtube APi to find videos based on a song title and artist name. |
||||||
|
|
||||||
|
Note: You need to create a Token class with your own Discord Token, Youtube API Key and Spotify API client and secret. |
||||||
|
|
||||||
|
Sample: |
||||||
|
|
||||||
|
```java |
||||||
|
|
||||||
|
public class Token { |
||||||
|
public static final String TOKEN = "discordToken"; |
||||||
|
public static final String YOUTUBE_API_KEY = "YoutubeApiKey"; |
||||||
|
} |
||||||
|
|
||||||
|
``` |
||||||
@ -0,0 +1,20 @@ |
|||||||
|
package musicbot; |
||||||
|
|
||||||
|
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; |
||||||
|
|
||||||
|
/** |
||||||
|
* Interface to build a button interaction to manage a button event |
||||||
|
*/ |
||||||
|
public interface ButtonInteraction { |
||||||
|
/** |
||||||
|
* A unique name that identifies the button interaction |
||||||
|
* @return String the name of the button |
||||||
|
*/ |
||||||
|
String getName(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Executes the interaction logic of the button |
||||||
|
* @param event the ButtonInteractionEvent |
||||||
|
*/ |
||||||
|
void execute(ButtonInteractionEvent event); |
||||||
|
} |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
package musicbot; |
||||||
|
|
||||||
|
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; |
||||||
|
import net.dv8tion.jda.api.hooks.ListenerAdapter; |
||||||
|
import org.jetbrains.annotations.NotNull; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* Extension of the Listener Adaptor that manages all the interactions. Because |
||||||
|
* JDA doesn't have a class to specifically build button events, this is a custom way |
||||||
|
* of doing so. |
||||||
|
*/ |
||||||
|
public class ButtonInteractionManager extends ListenerAdapter { |
||||||
|
private final List<ButtonInteraction> buttonInteractions = new ArrayList<>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Listener for the button interactions. |
||||||
|
* @param event the {@code ButtonInteractionEvent} |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void onButtonInteraction(@NotNull ButtonInteractionEvent event) { |
||||||
|
final String id = event.getComponent().getId(); |
||||||
|
|
||||||
|
final String[] args = id.split("_", 0); |
||||||
|
|
||||||
|
for (final ButtonInteraction buttonInteraction : buttonInteractions) { |
||||||
|
if (args[0].equals(buttonInteraction.getName())) { |
||||||
|
buttonInteraction.execute(event); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add a ButtonInteraction to the ButtonInteractionManager |
||||||
|
* @param buttonInteraction the ButtonInteractiont to be added |
||||||
|
*/ |
||||||
|
public void add(@NotNull final ButtonInteraction buttonInteraction) { |
||||||
|
buttonInteractions.add(buttonInteraction); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
package musicbot; |
||||||
|
|
||||||
|
import net.dv8tion.jda.api.entities.Guild; |
||||||
|
import net.dv8tion.jda.api.events.ReadyEvent; |
||||||
|
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; |
||||||
|
import net.dv8tion.jda.api.hooks.ListenerAdapter; |
||||||
|
import org.jetbrains.annotations.NotNull; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* Extension of the Listener Adaptor that manages all the commands. |
||||||
|
*/ |
||||||
|
public class CommandManager extends ListenerAdapter { |
||||||
|
private final List<MCommand> commands = new ArrayList<>(); |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onReady(final ReadyEvent event) { |
||||||
|
for (final Guild guild : event.getJDA().getGuilds()) { |
||||||
|
for (MCommand command : commands) { |
||||||
|
if (command.getOptions() == null) { |
||||||
|
guild.upsertCommand(command.getName(), command.getDescription()).queue(); |
||||||
|
} else { |
||||||
|
guild.upsertCommand(command.getName(), command.getDescription()).addOptions(command.getOptions()).queue(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSlashCommandInteraction(final @NotNull SlashCommandInteractionEvent event) { |
||||||
|
for(final MCommand command : commands) { |
||||||
|
if(command.getName().equals(event.getName())) { |
||||||
|
command.execute(event); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void add(final MCommand command) { |
||||||
|
commands.add(command); |
||||||
|
} |
||||||
|
} |
||||||
@ -1,29 +0,0 @@ |
|||||||
package musicbot; |
|
||||||
|
|
||||||
import net.dv8tion.jda.api.entities.Guild; |
|
||||||
import net.dv8tion.jda.api.events.ReadyEvent; |
|
||||||
import net.dv8tion.jda.api.hooks.ListenerAdapter; |
|
||||||
import net.dv8tion.jda.api.interactions.commands.OptionType; |
|
||||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData; |
|
||||||
import org.jetbrains.annotations.NotNull; |
|
||||||
|
|
||||||
import java.util.Objects; |
|
||||||
|
|
||||||
public class Listeners extends ListenerAdapter { |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onReady(@NotNull final ReadyEvent event) { |
|
||||||
event.getJDA().upsertCommand("music", "find music to play").addOptions( |
|
||||||
new OptionData( |
|
||||||
OptionType.STRING, |
|
||||||
"title", |
|
||||||
"The name of the song", |
|
||||||
true), |
|
||||||
new OptionData( |
|
||||||
OptionType.STRING, |
|
||||||
"artist", |
|
||||||
"The artist of the song", |
|
||||||
true) |
|
||||||
).queue(); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,21 @@ |
|||||||
|
package musicbot; |
||||||
|
|
||||||
|
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; |
||||||
|
import net.dv8tion.jda.api.interactions.commands.build.OptionData; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* Interface to build a command interaction to manage a button event. |
||||||
|
* This class is prefaced with an M (for MusicBot) as to not confuse with |
||||||
|
* JDA's Command class. |
||||||
|
*/ |
||||||
|
public interface MCommand { |
||||||
|
String getName(); |
||||||
|
|
||||||
|
String getDescription(); |
||||||
|
|
||||||
|
List<OptionData> getOptions(); |
||||||
|
|
||||||
|
void execute(SlashCommandInteractionEvent event); |
||||||
|
} |
||||||
@ -1,15 +1,35 @@ |
|||||||
package musicbot; |
package musicbot; |
||||||
|
|
||||||
|
import musicbot.buttonInteractions.MoreGenre; |
||||||
|
import musicbot.buttonInteractions.MoreRecommendation; |
||||||
|
import musicbot.buttonInteractions.VideoRecommendation; |
||||||
|
import musicbot.commands.Artist; |
||||||
|
import musicbot.commands.Genres; |
||||||
import musicbot.commands.MusicVideo; |
import musicbot.commands.MusicVideo; |
||||||
|
import musicbot.commands.Recommend; |
||||||
import net.dv8tion.jda.api.JDA; |
import net.dv8tion.jda.api.JDA; |
||||||
import net.dv8tion.jda.api.JDABuilder; |
import net.dv8tion.jda.api.JDABuilder; |
||||||
|
|
||||||
public class Main { |
public class Main { |
||||||
public static void main(final String[] args) { |
public static void main(final String[] args) { |
||||||
final JDA jda = JDABuilder |
final JDA jda = JDABuilder |
||||||
.createDefault(Token.TOKEN) |
.createDefault(Token.DEV_TOKEN) |
||||||
.build(); |
.build(); |
||||||
jda.addEventListener(new Listeners()); |
final CommandManager commandManager = new CommandManager(); |
||||||
jda.addEventListener(new MusicVideo()); |
final ButtonInteractionManager buttonInteractionManager = new ButtonInteractionManager(); |
||||||
} |
|
||||||
|
// commands
|
||||||
|
commandManager.add(new Artist()); |
||||||
|
commandManager.add(new Genres()); |
||||||
|
commandManager.add(new MusicVideo()); |
||||||
|
commandManager.add(new Recommend()); |
||||||
|
|
||||||
|
// button interactions
|
||||||
|
buttonInteractionManager.add(new MoreRecommendation()); |
||||||
|
buttonInteractionManager.add(new MoreGenre()); |
||||||
|
buttonInteractionManager.add(new VideoRecommendation()); |
||||||
|
|
||||||
|
// add the managers
|
||||||
|
jda.addEventListener(commandManager); |
||||||
|
jda.addEventListener(buttonInteractionManager); |
||||||
|
} |
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,65 @@ |
|||||||
|
package musicbot; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class Paginator { |
||||||
|
|
||||||
|
public static class PaginatedItems<T> { |
||||||
|
private List<T> list = new ArrayList<T>(); |
||||||
|
private static boolean isLastPage = true; |
||||||
|
private static int nextPageNum; |
||||||
|
|
||||||
|
public void set(List<T> items){ |
||||||
|
list = items; |
||||||
|
} |
||||||
|
|
||||||
|
public void setNextPageNum(final int nextPage) { |
||||||
|
nextPageNum = nextPage; |
||||||
|
} |
||||||
|
|
||||||
|
public int getNextPageNum() { |
||||||
|
return nextPageNum; |
||||||
|
} |
||||||
|
|
||||||
|
public void setIsLastPage(final boolean isLastPageBool) { |
||||||
|
isLastPage = isLastPageBool; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isIsLastPage() { |
||||||
|
return isLastPage; |
||||||
|
} |
||||||
|
|
||||||
|
public List<T> get() { |
||||||
|
return list; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public <E> PaginatedItems<E> paginate( |
||||||
|
final List<E> tracks, |
||||||
|
final int numItemsPerPage, |
||||||
|
final int pageNumber |
||||||
|
) { |
||||||
|
if (tracks.isEmpty()) { |
||||||
|
return new PaginatedItems<>(); |
||||||
|
} |
||||||
|
|
||||||
|
final int maxSize = tracks.size(); |
||||||
|
final int maxPage = Math.round(maxSize / ((float) numItemsPerPage)); |
||||||
|
final int maxIndex = numItemsPerPage * pageNumber; |
||||||
|
final int baseIndex = maxIndex - numItemsPerPage; |
||||||
|
final int nextPage = pageNumber + 1; |
||||||
|
final PaginatedItems<E> paginatedItems = new PaginatedItems<>(); |
||||||
|
|
||||||
|
if (nextPage <= maxPage) { |
||||||
|
paginatedItems.set(tracks.subList(baseIndex, maxIndex)); |
||||||
|
paginatedItems.setIsLastPage(false); |
||||||
|
paginatedItems.setNextPageNum(nextPage); |
||||||
|
} else { |
||||||
|
paginatedItems.set(tracks.subList(maxIndex, maxSize)); |
||||||
|
paginatedItems.setIsLastPage(true); |
||||||
|
} |
||||||
|
|
||||||
|
return paginatedItems; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,31 @@ |
|||||||
|
package musicbot; |
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList; |
||||||
|
import se.michaelthelin.spotify.model_objects.specification.Artist; |
||||||
|
import se.michaelthelin.spotify.model_objects.specification.Track; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
public class RecommendationService { |
||||||
|
|
||||||
|
public List<Track> getRecommendations(final String artist, final String genre) { |
||||||
|
|
||||||
|
if (artist.isEmpty() && genre.isEmpty()) { |
||||||
|
return ImmutableList.of(); |
||||||
|
} |
||||||
|
final SpotifyClient spotifyClient = new SpotifyClient(); |
||||||
|
final Optional<String> maybeArtistId = findArtistId(artist); |
||||||
|
final String artistId = maybeArtistId.orElse(""); |
||||||
|
final List<Track> tracks = spotifyClient.findRecommendations(artistId, genre); |
||||||
|
|
||||||
|
return tracks; |
||||||
|
} |
||||||
|
|
||||||
|
private Optional<String> findArtistId(final String artist) { |
||||||
|
final SpotifyClient spotifyClient = new SpotifyClient(); |
||||||
|
final Optional<Artist> maybeArtist = spotifyClient.findArtist(artist); |
||||||
|
|
||||||
|
return maybeArtist.map(Artist::getId); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,112 @@ |
|||||||
|
package musicbot; |
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList; |
||||||
|
import org.apache.hc.core5.http.ParseException; |
||||||
|
import se.michaelthelin.spotify.SpotifyApi; |
||||||
|
import se.michaelthelin.spotify.exceptions.SpotifyWebApiException; |
||||||
|
import se.michaelthelin.spotify.model_objects.credentials.ClientCredentials; |
||||||
|
import se.michaelthelin.spotify.model_objects.specification.Artist; |
||||||
|
import se.michaelthelin.spotify.model_objects.specification.Paging; |
||||||
|
import se.michaelthelin.spotify.model_objects.specification.Recommendations; |
||||||
|
import se.michaelthelin.spotify.model_objects.specification.Track; |
||||||
|
import se.michaelthelin.spotify.requests.authorization.client_credentials.ClientCredentialsRequest; |
||||||
|
import se.michaelthelin.spotify.requests.data.browse.GetRecommendationsRequest; |
||||||
|
import se.michaelthelin.spotify.requests.data.search.simplified.SearchArtistsRequest; |
||||||
|
import se.michaelthelin.spotify.requests.data.browse.miscellaneous.GetAvailableGenreSeedsRequest; |
||||||
|
import se.michaelthelin.spotify.requests.data.search.simplified.SearchTracksRequest; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
public class SpotifyClient { |
||||||
|
private static final SpotifyApi SPOTIFY_API = new SpotifyApi.Builder() |
||||||
|
.setClientId(Token.SPOTIFY_CLIENT_ID) |
||||||
|
.setClientSecret(Token.SPOTIFY_CLIENT_SECRET) |
||||||
|
.build(); |
||||||
|
|
||||||
|
private static final ClientCredentialsRequest CLIENT_CREDENTIALS_REQUEST = SPOTIFY_API |
||||||
|
.clientCredentials() |
||||||
|
.build(); |
||||||
|
|
||||||
|
public SpotifyClient() { |
||||||
|
clientCredentialsSync(); |
||||||
|
} |
||||||
|
|
||||||
|
public Optional<Artist> findArtist(final String artist) { |
||||||
|
try { |
||||||
|
final SearchArtistsRequest searchArtistsRequest = SPOTIFY_API.searchArtists(artist) |
||||||
|
.limit(1) |
||||||
|
.build(); |
||||||
|
|
||||||
|
final Paging<Artist> artists = searchArtistsRequest.execute(); |
||||||
|
|
||||||
|
return Arrays.stream(artists.getItems()).findFirst(); |
||||||
|
} catch (SpotifyWebApiException | ParseException | IOException exception) { |
||||||
|
return Optional.empty(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public List<Track> findTracks(final String query) { |
||||||
|
try { |
||||||
|
final SearchTracksRequest searchTracksRequest = SPOTIFY_API.searchTracks(query).build(); |
||||||
|
|
||||||
|
final Paging<Track> tracks = searchTracksRequest.execute(); |
||||||
|
|
||||||
|
return Arrays.stream(tracks.getItems()).collect(ImmutableList.toImmutableList()); |
||||||
|
} catch (SpotifyWebApiException | ParseException | IOException exception) { |
||||||
|
return List.of(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public List<Track> findRecommendations(final String artistId, final String genres) { |
||||||
|
final GetRecommendationsRequest.Builder recommendationsRequestBuilder = SPOTIFY_API.getRecommendations(); |
||||||
|
|
||||||
|
if (!artistId.isEmpty()) { |
||||||
|
recommendationsRequestBuilder |
||||||
|
.seed_artists(artistId); |
||||||
|
} |
||||||
|
|
||||||
|
if (!genres.isEmpty()) { |
||||||
|
recommendationsRequestBuilder |
||||||
|
.seed_genres(genres); |
||||||
|
} |
||||||
|
|
||||||
|
final GetRecommendationsRequest recommendationsRequest = recommendationsRequestBuilder.build(); |
||||||
|
|
||||||
|
try { |
||||||
|
final Recommendations recommendations = recommendationsRequest.execute(); |
||||||
|
|
||||||
|
return Arrays.stream(recommendations.getTracks()).collect(ImmutableList.toImmutableList()); |
||||||
|
} catch (IOException | SpotifyWebApiException | ParseException e) { |
||||||
|
System.out.println("Error: " + e.getMessage()); |
||||||
|
return ImmutableList.of(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public List<String> findGenres() { |
||||||
|
final GetAvailableGenreSeedsRequest request = SPOTIFY_API.getAvailableGenreSeeds().build(); |
||||||
|
|
||||||
|
try { |
||||||
|
final String[] genres = request.execute(); |
||||||
|
return Arrays.stream(genres).collect(ImmutableList.toImmutableList()); |
||||||
|
} catch (IOException | SpotifyWebApiException | ParseException e) { |
||||||
|
System.out.println("Error: " + e.getMessage()); |
||||||
|
return ImmutableList.of(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static void clientCredentialsSync() { |
||||||
|
try { |
||||||
|
final ClientCredentials clientCredentials = CLIENT_CREDENTIALS_REQUEST.execute(); |
||||||
|
|
||||||
|
// Set access token for further "spotifyApi" object usage
|
||||||
|
SPOTIFY_API.setAccessToken(clientCredentials.getAccessToken()); |
||||||
|
|
||||||
|
System.out.println("Expires in: " + clientCredentials.getExpiresIn()); |
||||||
|
} catch (final IOException | SpotifyWebApiException | ParseException e) { |
||||||
|
System.out.println("Error: " + e.getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
package musicbot.buttonInteractions; |
||||||
|
|
||||||
|
import musicbot.ButtonInteraction; |
||||||
|
import musicbot.Paginator; |
||||||
|
import musicbot.SpotifyClient; |
||||||
|
import net.dv8tion.jda.api.EmbedBuilder; |
||||||
|
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; |
||||||
|
import net.dv8tion.jda.api.interactions.components.buttons.Button; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class MoreGenre implements ButtonInteraction { |
||||||
|
@Override |
||||||
|
public String getName() { |
||||||
|
return "genre"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void execute(ButtonInteractionEvent event) { |
||||||
|
final String id = event.getComponent().getId(); |
||||||
|
final Paginator paginator = new Paginator(); |
||||||
|
|
||||||
|
final String[] args = id.split("_", 0); |
||||||
|
event.deferReply().queue(); |
||||||
|
|
||||||
|
final int page = Integer.parseInt(args[1]); |
||||||
|
|
||||||
|
final SpotifyClient spotifyClient = new SpotifyClient(); |
||||||
|
final List<String> genres = spotifyClient.findGenres(); |
||||||
|
final Paginator.PaginatedItems<String> paginatedGenre = paginator.paginate(genres, 10, page); |
||||||
|
|
||||||
|
final EmbedBuilder eb = new EmbedBuilder(); |
||||||
|
|
||||||
|
paginatedGenre.get().forEach(genreItem -> eb.addField(genreItem, "", false)); |
||||||
|
if (!paginatedGenre.isIsLastPage()) { |
||||||
|
event.getHook().sendMessageEmbeds(eb.build()).addActionRow( |
||||||
|
Button.primary("genre_" + paginatedGenre.getNextPageNum(), "More genres") |
||||||
|
).queue(); |
||||||
|
} else { |
||||||
|
event.getHook().sendMessageEmbeds(eb.build()).queue(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,90 @@ |
|||||||
|
package musicbot.buttonInteractions; |
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList; |
||||||
|
import musicbot.ButtonInteraction; |
||||||
|
import musicbot.Paginator; |
||||||
|
import musicbot.RecommendationService; |
||||||
|
import net.dv8tion.jda.api.EmbedBuilder; |
||||||
|
import net.dv8tion.jda.api.entities.MessageEmbed; |
||||||
|
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; |
||||||
|
import net.dv8tion.jda.api.interactions.components.buttons.Button; |
||||||
|
import se.michaelthelin.spotify.model_objects.specification.ArtistSimplified; |
||||||
|
import se.michaelthelin.spotify.model_objects.specification.Track; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
/** |
||||||
|
* This interaction happens when a user requests more recommendations |
||||||
|
* after initial /recommend. |
||||||
|
*/ |
||||||
|
public class MoreRecommendation implements ButtonInteraction { |
||||||
|
private static final String ARGS_DELIMITER = "_"; |
||||||
|
private static final String ARTIST_GENRE_DELIMITER = "--"; |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getName() { |
||||||
|
return "more-recommend"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void execute(final ButtonInteractionEvent event) { |
||||||
|
event.deferReply().queue(); |
||||||
|
final String id = event.getComponent().getId(); |
||||||
|
final String[] args = id.split(ARGS_DELIMITER, 0); |
||||||
|
|
||||||
|
final String[] artistGenre = args[1].split(ARTIST_GENRE_DELIMITER, 0); |
||||||
|
final String artist = artistGenre[0]; |
||||||
|
final String genre = artistGenre.length < 2 ? "" : artistGenre[1]; |
||||||
|
|
||||||
|
final int pageNumber = Integer.parseInt(args[2]); |
||||||
|
final List<Track> tracks = new RecommendationService().getRecommendations(artist, genre); |
||||||
|
|
||||||
|
final Paginator paginator = new Paginator(); |
||||||
|
final Paginator.PaginatedItems<Track> paginatedTracks = paginator.paginate(tracks, 5, pageNumber); |
||||||
|
|
||||||
|
final EmbedBuilder eb = new EmbedBuilder(); |
||||||
|
final List<Track> trackList = paginatedTracks.get(); |
||||||
|
if (!trackList.isEmpty()) { |
||||||
|
eb |
||||||
|
.setTitle("More recommendations based on \"" + artist + "\"") |
||||||
|
.setDescription("Here are more recommendations based on your input."); |
||||||
|
|
||||||
|
trackList.forEach(track -> { |
||||||
|
final List<String> trackArtists = Arrays.stream(track.getArtists()) |
||||||
|
.map(ArtistSimplified::getName) |
||||||
|
.collect(ImmutableList.toImmutableList()); |
||||||
|
final String artists = String.join(", ", trackArtists); |
||||||
|
|
||||||
|
eb.addField(track.getName(), artists, false); |
||||||
|
}); |
||||||
|
|
||||||
|
final MessageEmbed embed = eb.build(); |
||||||
|
|
||||||
|
final List<Button> buttons = trackList.stream().map(track -> { |
||||||
|
final String artists = Arrays.stream(track.getArtists()).map(ArtistSimplified::getName) |
||||||
|
.collect(Collectors.joining(", ")); |
||||||
|
|
||||||
|
return Button.primary("recommend_" + artists, track.getName()); |
||||||
|
}).collect(ImmutableList.toImmutableList()); |
||||||
|
|
||||||
|
if (!paginatedTracks.isIsLastPage()) { |
||||||
|
final String buttonId = "more-recommend_" + artist + "--" + genre + "_" + paginatedTracks.getNextPageNum(); |
||||||
|
|
||||||
|
event |
||||||
|
.getHook() |
||||||
|
.sendMessageEmbeds(embed) |
||||||
|
.addActionRow(buttons) |
||||||
|
.addActionRow(Button.secondary(buttonId, "Get more recommendations")) |
||||||
|
.queue(); |
||||||
|
} else { |
||||||
|
event |
||||||
|
.getHook() |
||||||
|
.sendMessageEmbeds(embed) |
||||||
|
.addActionRow(buttons) |
||||||
|
.queue(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
package musicbot.buttonInteractions; |
||||||
|
|
||||||
|
import musicbot.ButtonInteraction; |
||||||
|
import musicbot.YoutubeSearch; |
||||||
|
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; |
||||||
|
|
||||||
|
public class VideoRecommendation implements ButtonInteraction { |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getName() { |
||||||
|
return "recommend"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void execute(ButtonInteractionEvent event) { |
||||||
|
final String id = event.getComponent().getId(); |
||||||
|
final String label = event.getComponent().getLabel(); |
||||||
|
final String[] args = id.split("_", 0); |
||||||
|
|
||||||
|
event.deferReply().queue(); |
||||||
|
|
||||||
|
final YoutubeSearch youtubeSearch = new YoutubeSearch(); |
||||||
|
final String result = youtubeSearch.searchForMusic(label, args[1]); |
||||||
|
|
||||||
|
event.getHook().sendMessage(result).queue(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,61 @@ |
|||||||
|
package musicbot.commands; |
||||||
|
|
||||||
|
import musicbot.MCommand; |
||||||
|
import musicbot.SpotifyClient; |
||||||
|
import net.dv8tion.jda.api.EmbedBuilder; |
||||||
|
import net.dv8tion.jda.api.entities.MessageEmbed; |
||||||
|
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; |
||||||
|
import net.dv8tion.jda.api.interactions.commands.OptionType; |
||||||
|
import net.dv8tion.jda.api.interactions.commands.build.OptionData; |
||||||
|
import se.michaelthelin.spotify.model_objects.specification.Track; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class Artist implements MCommand { |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getName() { |
||||||
|
return "artist"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getDescription() { |
||||||
|
return "Find songs by artist"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<OptionData> getOptions() { |
||||||
|
final List<OptionData> options= new ArrayList<>(); |
||||||
|
options.add(new OptionData( |
||||||
|
OptionType.STRING, |
||||||
|
"name", |
||||||
|
"Find tracks by an artist.", |
||||||
|
true |
||||||
|
)); |
||||||
|
|
||||||
|
return options; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void execute(SlashCommandInteractionEvent event) { |
||||||
|
event.deferReply().queue(); |
||||||
|
|
||||||
|
final String artist = event.getOption("name").getAsString(); |
||||||
|
|
||||||
|
final SpotifyClient spotifyClient = new SpotifyClient(); |
||||||
|
final List<Track> tracks = spotifyClient.findTracks("artist:" + artist); |
||||||
|
|
||||||
|
final EmbedBuilder eb = new EmbedBuilder(); |
||||||
|
eb |
||||||
|
.setTitle("Some tracks by " + artist); |
||||||
|
|
||||||
|
tracks.forEach(track -> |
||||||
|
eb.addField(track.getName(), "", false) |
||||||
|
); |
||||||
|
|
||||||
|
final MessageEmbed embed = eb.build(); |
||||||
|
|
||||||
|
event.getHook().sendMessageEmbeds(embed).queue(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,45 @@ |
|||||||
|
package musicbot.commands; |
||||||
|
|
||||||
|
import musicbot.MCommand; |
||||||
|
import musicbot.Paginator; |
||||||
|
import musicbot.SpotifyClient; |
||||||
|
import net.dv8tion.jda.api.EmbedBuilder; |
||||||
|
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; |
||||||
|
import net.dv8tion.jda.api.interactions.commands.build.OptionData; |
||||||
|
import net.dv8tion.jda.api.interactions.components.buttons.Button; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class Genres implements MCommand { |
||||||
|
@Override |
||||||
|
public String getName() { |
||||||
|
return "genres"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getDescription() { |
||||||
|
return "find support genres"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<OptionData> getOptions() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void execute(SlashCommandInteractionEvent event) { |
||||||
|
event.deferReply().queue(); |
||||||
|
final SpotifyClient spotifyClient = new SpotifyClient(); |
||||||
|
final List<String> genres = spotifyClient.findGenres(); |
||||||
|
final Paginator paginator = new Paginator(); |
||||||
|
|
||||||
|
final EmbedBuilder eb = new EmbedBuilder(); |
||||||
|
final Paginator.PaginatedItems<String> paginatedGenre = paginator.paginate(genres, 10, 1); |
||||||
|
|
||||||
|
paginatedGenre.get().forEach(genre -> eb.addField(genre, "", false)); |
||||||
|
|
||||||
|
event.getHook().sendMessageEmbeds(eb.build()).addActionRow( |
||||||
|
Button.primary("genre_" + paginatedGenre.getNextPageNum(), "More genres") |
||||||
|
).queue(); |
||||||
|
} |
||||||
|
} |
||||||
@ -1,23 +1,51 @@ |
|||||||
package musicbot.commands; |
package musicbot.commands; |
||||||
|
|
||||||
|
import musicbot.MCommand; |
||||||
import musicbot.YoutubeSearch; |
import musicbot.YoutubeSearch; |
||||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; |
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; |
||||||
import net.dv8tion.jda.api.hooks.ListenerAdapter; |
import net.dv8tion.jda.api.interactions.commands.OptionType; |
||||||
import org.jetbrains.annotations.NotNull; |
import net.dv8tion.jda.api.interactions.commands.build.OptionData; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
import java.util.Objects; |
import java.util.Objects; |
||||||
|
|
||||||
public class MusicVideo extends ListenerAdapter { |
public class MusicVideo implements MCommand { |
||||||
@Override |
|
||||||
public void onSlashCommandInteraction(@NotNull final SlashCommandInteractionEvent event) { |
|
||||||
if (!event.getName().equals("music")) return; |
|
||||||
|
|
||||||
final String title = Objects.requireNonNull(event.getOption("title")).getAsString(); |
@Override |
||||||
final String artist = Objects.requireNonNull(event.getOption("artist")).getAsString(); |
public String getName() { |
||||||
|
return "music"; |
||||||
|
} |
||||||
|
|
||||||
final YoutubeSearch youtubeSearch = new YoutubeSearch(); |
@Override |
||||||
final String result = youtubeSearch.searchForMusic(title, artist); |
public String getDescription() { |
||||||
|
return "find music to play"; |
||||||
|
} |
||||||
|
|
||||||
event.reply(result).queue(); |
@Override |
||||||
} |
public List<OptionData> getOptions() { |
||||||
|
final List<OptionData> options = new ArrayList<>(); |
||||||
|
options.add(new OptionData( |
||||||
|
OptionType.STRING, |
||||||
|
"title", |
||||||
|
"The name of the song", |
||||||
|
true)); |
||||||
|
options.add(new OptionData( |
||||||
|
OptionType.STRING, |
||||||
|
"artist", |
||||||
|
"The artist of the song", |
||||||
|
true)); |
||||||
|
return options; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void execute(SlashCommandInteractionEvent event) { |
||||||
|
final String title = Objects.requireNonNull(event.getOption("title")).getAsString(); |
||||||
|
final String artist = Objects.requireNonNull(event.getOption("artist")).getAsString(); |
||||||
|
|
||||||
|
final YoutubeSearch youtubeSearch = new YoutubeSearch(); |
||||||
|
final String result = youtubeSearch.searchForMusic(title, artist); |
||||||
|
|
||||||
|
event.reply(result).queue(); |
||||||
|
} |
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,98 @@ |
|||||||
|
package musicbot.commands; |
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList; |
||||||
|
import musicbot.MCommand; |
||||||
|
import musicbot.Paginator; |
||||||
|
import musicbot.RecommendationService; |
||||||
|
import net.dv8tion.jda.api.EmbedBuilder; |
||||||
|
import net.dv8tion.jda.api.entities.MessageEmbed; |
||||||
|
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; |
||||||
|
import net.dv8tion.jda.api.interactions.commands.OptionType; |
||||||
|
import net.dv8tion.jda.api.interactions.commands.build.OptionData; |
||||||
|
import net.dv8tion.jda.api.interactions.components.buttons.Button; |
||||||
|
import se.michaelthelin.spotify.model_objects.specification.ArtistSimplified; |
||||||
|
import se.michaelthelin.spotify.model_objects.specification.Track; |
||||||
|
|
||||||
|
import java.util.*; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
public class Recommend implements MCommand { |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getName() { |
||||||
|
return "recommend"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getDescription() { |
||||||
|
return "find recommendations for music"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<OptionData> getOptions() { |
||||||
|
final List<OptionData> options = new ArrayList<>(); |
||||||
|
options.add(new OptionData( |
||||||
|
OptionType.STRING, |
||||||
|
"artist", |
||||||
|
"Find other songs and artists based on an artist you like" |
||||||
|
)); |
||||||
|
options.add(new OptionData( |
||||||
|
OptionType.STRING, |
||||||
|
"genre", |
||||||
|
"Find other songs and artists based on a genre you like" |
||||||
|
)); |
||||||
|
return options; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void execute(SlashCommandInteractionEvent event) { |
||||||
|
event.deferReply().queue(); |
||||||
|
|
||||||
|
final String artistInput = event.getOption("artist") != null ? event.getOption("artist").getAsString() : ""; |
||||||
|
final String genreInput = event.getOption("genre") != null ? event.getOption("genre").getAsString() : ""; |
||||||
|
final List<Track> tracks = new RecommendationService().getRecommendations(artistInput, genreInput); |
||||||
|
final Paginator paginator = new Paginator(); |
||||||
|
final Paginator.PaginatedItems<Track> paginatedTracks = paginator.paginate(tracks, 5, 1); |
||||||
|
final EmbedBuilder eb = new EmbedBuilder(); |
||||||
|
final List<Track> trackList = paginatedTracks.get(); |
||||||
|
|
||||||
|
if (!trackList.isEmpty()) { |
||||||
|
eb |
||||||
|
.setTitle("Recommendations based on \"" + artistInput + "\"") |
||||||
|
.setDescription("Here are a list of recommendations based on your input. " + |
||||||
|
"It will help you find new music to listen to.") |
||||||
|
.setFooter("To find a song, select a button below."); |
||||||
|
|
||||||
|
trackList.forEach(track -> { |
||||||
|
final List<String> trackArtists = Arrays.stream(track.getArtists()) |
||||||
|
.map(ArtistSimplified::getName) |
||||||
|
.collect(ImmutableList.toImmutableList()); |
||||||
|
final String artists = String.join(", ", trackArtists); |
||||||
|
|
||||||
|
eb.addField(track.getName(), artists, false); |
||||||
|
}); |
||||||
|
|
||||||
|
final MessageEmbed embed = eb.build(); |
||||||
|
final List<Button> buttons = trackList.stream().map(track -> { |
||||||
|
final String artists = Arrays.stream(track.getArtists()).map(ArtistSimplified::getName) |
||||||
|
.collect(Collectors.joining(", ")); |
||||||
|
|
||||||
|
return Button.primary("recommend_" + artists, track.getName()); |
||||||
|
}).collect(ImmutableList.toImmutableList()); |
||||||
|
|
||||||
|
final String buttonId = "more-recommend_" + artistInput + "--" + genreInput + "_" + paginatedTracks.getNextPageNum(); |
||||||
|
|
||||||
|
event |
||||||
|
.getHook() |
||||||
|
.sendMessageEmbeds(embed) |
||||||
|
.addActionRow(buttons) |
||||||
|
.addActionRow(Button.secondary(buttonId, "Get more recommendations")) |
||||||
|
.queue(); |
||||||
|
} else { |
||||||
|
eb.setTitle("No recommendations found"); |
||||||
|
final MessageEmbed embed = eb.build(); |
||||||
|
|
||||||
|
event.getHook().sendMessageEmbeds(embed).queue(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue