diff --git a/src/main/java/moe/nekojimi/chords/Downloader.java b/src/main/java/moe/nekojimi/chords/Downloader.java index 601dbad..f1ae83d 100644 --- a/src/main/java/moe/nekojimi/chords/Downloader.java +++ b/src/main/java/moe/nekojimi/chords/Downloader.java @@ -36,6 +36,7 @@ public class Downloader implements Consumer private static final int BITRATE_TARGET = 64_000; private static final Pattern FORMAT_PATTERN = Pattern.compile("^([\\w]+)\\s+([\\w]+)\\s+(\\w+ ?\\w*)\\s+(.*)$"); + public static final Pattern DESTINATION_PATTERN = Pattern.compile("Destination: (.*\\.wav)"); private final List downloadQueue = new LinkedList<>(); private final LinkedBlockingDeque workQueue = new LinkedBlockingDeque<>(); @@ -62,9 +63,8 @@ public class Downloader implements Consumer { try { - List formats = getFormats(song); - Format chosenFormat = chooseFormat(formats); - download(song, chosenFormat); + getFormats(song); + download(song); } catch (Exception ex) { ex.printStackTrace(); @@ -78,19 +78,20 @@ public class Downloader implements Consumer this.next = next; } - private Format chooseFormat(List formats) + private void chooseFormats(Song song) { + List formats = song.getFormats(); if (formats.isEmpty()) - return null; - System.out.println("Choosing from " + formats.size() + " formats:"); - System.out.println(formats); + return; +// System.out.println("Choosing from " + formats.size() + " formats:"); +// System.out.println(formats); formats.sort((Format a, Format b) -> { // audio only preferred to video - System.out.println("sort entered; a=" + a.toString() + " b=" + b.toString()); +// System.out.println("sort entered; a=" + a.toString() + " b=" + b.toString()); int comp = 0; comp = Boolean.compare(a.isAudioOnly(), b.isAudioOnly()); - System.out.println("\tCompared on audio only: " + comp); +// System.out.println("\tCompared on audio only: " + comp); if (comp == 0) { // known preferred to unknown @@ -105,7 +106,7 @@ public class Downloader implements Consumer int aDist = Math.abs(a.getBitrate() - BITRATE_TARGET); int bDist = Math.abs(b.getBitrate() - BITRATE_TARGET); comp = Integer.compare(bDist, aDist); - System.out.println("\tCompared on bitrate distance: " + comp); +// System.out.println("\tCompared on bitrate distance: " + comp); } } if (comp == 0) @@ -120,17 +121,17 @@ public class Downloader implements Consumer else // smaller is better { comp = Long.compare(b.getSize(), a.getSize()); - System.out.println("\tCompared on filesize: " + comp); +// System.out.println("\tCompared on filesize: " + comp); } } - System.out.println("\tOverall: " + comp); - return comp; +// System.out.println("\tOverall: " + comp); + return -comp; }); - System.out.println("Sorting done! Formats:" + formats); - return formats.get(formats.size() - 1); + song.setFormats(formats); +// System.out.println("Sorting done! Formats:" + formats); } - private List getFormats(Song song) + private void getFormats(Song song) { // try @@ -155,15 +156,16 @@ public class Downloader implements Consumer continue; formats.add(new Format(split[0], split[1], split[2], split[3])); } + song.setFormats(formats); // Matcher matcher = FORMAT_PATTERN.matcher(output); // while (matcher.find()) // formats.add(new Format(matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4))); - return formats; +// return formats; } catch (Exception ex) { Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, ex); - return List.of(); +// return List.of(); } } @@ -193,15 +195,19 @@ public class Downloader implements Consumer return downloadDir; } - private void download(Song song, Format format) + private void download(Song song) { - final String formatCodes = format != null ? format.getCode() + "/" : ""; + chooseFormats(song); + String formatCodes = ""; + final List formats = song.getFormats(); + for (int i = 0; i < 3 && i < song.getFormats().size(); i++) + formatCodes += formats.get(i).getCode() + "/"; try { messageHandler.accept(song, null); String cmd = "/usr/bin/youtube-dl -x" - + " -f " + formatCodes + "worstaudio/bestaudio/worst/best " + + " -f " + formatCodes + "worstaudio/bestaudio/worst/best" + " --audio-format=wav" + " --no-playlist" + " -o " + getDownloadDir().getAbsolutePath() + "/%(title)s.%(ext)s " @@ -212,7 +218,7 @@ public class Downloader implements Consumer String output = new String(in.readAllBytes(), Charset.defaultCharset()); String error = new String(exec.getErrorStream().readAllBytes(), Charset.defaultCharset()); System.out.println(output); - Matcher matcher = Pattern.compile("Destination: (.*\\.wav)").matcher(output); + Matcher matcher = DESTINATION_PATTERN.matcher(output); if (matcher.find()) song.setLocation(new File(matcher.group(1))); else if (exec.exitValue() != 0) diff --git a/src/main/java/moe/nekojimi/chords/Main.java b/src/main/java/moe/nekojimi/chords/Main.java index f378919..4d3d3e5 100644 --- a/src/main/java/moe/nekojimi/chords/Main.java +++ b/src/main/java/moe/nekojimi/chords/Main.java @@ -12,6 +12,8 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import javax.security.auth.login.LoginException; +import moe.nekojimi.chords.commands.Command; +import moe.nekojimi.chords.commands.JoinCommand; import moe.nekojimi.musicsearcher.Query; import moe.nekojimi.musicsearcher.Result; import moe.nekojimi.musicsearcher.providers.MetaSearcher; @@ -41,6 +43,8 @@ public class Main extends ListenerAdapter private final Searcher searcher; private JDA jda; + private final Map commands = new HashMap<>(); + private VoiceChannel currentVoiceChannel = null; private List lastSearchResults; @@ -91,12 +95,27 @@ public class Main extends ListenerAdapter if (song.getLocation() != null) channel.sendMessage(/*bracketNo + */"Finished downloading " + song + " for " + song.getRequestedBy() + ", added to queue!").queue(); else - channel.sendMessage(/*bracketNo + */"Now downloading " + song + " for " + song.getRequestedBy() + " ...").queue(); + { + Format format = song.getBestFormat(); + String formatDetails = ""; + if (format != null) + { + formatDetails = " (" + format.getBitrate() / 1000 + "k, " + String.format("%.2f", format.getSize() / (1024.0 * 1024.0)) + "MiB)"; + } + channel.sendMessage(/*bracketNo + */"Now downloading " + song + formatDetails + " for " + song.getRequestedBy() + " ...").queue(); + } else channel.sendMessage(/*bracketNo + */"Failed to download " + song + " for " + song.getRequestedBy() + "! Reason: " + ex.getMessage()).queue(); }); searcher = MetaSearcher.loadYAML(new File("searchproviders.yml")); + + addCommand(new JoinCommand(this)); + } + + private void addCommand(Command command) + { + commands.put(command.getKeyword(), command); } public void setJda(JDA jda) @@ -122,17 +141,30 @@ public class Main extends ListenerAdapter try { + String[] split = content.split("\\s+", 2); + String cmd = split[0].toLowerCase(); + + if (!cmd.startsWith("!")) + return; // doesn't start with prefix char + + cmd = cmd.substring(1); // strip prefix char String arg = ""; - if (content.contains(" ")) - arg = content.split(" ", 2)[1]; + if (split.length > 1) + arg = split[1]; - if (content.startsWith("!join ")) - onJoinCommand(event, guild, arg); + if (commands.containsKey(cmd)) + { + Command command = commands.get(cmd); + command.call(event, List.of(arg)); + } + + // else if (content.startsWith("!join ")) + // onJoinCommand(event, guild, arg); else if (content.equals("!leave")) onLeaveCommand(event); - else if (content.equals("!join")) - onJoinCommand(event); +// else if (content.equals("!join")) +// onJoinCommand(event); else if (content.startsWith("!play ")) onPlayCommand(event, guild, arg); else if (content.startsWith("!queue")) @@ -157,20 +189,20 @@ public class Main extends ListenerAdapter * @param event * The event for this command */ - private void onJoinCommand(GuildMessageReceivedEvent event) - { - // Note: None of these can be null due to our configuration with the JDABuilder! - Member member = event.getMember(); // Member is the context of the user for the specific guild, containing voice state and roles - GuildVoiceState voiceState = member.getVoiceState(); // Check the current voice state of the user - VoiceChannel channel = voiceState.getChannel(); // Use the channel the user is currently connected to -// if (channel != null) -// { -// connectTo(channel); // Join the channel of the user -// onConnecting(channel, event.getChannel()); // Tell the user about our success -// } else -// onUnknownChannel(event.getChannel(), "your voice channel"); // Tell the user about our failure - onJoinCommand(event, member.getGuild(), channel.getName()); - } +// private void onJoinCommand(GuildMessageReceivedEvent event) +// { +// // Note: None of these can be null due to our configuration with the JDABuilder! +// Member member = event.getMember(); // Member is the context of the user for the specific guild, containing voice state and roles +// GuildVoiceState voiceState = member.getVoiceState(); // Check the current voice state of the user +// VoiceChannel channel = voiceState.getChannel(); // Use the channel the user is currently connected to +//// if (channel != null) +//// { +//// connectTo(channel); // Join the channel of the user +//// onConnecting(channel, event.getChannel()); // Tell the user about our success +//// } else +//// onUnknownChannel(event.getChannel(), "your voice channel"); // Tell the user about our failure +// onJoinCommand(event, member.getGuild(), channel.getName()); +// } /** * Handle command with arguments. @@ -182,35 +214,34 @@ public class Main extends ListenerAdapter * @param arg * The input argument */ - private void onJoinCommand(GuildMessageReceivedEvent event, Guild guild, String arg) - { - boolean isNumber = arg.matches("\\d+"); // This is a regular expression that ensures the input consists of digits - VoiceChannel channel = null; - if (isNumber) // The input is an id? - channel = guild.getVoiceChannelById(arg); - if (channel == null) // Then the input must be a name? - { - List channels = guild.getVoiceChannelsByName(arg, true); - if (!channels.isEmpty()) // Make sure we found at least one exact match - channel = channels.get(0); // We found a channel! This cannot be null. - } - - TextChannel textChannel = event.getChannel(); - if (channel == null) // I have no idea what you want mr user - { - onUnknownChannel(textChannel, arg); // Let the user know about our failure - return; - } - connectTo(channel); // We found a channel to connect to! - onConnecting(channel, textChannel); // Let the user know, we were successful! - } +// private void onJoinCommand(GuildMessageReceivedEvent event, Guild guild, String arg) +// { +// boolean isNumber = arg.matches("\\d+"); // This is a regular expression that ensures the input consists of digits +// VoiceChannel channel = null; +// if (isNumber) // The input is an id? +// channel = guild.getVoiceChannelById(arg); +// if (channel == null) // Then the input must be a name? +// { +// List channels = guild.getVoiceChannelsByName(arg, true); +// if (!channels.isEmpty()) // Make sure we found at least one exact match +// channel = channels.get(0); // We found a channel! This cannot be null. +// } +// +// TextChannel textChannel = event.getChannel(); +// if (channel == null) // I have no idea what you want mr user +// { +// onUnknownChannel(textChannel, arg); // Let the user know about our failure +// return; +// } +// connectTo(channel); // We found a channel to connect to! +// onConnecting(channel, textChannel); // Let the user know, we were successful! +// } private void onLeaveCommand(GuildMessageReceivedEvent event) { if (currentVoiceChannel != null) { disconnect(); - } } @@ -404,43 +435,19 @@ public class Main extends ListenerAdapter + "!remove - Remove the song at position from the queue.\n" + "!skip - Skip the current song and play the next one.\n" + "!restart - Try playing the current song again in case it goes wrong.\n"; +// for (String key: commands.keySet()) +// { +// help += "!" + key + ":" +// } event.getChannel().sendMessage(help).queue(); } - - /** - * Inform user about successful connection. - * - * @param channel - * The voice channel we connected to - * @param textChannel - * The text channel to send the message in - */ - private void onConnecting(VoiceChannel channel, TextChannel textChannel) - { - textChannel.sendMessage("Connecting to " + channel.getName()).queue(); // never forget to queue()! - } - - /** - * The channel to connect to is not known to us. - * - * @param channel - * The message channel (text channel abstraction) to send failure - * information to - * @param comment - * The information of this channel - */ - private void onUnknownChannel(MessageChannel channel, String comment) - { - channel.sendMessage("Unable to connect to ``" + comment + "``, no such channel!").queue(); // never forget to queue()! - } - /** * Connect to requested channel and start echo handler * * @param channel * The channel to connect to */ - private void connectTo(VoiceChannel channel) + public void connectTo(VoiceChannel channel) { Guild guild = channel.getGuild(); // Get an audio manager for this guild, this will be created upon first use for each guild @@ -460,7 +467,7 @@ public class Main extends ListenerAdapter currentVoiceChannel = channel; } - private void disconnect() + public void disconnect() { if (currentVoiceChannel != null) { @@ -473,4 +480,39 @@ public class Main extends ListenerAdapter } } + public MusicHandler getMusicHandler() + { + return musicHandler; + } + + public Downloader getDownloader() + { + return downloader; + } + + public Searcher getSearcher() + { + return searcher; + } + + public JDA getJda() + { + return jda; + } + + public VoiceChannel getCurrentVoiceChannel() + { + return currentVoiceChannel; + } + + public List getLastSearchResults() + { + return lastSearchResults; + } + + public int getTrackNumber() + { + return trackNumber; + } + } diff --git a/src/main/java/moe/nekojimi/chords/Song.java b/src/main/java/moe/nekojimi/chords/Song.java index 0cf134c..f9dd140 100644 --- a/src/main/java/moe/nekojimi/chords/Song.java +++ b/src/main/java/moe/nekojimi/chords/Song.java @@ -7,6 +7,8 @@ package moe.nekojimi.chords; import java.io.File; import java.net.URL; +import java.util.ArrayList; +import java.util.List; /** * @@ -19,6 +21,7 @@ public class Song private final URL url; private File location = null; private int number; + private List formats = new ArrayList<>(); private String requestedBy; private String requestedIn; @@ -127,4 +130,22 @@ public class Song return "[" + number + "] " + ret; // return url.toExternalForm(); } + + public List getFormats() + { + return formats; + } + + public void setFormats(List formats) + { + this.formats = formats; + } + + public Format getBestFormat() + { + if (formats.isEmpty()) + return null; + return formats.get(0); + } + }