Fixed many things, and now you can use youtube to add to playlists.

This commit is contained in:
Andrew Lalis 2017-07-06 02:14:34 +02:00 committed by Andrew Lalis
parent 8177ad8669
commit fde75385bd
10 changed files with 159 additions and 41 deletions

View File

@ -6,7 +6,7 @@
<groupId>com.github.andrewlalis</groupId> <groupId>com.github.andrewlalis</groupId>
<artifactId>HandieBot</artifactId> <artifactId>HandieBot</artifactId>
<version>1.4.0</version> <version>1.4.1</version>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>

View File

@ -52,7 +52,8 @@ public class Commands {
public static void executeCommand(String command, CommandContext context){ public static void executeCommand(String command, CommandContext context){
for (Command cmd : commands) { for (Command cmd : commands) {
if (cmd.getName().equals(command)){ if (cmd.getName().equals(command)){
if (cmd instanceof StaticCommand){ if (cmd instanceof StaticCommand &&
((context != null && cmd.canUserExecute(context.getUser(), context.getGuild())) || context == null)){
log.log(BotLog.TYPE.COMMAND, command+" has been issued."); log.log(BotLog.TYPE.COMMAND, command+" has been issued.");
((StaticCommand)cmd).execute(); ((StaticCommand)cmd).execute();
return; return;

View File

@ -4,13 +4,11 @@ import com.google.api.services.youtube.model.Video;
import handiebot.HandieBot; import handiebot.HandieBot;
import handiebot.command.CommandContext; import handiebot.command.CommandContext;
import handiebot.command.ReactionHandler; import handiebot.command.ReactionHandler;
import handiebot.command.reactionListeners.YoutubeChoiceListener; import handiebot.command.reactionListeners.YoutubePlayListener;
import handiebot.command.types.ContextCommand; import handiebot.command.types.ContextCommand;
import handiebot.lavaplayer.playlist.UnloadedTrack; import handiebot.lavaplayer.playlist.UnloadedTrack;
import handiebot.utils.YoutubeSearch; import handiebot.utils.YoutubeSearch;
import sx.blah.discord.api.internal.json.objects.EmbedObject;
import sx.blah.discord.handle.obj.IMessage; import sx.blah.discord.handle.obj.IMessage;
import sx.blah.discord.util.RequestBuffer;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
@ -46,24 +44,17 @@ public class PlayCommand extends ContextCommand {
e.printStackTrace(); e.printStackTrace();
} }
} else { } else {
//Construct a Youtube song. //Construct a Youtube song choice.
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (int i = 0; i < context.getArgs().length; i++){ for (int i = 0; i < context.getArgs().length; i++){
sb.append(context.getArgs()[i]).append(' '); sb.append(context.getArgs()[i]).append(' ');
} }
List<Video> videos = YoutubeSearch.query(sb.toString().trim()); List<Video> videos = YoutubeSearch.query(sb.toString().trim());
if (videos != null) { if (videos != null) {
EmbedObject e = YoutubeSearch.createEmbed(videos);
IMessage message = context.getChannel().sendMessage(e);
List<String> urls = new ArrayList<>(videos.size()); List<String> urls = new ArrayList<>(videos.size());
videos.forEach((video) -> urls.add(WATCH_URL+video.getId())); videos.forEach((video) -> urls.add(WATCH_URL+video.getId()));
RequestBuffer.request(() -> message.addReaction(":one:")).get(); IMessage message = YoutubeSearch.displayChoicesDialog(videos, context.getChannel());
RequestBuffer.request(() -> message.addReaction(":two:")).get(); ReactionHandler.addListener(new YoutubePlayListener(message, context.getUser(), urls));
RequestBuffer.request(() -> message.addReaction(":three:")).get();
RequestBuffer.request(() -> message.addReaction(":four:")).get();
RequestBuffer.request(() -> message.addReaction(":five:")).get();
RequestBuffer.request(() -> message.addReaction(":x:")).get();
ReactionHandler.addListener(new YoutubeChoiceListener(message, context.getUser(), urls));
} }
} }
} }

View File

@ -1,12 +1,16 @@
package handiebot.command.commands.music; package handiebot.command.commands.music;
import com.google.api.services.youtube.model.Video;
import handiebot.HandieBot; import handiebot.HandieBot;
import handiebot.command.CommandContext; import handiebot.command.CommandContext;
import handiebot.command.CommandHandler; import handiebot.command.CommandHandler;
import handiebot.command.ReactionHandler;
import handiebot.command.reactionListeners.YoutubePlaylistAddListener;
import handiebot.command.types.ContextCommand; import handiebot.command.types.ContextCommand;
import handiebot.lavaplayer.playlist.Playlist; import handiebot.lavaplayer.playlist.Playlist;
import handiebot.lavaplayer.playlist.UnloadedTrack; import handiebot.lavaplayer.playlist.UnloadedTrack;
import handiebot.utils.MessageUtils; import handiebot.utils.MessageUtils;
import handiebot.utils.YoutubeSearch;
import handiebot.view.BotLog; import handiebot.view.BotLog;
import sx.blah.discord.handle.obj.IChannel; import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IMessage; import sx.blah.discord.handle.obj.IMessage;
@ -14,10 +18,12 @@ import sx.blah.discord.util.RequestBuffer;
import java.io.File; import java.io.File;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import static handiebot.HandieBot.log; import static handiebot.HandieBot.log;
import static handiebot.HandieBot.resourceBundle; import static handiebot.HandieBot.resourceBundle;
import static handiebot.utils.YoutubeSearch.WATCH_URL;
/** /**
* @author Andrew Lalis * @author Andrew Lalis
@ -32,7 +38,7 @@ public class PlaylistCommand extends ContextCommand {
"\t`create <PLAYLIST>` - "+resourceBundle.getString("commands.command.playlist.description.create")+"\n" + "\t`create <PLAYLIST>` - "+resourceBundle.getString("commands.command.playlist.description.create")+"\n" +
"\t`delete <PLAYLIST>` - "+resourceBundle.getString("commands.command.playlist.description.delete")+"\n" + "\t`delete <PLAYLIST>` - "+resourceBundle.getString("commands.command.playlist.description.delete")+"\n" +
"\t`show [PLAYLIST]` - "+resourceBundle.getString("commands.command.playlist.description.show")+"\n" + "\t`show [PLAYLIST]` - "+resourceBundle.getString("commands.command.playlist.description.show")+"\n" +
"\t`add <PLAYLIST> <URL> [URL]...` - "+resourceBundle.getString("commands.command.playlist.description.add")+"\n" + "\t`add <PLAYLIST> <URL URL...|SEARCHTEXT, SEARCHTEXT...>` - "+resourceBundle.getString("commands.command.playlist.description.add")+"\n" +
"\t`remove <PLAYLIST> <SONGINDEX>` - "+resourceBundle.getString("commands.command.playlist.description.remove")+"\n" + "\t`remove <PLAYLIST> <SONGINDEX>` - "+resourceBundle.getString("commands.command.playlist.description.remove")+"\n" +
"\t`rename <PLAYLIST> <NEWNAME>` - "+resourceBundle.getString("commands.command.playlist.description.rename")+"\n" + "\t`rename <PLAYLIST> <NEWNAME>` - "+resourceBundle.getString("commands.command.playlist.description.rename")+"\n" +
"\t`move <PLAYLIST> <OLDINDEX> <NEWINDEX>` - "+resourceBundle.getString("commands.command.playlist.description.move")+"\n" + "\t`move <PLAYLIST> <OLDINDEX> <NEWINDEX>` - "+resourceBundle.getString("commands.command.playlist.description.move")+"\n" +
@ -160,6 +166,8 @@ public class PlaylistCommand extends ContextCommand {
return; return;
Playlist playlist = new Playlist(context.getArgs()[1]); Playlist playlist = new Playlist(context.getArgs()[1]);
playlist.load(); playlist.load();
if (context.getArgs()[2].startsWith("http")){
//These are songs, so add them immediately.
for (int i = 2; i < context.getArgs().length; i++){ for (int i = 2; i < context.getArgs().length; i++){
playlist.loadTrack(context.getArgs()[i]); playlist.loadTrack(context.getArgs()[i]);
RequestBuffer.request(() -> context.getChannel().sendMessage(MessageFormat.format(resourceBundle.getString("commands.command.playlist.add.message"), playlist.getName()))).get(); RequestBuffer.request(() -> context.getChannel().sendMessage(MessageFormat.format(resourceBundle.getString("commands.command.playlist.add.message"), playlist.getName()))).get();
@ -167,6 +175,20 @@ public class PlaylistCommand extends ContextCommand {
playlist.save(); playlist.save();
context.getChannel().sendMessage(playlist.toString()); context.getChannel().sendMessage(playlist.toString());
log.log(BotLog.TYPE.INFO, MessageFormat.format(resourceBundle.getString("commands.command.playlist.add.log"), playlist.getName())); log.log(BotLog.TYPE.INFO, MessageFormat.format(resourceBundle.getString("commands.command.playlist.add.log"), playlist.getName()));
} else {
//This is a youtube search query.
StringBuilder sb = new StringBuilder();
for (int i = 2; i < context.getArgs().length; i++){
sb.append(context.getArgs()[i]).append(' ');
}
List<Video> videos = YoutubeSearch.query(sb.toString().trim());
if (videos != null) {
List<String> urls = new ArrayList<>(videos.size());
videos.forEach((video) -> urls.add(WATCH_URL+video.getId()));
IMessage message = YoutubeSearch.displayChoicesDialog(videos, context.getChannel());
ReactionHandler.addListener(new YoutubePlaylistAddListener(message, context.getUser(), urls, playlist));
}
}
} else { } else {
if (context.getArgs().length == 1){ if (context.getArgs().length == 1){
context.getChannel().sendMessage(MessageFormat.format(resourceBundle.getString("commands.command.playlist.error.addNameNeeded"), getPlaylistShowString(context))); context.getChannel().sendMessage(MessageFormat.format(resourceBundle.getString("commands.command.playlist.error.addNameNeeded"), getPlaylistShowString(context)));

View File

@ -1,9 +1,7 @@
package handiebot.command.reactionListeners; package handiebot.command.reactionListeners;
import handiebot.HandieBot;
import handiebot.command.ReactionHandler; import handiebot.command.ReactionHandler;
import handiebot.command.types.ReactionListener; import handiebot.command.types.ReactionListener;
import handiebot.lavaplayer.playlist.UnloadedTrack;
import handiebot.view.BotLog; import handiebot.view.BotLog;
import sx.blah.discord.handle.impl.events.guild.channel.message.reaction.ReactionEvent; import sx.blah.discord.handle.impl.events.guild.channel.message.reaction.ReactionEvent;
import sx.blah.discord.handle.obj.IMessage; import sx.blah.discord.handle.obj.IMessage;
@ -20,14 +18,14 @@ import static java.lang.Thread.sleep;
* Interface for youtube search results choices. A new instance of this listener is created every time a youtube * Interface for youtube search results choices. A new instance of this listener is created every time a youtube
* query is shown, and is unique for each user. * query is shown, and is unique for each user.
*/ */
public class YoutubeChoiceListener implements ReactionListener { public abstract class YoutubeChoiceListener implements ReactionListener {
private final IMessage message; protected final IMessage message;
private final IUser user; protected final IUser user;
private final List<String> urls; protected final List<String> urls;
private static final long timeout = 30000;//Time until the choice times out and deletes itself. protected static final long timeout = 30000;//Time until the choice times out and deletes itself.
private String[] choices = { private static final String[] choices = {
"1⃣", "1⃣",
"2⃣", "2⃣",
"3⃣", "3⃣",
@ -39,7 +37,6 @@ public class YoutubeChoiceListener implements ReactionListener {
this.message = message; this.message = message;
this.user = user; this.user = user;
this.urls = urls; this.urls = urls;
YoutubeChoiceListener instance = this;
new Thread(() -> { new Thread(() -> {
try { try {
sleep(timeout); sleep(timeout);
@ -59,12 +56,7 @@ public class YoutubeChoiceListener implements ReactionListener {
(this.user.getLongID() == event.getUser().getLongID())){ (this.user.getLongID() == event.getUser().getLongID())){
for (int i = 0; i < choices.length; i++){ for (int i = 0; i < choices.length; i++){
if (event.getReaction().toString().equals(choices[i])){ if (event.getReaction().toString().equals(choices[i])){
try { onChoice(i);
HandieBot.musicPlayer.addToQueue(message.getGuild(), new UnloadedTrack(urls.get(i)), this.user);
log.log(BotLog.TYPE.MUSIC, message.getGuild(), this.user.getName()+" chose item "+(i+1)+" from the Youtube query.");
} catch (Exception e) {
e.printStackTrace();
}
break; break;
} }
} }
@ -72,8 +64,17 @@ public class YoutubeChoiceListener implements ReactionListener {
} }
} }
/**
* Method to delete the large, unwieldy message and remove the listener for this set of videos.
*/
private void cleanup(){ private void cleanup(){
RequestBuffer.request(message::delete); RequestBuffer.request(message::delete);
ReactionHandler.removeListener(this); ReactionHandler.removeListener(this);
} }
/**
* What to do when a choice is made.
* @param choice An integer value from 0 to 4, describing which choice the player has chosen.
*/
protected abstract void onChoice(int choice);
} }

View File

@ -0,0 +1,33 @@
package handiebot.command.reactionListeners;
import handiebot.HandieBot;
import handiebot.lavaplayer.playlist.UnloadedTrack;
import handiebot.view.BotLog;
import sx.blah.discord.handle.obj.IMessage;
import sx.blah.discord.handle.obj.IUser;
import java.util.List;
import static handiebot.HandieBot.log;
/**
* @author Andrew Lalis
* Specific Listener for choices in the Play command, where songs chosen are added to the active queue.
*/
public class YoutubePlayListener extends YoutubeChoiceListener {
public YoutubePlayListener(IMessage message, IUser user, List<String> urls) {
super(message, user, urls);
}
@Override
protected void onChoice(int choice) {
try {
HandieBot.musicPlayer.addToQueue(message.getGuild(), new UnloadedTrack(urls.get(choice)), this.user);
log.log(BotLog.TYPE.MUSIC, message.getGuild(), this.user.getName()+" chose item "+(choice+1)+" from the Youtube query.");
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,29 @@
package handiebot.command.reactionListeners;
import handiebot.lavaplayer.playlist.Playlist;
import handiebot.utils.MessageUtils;
import sx.blah.discord.handle.obj.IMessage;
import sx.blah.discord.handle.obj.IUser;
import java.util.List;
/**
* @author Andrew Lalis
* Specific Listener for adding songs to a playlist that must be saved.
*/
public class YoutubePlaylistAddListener extends YoutubeChoiceListener {
private Playlist playlist;
public YoutubePlaylistAddListener(IMessage message, IUser user, List<String> urls, Playlist playlist) {
super(message, user, urls);
this.playlist = playlist;
}
@Override
protected void onChoice(int choice) {
this.playlist.loadTrack(this.urls.get(choice));
this.playlist.save();
MessageUtils.sendMessage("Added song to *"+this.playlist.getName()+"*.", message.getChannel());
}
}

View File

@ -218,10 +218,7 @@ public class MusicPlayer {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (timeUntilPlay > 0) { if (timeUntilPlay > 0) {
sb.append(MessageFormat.format(resourceBundle.getString("player.addedToQueue"), user.getName(), track.getTitle())); sb.append(MessageFormat.format(resourceBundle.getString("player.addedToQueue"), user.getName(), track.getTitle()));
} sb.append(String.format("\nTime until play: %d min, %02d sec",
//If there's some tracks in the queue, get the time until this one plays.
if (timeUntilPlay > 0){
sb.append(String.format("\nTime until play: %d min, %d sec",
TimeUnit.MILLISECONDS.toMinutes(timeUntilPlay), TimeUnit.MILLISECONDS.toMinutes(timeUntilPlay),
TimeUnit.MILLISECONDS.toSeconds(timeUntilPlay) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeUntilPlay)) TimeUnit.MILLISECONDS.toSeconds(timeUntilPlay) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeUntilPlay))
)); ));

View File

@ -2,6 +2,8 @@ package handiebot.utils;
import handiebot.HandieBot; import handiebot.HandieBot;
import handiebot.view.BotLog; import handiebot.view.BotLog;
import sx.blah.discord.api.internal.json.objects.EmbedObject;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IMessage; import sx.blah.discord.handle.obj.IMessage;
import sx.blah.discord.handle.obj.Permissions; import sx.blah.discord.handle.obj.Permissions;
import sx.blah.discord.util.RequestBuffer; import sx.blah.discord.util.RequestBuffer;
@ -16,6 +18,26 @@ import static handiebot.HandieBot.resourceBundle;
public class MessageUtils extends Thread implements Runnable { public class MessageUtils extends Thread implements Runnable {
private MessageUtils(){} private MessageUtils(){}
//TODO: Replace all 'sendMessage' calls with the new rate-limited calls.
/**
* Sends a message to a channel safely, using a request buffer.
* @param content The string content of the message.
* @param channel The channel to send the message on.
* @return The message object that was sent.
*/
public static IMessage sendMessage(String content, IChannel channel){
return RequestBuffer.request(() -> (IMessage)channel.sendMessage(content)).get();
}
/**
* Sends an embed object to a channel safely, using a request buffer.
* @param embed The content of the message.
* @param channel The channel to send the message on.
* @return The message object that was sent.
*/
public static IMessage sendMessage(EmbedObject embed, IChannel channel){
return RequestBuffer.request(() -> (IMessage)channel.sendMessage(embed)).get();
}
/** /**
* Deletes a message after a set amount of time. * Deletes a message after a set amount of time.

View File

@ -19,7 +19,10 @@ import com.google.api.services.youtube.model.Video;
import com.google.api.services.youtube.model.VideoListResponse; import com.google.api.services.youtube.model.VideoListResponse;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import sx.blah.discord.api.internal.json.objects.EmbedObject; import sx.blah.discord.api.internal.json.objects.EmbedObject;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IMessage;
import sx.blah.discord.util.EmbedBuilder; import sx.blah.discord.util.EmbedBuilder;
import sx.blah.discord.util.RequestBuffer;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.File;
@ -188,4 +191,23 @@ public class YoutubeSearch {
return builder.build(); return builder.build();
} }
/**
* Displays the first five results from a youtube search, and adds reactions so that a user may choose an option.
* @param videos The list of videos, returned from calling {@code query}.
* @param channel The channel to display the dialog in.
*/
public static IMessage displayChoicesDialog(List<Video> videos, IChannel channel){
EmbedObject e = YoutubeSearch.createEmbed(videos);
IMessage message = channel.sendMessage(e);
List<String> urls = new ArrayList<>(videos.size());
videos.forEach((video) -> urls.add(WATCH_URL + video.getId()));
RequestBuffer.request(() -> message.addReaction(":one:")).get();
RequestBuffer.request(() -> message.addReaction(":two:")).get();
RequestBuffer.request(() -> message.addReaction(":three:")).get();
RequestBuffer.request(() -> message.addReaction(":four:")).get();
RequestBuffer.request(() -> message.addReaction(":five:")).get();
RequestBuffer.request(() -> message.addReaction(":x:")).get();
return message;
}
} }