Compare commits

..

No commits in common. 'master' and 'main' have entirely different histories.
master ... main

  1. 16
      README.md
  2. 22
      app/build.gradle
  3. 20
      app/src/main/java/musicbot/ButtonInteraction.java
  4. 42
      app/src/main/java/musicbot/ButtonInteractionManager.java
  5. 44
      app/src/main/java/musicbot/CommandManager.java
  6. 29
      app/src/main/java/musicbot/Listeners.java
  7. 21
      app/src/main/java/musicbot/MCommand.java
  8. 36
      app/src/main/java/musicbot/Main.java
  9. 65
      app/src/main/java/musicbot/Paginator.java
  10. 31
      app/src/main/java/musicbot/RecommendationService.java
  11. 112
      app/src/main/java/musicbot/SpotifyClient.java
  12. 132
      app/src/main/java/musicbot/YoutubeSearch.java
  13. 43
      app/src/main/java/musicbot/buttonInteractions/MoreGenre.java
  14. 90
      app/src/main/java/musicbot/buttonInteractions/MoreRecommendation.java
  15. 27
      app/src/main/java/musicbot/buttonInteractions/VideoRecommendation.java
  16. 61
      app/src/main/java/musicbot/commands/Artist.java
  17. 45
      app/src/main/java/musicbot/commands/Genres.java
  18. 52
      app/src/main/java/musicbot/commands/MusicVideo.java
  19. 98
      app/src/main/java/musicbot/commands/Recommend.java

16
README.md

@ -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";
}
```

22
app/build.gradle

@ -9,6 +9,7 @@
plugins { plugins {
// Apply the application plugin to add support for building a CLI application in Java. // Apply the application plugin to add support for building a CLI application in Java.
id 'application' id 'application'
id("net.ltgt.errorprone") version "3.1.0"
} }
repositories { repositories {
@ -17,6 +18,7 @@ repositories {
} }
dependencies { dependencies {
errorprone("com.google.errorprone:error_prone_core:2.24.1")
implementation 'com.google.apis:google-api-services-youtube:v3-rev222-1.25.0' implementation 'com.google.apis:google-api-services-youtube:v3-rev222-1.25.0'
// Use JUnit test framework. // Use JUnit test framework.
@ -24,24 +26,12 @@ dependencies {
// This dependency is used by the application. // This dependency is used by the application.
implementation("net.dv8tion:JDA:5.0.0-alpha.19") implementation("net.dv8tion:JDA:5.0.0-alpha.19")
implementation 'se.michaelthelin.spotify:spotify-web-api-java:8.3.4'
implementation("com.google.guava:guava:33.0.0-jre")
} }
task fatJar(type: Jar) { tasks.withType(JavaCompile).configureEach {
manifest { options.errorprone.disableWarningsInGeneratedCode.set(false)
attributes 'Main-Class': 'Main'
}
from {
configurations.compile.collect {
dependency ->
if (dependency.directory) {
return dependency
} else {
return zipTree(dependency)
}
}
}
with jar
} }
application { application {

20
app/src/main/java/musicbot/ButtonInteraction.java

@ -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);
}

42
app/src/main/java/musicbot/ButtonInteractionManager.java

@ -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);
}
}

44
app/src/main/java/musicbot/CommandManager.java

@ -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);
}
}

29
app/src/main/java/musicbot/Listeners.java

@ -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();
}
}

21
app/src/main/java/musicbot/MCommand.java

@ -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);
}

36
app/src/main/java/musicbot/Main.java

@ -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);
}
} }

65
app/src/main/java/musicbot/Paginator.java

@ -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;
}
}

31
app/src/main/java/musicbot/RecommendationService.java

@ -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);
}
}

112
app/src/main/java/musicbot/SpotifyClient.java

@ -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());
}
}
}

132
app/src/main/java/musicbot/YoutubeSearch.java

@ -1,4 +1,5 @@
package musicbot; package musicbot;
import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonFactory;
import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequest;
@ -18,73 +19,68 @@ import java.util.Optional;
import java.util.Properties; import java.util.Properties;
public class YoutubeSearch { public class YoutubeSearch {
private static final String PROPERTIES_FILENAME = "youtube.properties"; private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport(); private static final JsonFactory JSON_FACTORY = new JacksonFactory();
private static final JsonFactory JSON_FACTORY = new JacksonFactory(); private static final long NUMBER_VIDEOS_RETURNED = 1;
private static final long NUMBER_VIDEOS_RETURNED = 1; private static YouTube _youtube;
private static Properties _properties; public String searchForMusic(final String title, final String artist) {
_youtube = get_youtube();
private static YouTube _youtube;
final List<SearchResult> searchResults = doSearch(title, artist);
public String searchForMusic(final String title, final String artist) {
_youtube = get_youtube(); final Optional<SearchResult> maybeResult = searchResults.stream().findFirst();
final List<SearchResult> searchResults = doSearch(title, artist); return maybeResult.map(result -> "http://www.youtube.com/watch?v=" + result.getId().getVideoId())
.orElse("No videos found.");
final Optional<SearchResult> maybeResult = searchResults.stream().findFirst(); }
searchResults.stream().forEach(result -> {
System.out.println("result ID = " + result.getId()); private YouTube get_youtube() {
}); return new YouTube.Builder(
HTTP_TRANSPORT,
return maybeResult.map(result -> "http://www.youtube.com/watch?v=" + result.getId().getVideoId()) JSON_FACTORY,
.orElse("No videos found."); createRequestInitializer()
} ).setApplicationName("discord-musicbot").build();
}
private YouTube get_youtube() {
return new YouTube.Builder( private HttpRequestInitializer createRequestInitializer() {
HTTP_TRANSPORT, final HttpRequestInitializer initializer = new HttpRequestInitializer() {
JSON_FACTORY, @Override
createRequestInitializer() public void initialize(HttpRequest request) throws IOException {
).setApplicationName("youtube-cmdline-search-sample").build(); }
} };
private HttpRequestInitializer createRequestInitializer() { return initializer;
final HttpRequestInitializer initializer = new HttpRequestInitializer() { }
public void initialize(HttpRequest request) throws IOException {}
}; private List<SearchResult> doSearch(final String title, final String artist) {
try {
return initializer; final String apiKey = Token.YOUTUBE_API_KEY;
}
final String query = title + " by " + artist;
private List<SearchResult> doSearch(final String title, final String artist) {
try { final YouTube.Search.List search = _youtube.search().list("id,snippet")
final String apiKey = Token.YOUTUBE_API_KEY; .setKey(apiKey)
.setType("video")
final String query = title + " by " + artist; .setFields("items(id/kind,id/videoId,snippet/title,snippet/thumbnails/default/url)")
.setMaxResults(NUMBER_VIDEOS_RETURNED)
final YouTube.Search.List search = _youtube.search().list("id,snippet") .setQ(query);
.setKey(apiKey)
.setType("video") final SearchListResponse response = search.execute();
.setFields("items(id/kind,id/videoId,snippet/title,snippet/thumbnails/default/url)") return response.getItems();
.setMaxResults(NUMBER_VIDEOS_RETURNED) } catch (GoogleJsonResponseException e) {
.setQ(query); System.err.println("There was a service error: " + e.getDetails().getCode() + " : "
+ e.getDetails().getMessage());
final SearchListResponse response = search.execute(); return ImmutableList.of();
return response.getItems(); } catch (IOException e) {
} catch (GoogleJsonResponseException e) { System.err.println("There was an IO error: " + e.getCause() + " : " + e.getMessage());
System.err.println("There was a service error: " + e.getDetails().getCode() + " : " return ImmutableList.of();
+ e.getDetails().getMessage()); } catch (Throwable t) {
return ImmutableList.of(); t.printStackTrace();
} catch (IOException e) { return ImmutableList.of();
System.err.println("There was an IO error: " + e.getCause() + " : " + e.getMessage());
return ImmutableList.of();
} catch (Throwable t) {
t.printStackTrace();
return ImmutableList.of();
}
} }
}
} }

43
app/src/main/java/musicbot/buttonInteractions/MoreGenre.java

@ -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();
}
}
}

90
app/src/main/java/musicbot/buttonInteractions/MoreRecommendation.java

@ -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();
}
}
}
}

27
app/src/main/java/musicbot/buttonInteractions/VideoRecommendation.java

@ -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();
}
}

61
app/src/main/java/musicbot/commands/Artist.java

@ -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();
}
}

45
app/src/main/java/musicbot/commands/Genres.java

@ -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();
}
}

52
app/src/main/java/musicbot/commands/MusicVideo.java

@ -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();
}
} }

98
app/src/main/java/musicbot/commands/Recommend.java

@ -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…
Cancel
Save