From 4c62889ba10bc8024e661818d4aa257fcf87b43f Mon Sep 17 00:00:00 2001 From: Nekojimi Date: Wed, 18 May 2022 20:45:18 +0100 Subject: [PATCH] Add new song requests system, and update request messages. Also set the Discord presence to the currently playing song. --- .../java/moe/nekojimi/chords/Downloader.java | 33 ++-- src/main/java/moe/nekojimi/chords/Main.java | 152 +++++++++++++----- .../moe/nekojimi/chords/MusicHandler.java | 10 +- src/main/java/moe/nekojimi/chords/Song.java | 2 +- .../java/moe/nekojimi/chords/SongRequest.java | 133 +++++++++++++++ .../nekojimi/chords/commands/PlayCommand.java | 23 ++- 6 files changed, 284 insertions(+), 69 deletions(-) create mode 100644 src/main/java/moe/nekojimi/chords/SongRequest.java diff --git a/src/main/java/moe/nekojimi/chords/Downloader.java b/src/main/java/moe/nekojimi/chords/Downloader.java index 36b05c7..1152a9b 100644 --- a/src/main/java/moe/nekojimi/chords/Downloader.java +++ b/src/main/java/moe/nekojimi/chords/Downloader.java @@ -43,7 +43,7 @@ public class Downloader implements Consumer private final LinkedBlockingDeque workQueue = new LinkedBlockingDeque<>(); private final ThreadPoolExecutor exec = new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS, workQueue); // private Consumer next; - private BiConsumer messageHandler; + private BiConsumer messageHandler; private File downloadDir = null; @@ -56,19 +56,20 @@ public class Downloader implements Consumer public void accept(DownloadTask task) { // if already downloaded, just skip - if (task.getSong().isDownloaded()) + Song song = task.request.getSong(); + if (song.isDownloaded()) { - task.getDestination().accept(task.getSong()); + task.getDestination().accept(song); return; } downloadQueue.add(task); - getInfo(task.getSong()); + getInfo(song); exec.submit(() -> { try { - getFormats(task.getSong()); + getFormats(song); download(task); } catch (Exception ex) { @@ -196,7 +197,7 @@ public class Downloader implements Consumer private void download(DownloadTask task) { - Song song = task.song; + Song song = task.request.getSong(); chooseFormats(song); String formatCodes = ""; final List formats = song.getFormats(); @@ -205,7 +206,7 @@ public class Downloader implements Consumer try { - messageHandler.accept(song, null); + messageHandler.accept(task.request, null); String cmd = "/usr/bin/youtube-dl -x" + " -f " + formatCodes + "worstaudio/bestaudio/worst/best" + " --audio-format=wav" @@ -228,12 +229,12 @@ public class Downloader implements Consumer if (task.getDestination() != null) task.getDestination().accept(song); downloadQueue.remove(task); - messageHandler.accept(song, null); + messageHandler.accept(task.request, null); } catch (Exception ex) { Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, ex); if (messageHandler != null) - messageHandler.accept(song, ex); + messageHandler.accept(task.request, ex); downloadQueue.remove(task); } } @@ -252,12 +253,12 @@ public class Downloader implements Consumer return exec; } - public BiConsumer getMessageHandler() + public BiConsumer getMessageHandler() { return messageHandler; } - public void setMessageHandler(BiConsumer messageHandler) + public void setMessageHandler(BiConsumer messageHandler) { this.messageHandler = messageHandler; } @@ -270,18 +271,18 @@ public class Downloader implements Consumer public static class DownloadTask { - private final Song song; + private final SongRequest request; private final Consumer destination; - public DownloadTask(Song song, Consumer destination) + public DownloadTask(SongRequest request, Consumer destination) { - this.song = song; + this.request = request; this.destination = destination; } - public Song getSong() + public SongRequest getSong() { - return song; + return request; } public Consumer getDestination() diff --git a/src/main/java/moe/nekojimi/chords/Main.java b/src/main/java/moe/nekojimi/chords/Main.java index a47d741..6d53596 100644 --- a/src/main/java/moe/nekojimi/chords/Main.java +++ b/src/main/java/moe/nekojimi/chords/Main.java @@ -6,10 +6,13 @@ package moe.nekojimi.chords; import java.io.File; +import java.net.MalformedURLException; import java.net.URL; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import javax.security.auth.login.LoginException; import moe.nekojimi.chords.commands.*; import moe.nekojimi.musicsearcher.Result; @@ -17,6 +20,7 @@ import moe.nekojimi.musicsearcher.providers.MetaSearcher; import moe.nekojimi.musicsearcher.providers.Searcher; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; +import net.dv8tion.jda.api.MessageBuilder; import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.events.guild.voice.GuildVoiceLeaveEvent; import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; @@ -33,7 +37,6 @@ import net.dv8tion.jda.api.utils.cache.CacheFlag; public class Main extends ListenerAdapter { - private MusicHandler musicHandler; private final Downloader downloader; private final Searcher searcher; @@ -41,6 +44,7 @@ public class Main extends ListenerAdapter private final Map commands = new HashMap<>(); private final Command helpCommand; + private PlayCommand playCommand; private VoiceChannel currentVoiceChannel = null; @@ -56,13 +60,14 @@ public class Main extends ListenerAdapter // We need messages in guilds to accept commands from users GatewayIntent.GUILD_MESSAGES, // We need voice states to connect to the voice channel - GatewayIntent.GUILD_VOICE_STATES + GatewayIntent.GUILD_VOICE_STATES, + GatewayIntent.GUILD_MEMBERS ); JDABuilder builder = JDABuilder.createDefault(args[0], intents); // Disable parts of the cache - builder.disableCache(CacheFlag.MEMBER_OVERRIDES, CacheFlag.VOICE_STATE); + builder.disableCache(CacheFlag.MEMBER_OVERRIDES); // Enable the bulk delete event builder.setBulkDeleteSplittingEnabled(false); // Disable compression (not recommended) @@ -73,48 +78,63 @@ public class Main extends ListenerAdapter final Main listener = new Main(); builder.addEventListeners(listener); + builder.setAutoReconnect(true); JDA jda = builder.build(); listener.setJda(jda); } + private final Consumer nowPlayingConsumer = (Song song) -> + { + if (song != null) + jda.getPresence().setActivity(Activity.of(Activity.ActivityType.LISTENING, song.toString())); + else + jda.getPresence().setActivity(null); + }; - public Main() + private final BiConsumer downloaderMessageHandler = (SongRequest request, Exception ex) -> { - log("INFO", "Starting up..."); - downloader = new Downloader(); - downloader.setMessageHandler((Song song, Exception ex) -> - { - TextChannel channel = jda.getTextChannelById(song.getRequestedIn()); + Song song = request.getSong(); +// TextChannel channel = jda.getTextChannelById(song.getRequestedIn()); // String bracketNo = "[" + song.getNumber() + "] "; - if (channel != null) - if (ex == null) - if (song.getLocation() != null) - { - channel.sendMessage(/*bracketNo + */"Finished downloading " + song + " for " + song.getRequestedBy() + ", added to queue!").queue(); - log("DOWN", "Downloaded " + song); - } else - { - 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(); - log("DOWN", "Downloading " + song + "..."); - } - else + if (ex == null) + if (song.getLocation() != null) + { + request.respond("Finished downloading " + song + ", added to queue!"); + log("DOWN", "Downloaded " + song); + } else + { + Format format = song.getBestFormat(); + String formatDetails = ""; + if (format != null) { - channel.sendMessage(/*bracketNo + */"Failed to download " + song + " for " + song.getRequestedBy() + "! Reason: " + ex.getMessage()).queue(); - log("DOWN", "Failed to download " + song + "! Reason: " + ex.getMessage()); + final int bitrate = format.getBitrate() / 1000; + final long size = format.getSize(); + String sizeFmt = (size <= 0 ? "?.??" : String.format("%.2f", size / (1024.0 * 1024.0))) + "MiB"; + String bitFmt = (bitrate <= 0 ? "??" : bitrate) + "k"; + formatDetails = " (" + bitFmt + ", " + sizeFmt + ")"; } + request.respond("Now downloading " + song + formatDetails + " ..."); + log("DOWN", "Downloading " + song + "..."); + } + else + { + request.respond("Failed to download " + song + "! Reason: " + ex.getMessage()); + log("DOWN", "Failed to download " + song + "! Reason: " + ex.getMessage()); + } + + }; - }); + public Main() + { + log("INFO", "Starting up..."); + downloader = new Downloader(); + downloader.setMessageHandler(downloaderMessageHandler); searcher = MetaSearcher.loadYAML(new File("searchproviders.yml")); addCommand(new JoinCommand(this)); addCommand(new LeaveCommand(this)); - addCommand(new PlayCommand(this)); + playCommand = new PlayCommand(this); + addCommand(playCommand); addCommand(new QueueCommand(this)); addCommand(new RemoveCommand(this)); addCommand(new RestartCommand(this)); @@ -125,6 +145,7 @@ public class Main extends ListenerAdapter log("INFO", "Started OK!"); } + private void addCommand(Command command) { commands.put(command.getKeyword(), command); @@ -166,6 +187,15 @@ public class Main extends ListenerAdapter log("MESG", "G:" + guild.getName() + " A:" + author.getName() + " C:" + content); + try + { + URL parseURL = new URL(content.trim()); + playCommand.call(event, List.of(parseURL.toExternalForm())); + } catch (MalformedURLException ex) + { + // not a URL, then + } + try { String[] split = content.split("\\s+", 2); @@ -185,7 +215,9 @@ public class Main extends ListenerAdapter Command command = commands.get(cmd); command.call(event, List.of(arg)); } else + { helpCommand.call(event, List.of(arg)); + } } catch (Exception ex) { event.getChannel().sendMessage("Error in command! " + ex.getMessage()).queue(); @@ -193,24 +225,57 @@ public class Main extends ListenerAdapter } } - public Song queueDownload(final URL url, GuildMessageReceivedEvent event) + public Song queueDownload(SongRequest request) { - Song song = new Song(url); - song.setRequestedBy(event.getAuthor().getName()); - song.setRequestedIn(event.getChannel().getId()); + Song song; + if (request.getUrl() != null) + { + song = new Song(request.getUrl()); + } else + { + // interpret search result + throw new UnsupportedOperationException("Not supported yet."); + } + if (request.getRequestMessage() != null) + { + song.setRequestedBy(request.getRequestMessage().getAuthor().getName()); + song.setRequestedIn(request.getRequestMessage().getChannel().getId()); + } song.setNumber(trackNumber); trackNumber++; - downloader.accept(new Downloader.DownloadTask(song, musicHandler)); + request.setSong(song); + downloader.accept(new Downloader.DownloadTask(request, musicHandler)); + request.respond("Request pending..."); return song; } - public Song queueDownload(Result res, GuildMessageReceivedEvent event) +// public Song queueDownload(final URL url, GuildMessageReceivedEvent event) +// { +// Song song = new Song(url); +// song.setRequestedBy(event.getAuthor().getName()); +// song.setRequestedIn(event.getChannel().getId()); +// song.setNumber(trackNumber); +// trackNumber++; +// downloader.accept(new Downloader.DownloadTask(song, musicHandler)); +// return song; +// } +// +// public Song queueDownload(Result res, GuildMessageReceivedEvent event) +// { +// Song song = queueDownload(res.getLink(), event); +// song.setArtist(res.getArtist()); +// song.setTitle(res.getTitle()); +// song.setNumber(trackNumber); +// return song; +// } + public void setStatus(Song nowPlaying) { - Song song = queueDownload(res.getLink(), event); - song.setArtist(res.getArtist()); - song.setTitle(res.getTitle()); - song.setNumber(trackNumber); - return song; + jda.getPresence().setActivity(Activity.listening(nowPlaying.toString())); + } + + public void clearStatus() + { + jda.getPresence().setActivity(null); } /** @@ -236,6 +301,8 @@ public class Main extends ListenerAdapter // Connect to the voice channel audioManager.openAudioConnection(channel); currentVoiceChannel = channel; + + musicHandler.setNowPlayingConsumer(nowPlayingConsumer); } public void disconnect() @@ -281,7 +348,6 @@ public class Main extends ListenerAdapter return currentVoiceChannel; } - public int getTrackNumber() { return trackNumber; diff --git a/src/main/java/moe/nekojimi/chords/MusicHandler.java b/src/main/java/moe/nekojimi/chords/MusicHandler.java index b0b6dc4..16a5627 100644 --- a/src/main/java/moe/nekojimi/chords/MusicHandler.java +++ b/src/main/java/moe/nekojimi/chords/MusicHandler.java @@ -38,6 +38,12 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer private int byteCount; private boolean arrayErr = false; + private Consumer nowPlayingConsumer; + + public void setNowPlayingConsumer(Consumer nowPlayingConsumer) + { + this.nowPlayingConsumer = nowPlayingConsumer; + } public MusicHandler() { @@ -103,6 +109,8 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer if (currentSong == null) return false; System.out.println("Playing song " + currentSong.getLocation().getAbsolutePath()); + if (nowPlayingConsumer != null) + nowPlayingConsumer.accept(currentSong); arrayErr = false; in = AudioSystem.getAudioInputStream(currentSong.getLocation()); AudioFormat decodedFormat = AudioSendHandler.INPUT_FORMAT; @@ -177,7 +185,7 @@ public class MusicHandler implements AudioSendHandler, Closeable, Consumer byte[] bytes = new byte[bytesToRead]; // byte[] bytes = din.readNBytes(bytesToRead); int read = din.read(bytes); - System.out.println("Wanted: " + byteCount + " Space:" + space + " Available: " + din.available() + " To read: " + bytesToRead + " Read: " + read); +// System.out.println("Wanted: " + byteCount + " Space:" + space + " Available: " + din.available() + " To read: " + bytesToRead + " Read: " + read); if (read < 0) return false; // queue.add(bytes); diff --git a/src/main/java/moe/nekojimi/chords/Song.java b/src/main/java/moe/nekojimi/chords/Song.java index 30ffb2c..45c44ea 100644 --- a/src/main/java/moe/nekojimi/chords/Song.java +++ b/src/main/java/moe/nekojimi/chords/Song.java @@ -175,7 +175,7 @@ public class Song if (ret.isEmpty()) ret = "track " + number; - return "[" + number + "] " + ret; + return /*"[" + number + "] " + */ ret; // return url.toExternalForm(); } diff --git a/src/main/java/moe/nekojimi/chords/SongRequest.java b/src/main/java/moe/nekojimi/chords/SongRequest.java new file mode 100644 index 0000000..6ae1fd1 --- /dev/null +++ b/src/main/java/moe/nekojimi/chords/SongRequest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2022 jimj316 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package moe.nekojimi.chords; + +import java.net.URL; +import java.util.List; +import moe.nekojimi.musicsearcher.Result; +import net.dv8tion.jda.api.MessageBuilder; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.requests.restaction.MessageAction; +import net.dv8tion.jda.internal.entities.DataMessage; + +/** + * + * @author jimj316 + */ +public class SongRequest +{ + + private Message requestMessage; + private Message responseMessage; + + private String query; + private URL url; + + private List searchResults; + + private Result result; + + private Song song; + + @SuppressWarnings("null") + public void respond(String text) + { + MessageAction action = null; + + if (responseMessage == null) + { + action = requestMessage.reply(text); + } else + { + action = responseMessage.editMessage(text); + } + + responseMessage = action.complete(); + } + + + public List getSearchResults() + { + return searchResults; + } + + public void setSearchResults(List searchResults) + { + this.searchResults = searchResults; + } + + public Result getResult() + { + return result; + } + + public void setResult(Result result) + { + this.result = result; + } + + public Message getRequestMessage() + { + return requestMessage; + } + + public void setRequestMessage(Message requestMessage) + { + this.requestMessage = requestMessage; + } + + public Message getResponseMessage() + { + return responseMessage; + } + + public void setResponseMessage(Message responseMessage) + { + this.responseMessage = responseMessage; + } + + public String getQuery() + { + return query; + } + + public void setQuery(String query) + { + this.query = query; + } + + public URL getUrl() + { + return url; + } + + public void setUrl(URL url) + { + this.url = url; + } + + public Song getSong() + { + return song; + } + + public void setSong(Song song) + { + this.song = song; + } + +} diff --git a/src/main/java/moe/nekojimi/chords/commands/PlayCommand.java b/src/main/java/moe/nekojimi/chords/commands/PlayCommand.java index 1ff4031..3e221e4 100644 --- a/src/main/java/moe/nekojimi/chords/commands/PlayCommand.java +++ b/src/main/java/moe/nekojimi/chords/commands/PlayCommand.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import moe.nekojimi.chords.Main; +import moe.nekojimi.chords.SongRequest; import moe.nekojimi.musicsearcher.Query; import moe.nekojimi.musicsearcher.Result; import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; @@ -35,7 +36,7 @@ public class PlayCommand extends Command private static final double SEARCH_SCORE_THRESHOLD_DISPLAY = 0.6; private static final double SEARCH_SCORE_THRESHOLD_AUTOPLAY = 9999; // disable autoplay it sucks - private List lastSearchResults; +// private List lastSearchResults; public PlayCommand(Main main) { @@ -45,24 +46,28 @@ public class PlayCommand extends Command @Override public void call(GuildMessageReceivedEvent event, List arg) { + SongRequest request = new SongRequest(); + request.setRequestMessage(event.getMessage()); try { final URL url = new URL(arg.get(0)); - bot.queueDownload(url, event); + request.setUrl(url); + bot.queueDownload(request); } catch (MalformedURLException mux) { // not a URL, try parsing it as a search result - if (lastSearchResults != null && !lastSearchResults.isEmpty()) + if (request.getSearchResults() != null && !request.getSearchResults().isEmpty()) { try { int index = Integer.parseInt(arg.get(0)); - int size = lastSearchResults.size(); + int size = request.getSearchResults().size(); if (index >= 1 && index <= size) { - Result res = lastSearchResults.get(index - 1); - bot.queueDownload(res, event); + Result result = request.getSearchResults().get(index - 1); + request.setResult(result); + bot.queueDownload(request); // event.getChannel().sendMessage("Song removed.").queue(); } else if (size > 1) event.getChannel().sendMessage("That's not a number between 1 and " + size + "!").queue(); @@ -87,7 +92,8 @@ public class PlayCommand extends Command return; } - lastSearchResults = results; + request.setSearchResults(results); +// lastSearchResults = results; if (results.isEmpty()) { @@ -97,7 +103,8 @@ public class PlayCommand extends Command if (results.get(0).getScore() >= SEARCH_SCORE_THRESHOLD_AUTOPLAY) { - bot.queueDownload(results.get(0).getLink(), event); + request.setResult(results.get(0)); + bot.queueDownload(request); return; }