From 9544aa677f7c6968c093cdedaa53368b2ab3ed96 Mon Sep 17 00:00:00 2001 From: oko Date: Wed, 9 Nov 2022 16:11:12 -0800 Subject: [PATCH] Initial music rewrite --- .../Kiafumi/command/music/MusicCommand.java | 444 ++++++------------ 1 file changed, 156 insertions(+), 288 deletions(-) diff --git a/src/main/java/moe/oko/Kiafumi/command/music/MusicCommand.java b/src/main/java/moe/oko/Kiafumi/command/music/MusicCommand.java index 70fe12a..e76032d 100644 --- a/src/main/java/moe/oko/Kiafumi/command/music/MusicCommand.java +++ b/src/main/java/moe/oko/Kiafumi/command/music/MusicCommand.java @@ -27,322 +27,190 @@ import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.util.*; +import java.util.concurrent.TimeUnit; import static moe.oko.Kiafumi.util.LoggingManager.slashLog; /** * Music Command - * Most code taken from SHIRO Project (ISC License still applies) - * @author Kay + * Some code may be taken from SHIRO Project (ISC License still applies) + * @author Kay, oko */ public class MusicCommand extends CommandClass { - private static final int PLAYLIST_LIMIT = 200; - private static final AudioPlayerManager myManager = new DefaultAudioPlayerManager(); - private static final Map> players = new HashMap<>(); + private final AudioPlayerManager audioPlayerManager = new DefaultAudioPlayerManager(); + private final Map> players = new HashMap<>(); - private static final String CD = "\uD83D\uDCBF"; - private static final String DVD = "\uD83D\uDCC0"; - private static final String MIC = "\uD83C\uDFA4 **|>** "; - - private static final String QUEUE_TITLE = "__%s has added %d new track%s to the Queue:__"; - private static final String QUEUE_DESCRIPTION = "%s **|>** %s\n%s\n%s %s\n%s"; - private static final String QUEUE_INFO = "Info about the Queue: (Size - %d)"; - private static final String ERROR = "Error while loading \"%s\""; - - private boolean enabled = true; - - public MusicCommand() { - AudioSourceManagers.registerRemoteSources(myManager); - } + public MusicCommand() { AudioSourceManagers.registerRemoteSources(audioPlayerManager); } @Override - public boolean isEnabled() { - return enabled; - } + public boolean isEnabled() { return true; } @Override - public String getName() { - return "Music"; - } + public String getName() { return "Music"; } @Override public void newCommand(String name, SlashCommandInteractionEvent e) { - if(e.getGuild() != null) { - switch (name) { - case "nowplaying": - slashLog(e); - e.deferReply().queue(); - if (!hasPlayer(e.getGuild()) || getPlayer(e.getGuild()).getPlayingTrack() == null) { // No song is playing - e.getHook().sendMessage("No song is playing.").queue(); - } else { - var track = getPlayer(e.getGuild()).getPlayingTrack(); - //Works - var eb = new EmbedBuilder(); - eb.setColor(EmbedUI.SUCCESS); - eb.setTitle("Track Info"); - eb.setDescription("Currently Playing - " + track.getInfo().title); - eb.addField("Time", "["+ getTimestamp(track.getPosition()) + "/" + getTimestamp(track.getInfo().length) + "]", false); - eb.addField("Creator", track.getInfo().author, false); - eb.addField("Queued By", getTrackManager(e.getGuild()).getTrackInfo(track).getAuthor().getUser().getName(), false); - e.getHook().sendMessageEmbeds(eb.build()).queue(); - } - break; - case "queue": - slashLog(e); - e.deferReply().queue(); - if (!hasPlayer(e.getGuild()) || getTrackManager(e.getGuild()).getQueuedTracks().isEmpty()) { - e.getHook().sendMessage("The queue is empty.").queue(); - } else { - var sb = new StringBuilder(); - Set queue = getTrackManager(e.getGuild()).getQueuedTracks(); - queue.forEach(audioInfo -> sb.append(buildQueueMessage(audioInfo))); - var embedTitle = String.format(QUEUE_INFO, queue.size()); + switch (name) { + case "play" -> { + e.deferReply().queue(); + var input = e.getOption("url").getAsString(); + slashLog(e, "with search \"%s\".".formatted(input)); - if (sb.length() <= 1960) { - var eb = new EmbedBuilder(); - eb.setColor(EmbedUI.SUCCESS); - eb.setTitle(embedTitle); - eb.addField("In Queue", "**>** " + sb.toString(), false); - e.getHook().sendMessageEmbeds(eb.build()).queue(); - } else { - var qFile = new File("queue.txt"); - try { - FileUtils.write(qFile, sb.toString(), "UTF-8", false); - e.getHook().sendMessage("**Queue was too large to put into text, linked file below contains all songs queued.").queue(); - e.getHook().sendFile(qFile, qFile.getName(), null).queue(); - } catch (IOException ex) { - ex.printStackTrace(); - } - if (!qFile.delete()) { // Delete the queue file after we're done - qFile.deleteOnExit(); - } - } - } - break; - case "skip": - slashLog(e); - e.deferReply().queue(); - if (isIdle(e.getHook(), e.getGuild())) return; - - if (isCurrentDj(e.getMember())) { - forceSkipTrack(e.getGuild(), e.getHook()); - } else { - AudioInfo info = getTrackManager(e.getGuild()).getTrackInfo(getPlayer(e.getGuild()).getPlayingTrack()); - if (info.hasVoted(e.getUser())) { - e.getHook().sendMessage("\u26A0 You've already voted to skip this song!").queue(); - } else { - // Determine requisite votes. - int votes = info.getSkips(); - int channelUsers = info.getAuthor().getVoiceState().getChannel().getMembers().size()-1; - int required = channelUsers/2; - if (votes+1 >= required){ - getPlayer(e.getGuild()).stopTrack(); - e.getHook().sendMessage("\u23E9 Skipping current track.").queue(); - } else { - info.addSkip(e.getUser()); - e.getHook().sendMessage("**" + e.getUser().getName() + "** voted to skip the track. [" + (votes+1) + "/" + (required) + "]").queue(); - } - } - } - break; - case "forceskip": - slashLog(e); - e.deferReply().queue(); - if (isIdle(e.getHook(), e.getGuild())) return; - if (isCurrentDj(e.getMember()) || isDj(e.getMember())) { - forceSkipTrack(e.getGuild(), e.getHook()); - } else { - e.getHook().sendMessage("You don't have permission to do that!\n" - + "Use **/skip** to cast a vote!").queue(); - } - break; - case "reset": - slashLog(e); - e.deferReply().queue(); - if (!e.getMember().getPermissions().contains(Permission.ADMINISTRATOR) && !e.getMember().isOwner()) { - e.getHook().sendMessage("You don't have the required permissions to do that! [ADMIN]").queue(); - } else { - reset(e.getGuild()); - e.getHook().sendMessage("\uD83D\uDD04 Resetting the music player..").queue(); - } - break; - case "shuffle": - slashLog(e); - e.deferReply().queue(); - if (isIdle(e.getHook(), e.getGuild())) return; - - if (isDj(e.getMember())) { - getTrackManager(e.getGuild()).shuffleQueue(); - e.getHook().sendMessage("\u2705 Shuffled the queue!").queue(); - } else { - e.getHook().sendMessage("\u26D4 You don't have the permission to do that!").queue(); - } - break; - case "stop": - e.deferReply().queue(); - if(isIdle(e.getHook(), e.getGuild())) return; - - getTrackManager(e.getGuild()).purgeQueue(); + if(input.startsWith("https://")) + loadTrack(input, e.getMember(), e.getHook()); + else + loadTrack("ytsearch: " + input, e.getMember(), e.getHook()); + } + case "skip" -> { + e.deferReply().queue(); + slashLog(e); + if (isAdmin(e.getMember())) { getPlayer(e.getGuild()).stopTrack(); - e.getHook().sendMessage("Stopped the track. :boom:").queue(); - break; - case "play": - e.deferReply().queue(); - var input = e.getOption("url").getAsString(); - slashLog(e, "with search \"" + input + "\"."); - if(input.contains("https://")) { - loadTrack(input, e.getMember(), e.getHook()); - } else { - loadTrack("ytsearch: " + input, e.getMember(), e.getHook()); + e.getHook().sendMessage("Skipping the current track.").queue(); + } + else { + var info = getTrackManager(e.getGuild()).getTrackInfo(getPlayer(e.getGuild()).getPlayingTrack()); + if (info.hasVoted(e.getUser())) + e.getHook().setEphemeral(true).sendMessage("You've already voted to skip this track."); + else { + int votes = info.getSkips(); + int users = info.getAuthor().getVoiceState().getChannel().getMembers().size()-1; + int requiredVotes = users/2; + if (votes > requiredVotes) { + getPlayer(e.getGuild()).stopTrack(); + e.getHook().sendMessage("Skipping the current track.").queue(); + } else { + info.addSkip(e.getUser()); + e.getHook().sendMessage("**%s** has voted to skip the track".formatted(e.getUser().getName())).queue(); + } } - break; + } + } + case "nowplaying" -> { + e.deferReply().queue(); + slashLog(e); + if (!hasPlayer(e.getGuild()) || getPlayer(e.getGuild()).getPlayingTrack() == null) + e.getHook().sendMessage("No song is currently playing.").queue(); + else { + var track = getPlayer(e.getGuild()).getPlayingTrack().getInfo(); + e.getHook().sendMessageEmbeds(new EmbedBuilder() + .setColor(EmbedUI.SUCCESS) + .setAuthor("Now playing") + .setDescription("[%s](%s)".formatted(track.title, track.uri)) + .addField("Info", "Channel: %s\nLength: %s".formatted(track.author, getTimestamp(track.length)), false) + .setFooter("Requested by: " + e.getUser().getName()).build()).queue(); + } + } + case "queue" -> { + e.deferReply().queue(); + slashLog(e); + if (!hasPlayer(e.getGuild()) || getTrackManager(e.getGuild()).getQueuedTracks().isEmpty()) + e.getHook().sendMessage("There is nothing queued.").queue(); + else { + var trackList = new StringBuilder(); + var queuedTracks = getTrackManager(e.getGuild()).getQueuedTracks(); + queuedTracks.forEach(audioInfo -> trackList.append(buildQueueString(audioInfo))); + e.getHook().sendMessageEmbeds(new EmbedBuilder() + .setColor(EmbedUI.SUCCESS) + .addField("Queue", "**>** " + trackList.toString(), false).build()).queue(); + } } } } - private boolean hasPlayer(Guild guild) { - return players.containsKey(guild.getId()); + private void loadTrack(String input, Member author, InteractionHook hook) { + if (author.getVoiceState().getChannel() == null) { + hook.setEphemeral(true).sendMessage("You are not in a voice channel."); + return; + } + var server = author.getGuild(); + getPlayer(server); + audioPlayerManager.loadItemOrdered(server, input, new AudioLoadResultHandler() { + EmbedBuilder eb; + @Override + public void trackLoaded(AudioTrack audioTrack) { + var trackInfo = audioTrack.getInfo(); + eb = new EmbedBuilder() + .setColor(EmbedUI.SUCCESS) + .setAuthor("Playing") + .setDescription("[%s](%s)".formatted(trackInfo.title, trackInfo.uri)) + .addField("Info", "Channel: %s\nLength: %s".formatted(trackInfo.author, getTimestamp(trackInfo.length)), false) + .setFooter("Requested by: " + author.getEffectiveName()); + hook.sendMessageEmbeds(eb.build()).queue(); + getTrackManager(server).queue(audioTrack, author); + } + + @Override + public void playlistLoaded(AudioPlaylist audioPlaylist) { + if (input.startsWith("ytsearch:")) + getTrackManager(server).queue(audioPlaylist.getSelectedTrack(), author); + else { + for (int i = audioPlaylist.getTracks().indexOf(audioPlaylist.getSelectedTrack()) + 1; i < audioPlaylist.getTracks().size(); i++) + getTrackManager(server).queue(audioPlaylist.getTracks().get(i), author); + } + + eb = new EmbedBuilder() + .setColor(EmbedUI.SUCCESS) + .setAuthor("Loaded tracks") + .setDescription("**%s** tracks added to the queue.".formatted(audioPlaylist.getTracks().size())) + .setFooter("Requested by: " + author.getEffectiveName()); + hook.sendMessageEmbeds(eb.build()).queue(); + } + + @Override + public void noMatches() { + eb = new EmbedBuilder() + .setColor(EmbedUI.FAILURE) + .setAuthor("Error") + .setDescription("No matches were found."); + hook.sendMessageEmbeds(eb.build()).queue(); + } + + @Override + public void loadFailed(FriendlyException e) { hook.sendMessage(e.getLocalizedMessage()).queue(); } + }); + } + + private TrackManager getTrackManager(Guild server) { return players.get(server.getId()).getValue(); } + + private AudioPlayer getPlayer(Guild server) { + var player = hasPlayer(server) ? players.get(server.getId()).getKey() : createPlayer(server); + return player; + } + + private AudioPlayer createPlayer(Guild server) { + var newPlayer = audioPlayerManager.createPlayer(); + var manager = new TrackManager(newPlayer); + newPlayer.addListener(manager); + server.getAudioManager().setSendingHandler(new AudioPlayerSendHandler(newPlayer)); + players.put(server.getId(), new AbstractMap.SimpleEntry<>(newPlayer, manager)); + return newPlayer; + } + + private boolean hasPlayer(Guild server) { return players.containsKey(server.getId()); } + + private boolean isAdmin(Member member) { return member.hasPermission(Permission.ADMINISTRATOR); } + + private String getTimestamp(long ms) { + return String.format("%02d:%02d:%02d", ms/(3600*1000), + ms/(60*1000) % 60, + ms/1000 % 60); + } + private String buildQueueString(AudioInfo info) { + var trackInfo = info.getTrack().getInfo(); + return "%s [%s]\n".formatted(trackInfo.title, getTimestamp(trackInfo.length)); } @Override public List getSlashCommandInfo() { List cil = new ArrayList<>(); - CommandInfo ci = new CommandInfo("nowplaying", "Displays the song being currently played.", CommandType.COMMAND); - CommandInfo ci1 = new CommandInfo("queue", "Displays the songs currently queued.", CommandType.COMMAND); - CommandInfo ci2 = new CommandInfo("skip", "Votes to skip the song currently playing. Or just skips it if you're the DJ.", CommandType.COMMAND); - CommandInfo ci3 = new CommandInfo("forceskip", "Forcibly skips the song that is currently playing.", CommandType.COMMAND); - CommandInfo ci4 = new CommandInfo("reset", "Resets the song player. (Admin Only)", CommandType.COMMAND); - CommandInfo ci5 = new CommandInfo("play", "Plays a new song from a URL", CommandType.COMMAND); - ci5.addOption("url", "The URL or title of the song that you wanted to play.", OptionType.STRING, true); - cil.add(ci); - cil.add(ci1); - cil.add(ci2); - cil.add(ci3); - cil.add(ci4); - cil.add(ci5); + var playCommandList = new CommandInfo("play", "Adds a new track to the queue.", CommandType.COMMAND); + playCommandList.addOption("url", "The URL or title of the track you want to play.", OptionType.STRING, true); + cil.add(playCommandList); + cil.add(new CommandInfo("skip", "Votes to skip the current track.", CommandType.COMMAND)); + cil.add(new CommandInfo("nowplaying", "Displays the currently played track." , CommandType.COMMAND)); + cil.add(new CommandInfo("queue", "Displays the tracks currently queued.", CommandType.COMMAND)); return cil; } - - private AudioPlayer getPlayer(Guild guild) { - AudioPlayer p; - if (hasPlayer(guild)) { - p = players.get(guild.getId()).getKey(); - } else { - p = createPlayer(guild); - } - return p; - } - - private TrackManager getTrackManager(Guild guild) { - return players.get(guild.getId()).getValue(); - } - - private AudioPlayer createPlayer(Guild guild) { - var nPlayer = myManager.createPlayer(); - var manager = new TrackManager(nPlayer); - nPlayer.addListener(manager); - guild.getAudioManager().setSendingHandler(new AudioPlayerSendHandler(nPlayer)); - players.put(guild.getId(), new AbstractMap.SimpleEntry<>(nPlayer, manager)); - return nPlayer; - } - - private void reset(Guild guild) { - players.remove(guild.getId()); - getPlayer(guild).destroy(); - getTrackManager(guild).purgeQueue(); - guild.getAudioManager().closeAudioConnection(); - } - - private void loadTrack(String identifier, Member author, InteractionHook chat) { - if (author.getVoiceState().getChannel() == null) { - chat.sendMessage("You are not in a Voice Channel!").queue(); - return; - } - - Guild guild = author.getGuild(); - getPlayer(guild); // Make sure this guild has a player. - - myManager.loadItemOrdered(guild, identifier, new AudioLoadResultHandler() { - - @Override - public void trackLoaded(AudioTrack track) { - var eb = new EmbedBuilder(); - eb.setColor(EmbedUI.SUCCESS); - eb.setTitle(author.getEffectiveName() + " has loaded " + track.getInfo().title); - eb.addField("Track Info", "Creator: " + track.getInfo().author + "\nLength: " + getTimestamp(track.getInfo().length), false); - chat.sendMessageEmbeds(eb.build()).queue(); - getTrackManager(guild).queue(track, author); - } - - @Override - public void playlistLoaded(AudioPlaylist playlist) { - if (playlist.getSelectedTrack() != null) { - trackLoaded(playlist.getSelectedTrack()); - } else if (playlist.isSearchResult()) { - trackLoaded(playlist.getTracks().get(0)); - } else { - EmbedBuilder eb = new EmbedBuilder(); - eb.setColor(EmbedUI.SUCCESS); - eb.setTitle(author.getEffectiveName() + " has loaded " + playlist.getTracks().size() + " songs into the queue"); - chat.sendMessageEmbeds(eb.build()).queue(); - for (int i = 0; i < Math.min(playlist.getTracks().size(), PLAYLIST_LIMIT); i++) { - getTrackManager(guild).queue(playlist.getTracks().get(i), author); - } - } - } - - @Override - public void noMatches() { - chat.sendMessage("\u26A0 No playable tracks were found. (If searching, try the command 'ytplay' instead!)").queue(); - } - - @Override - public void loadFailed(FriendlyException exception) { - chat.sendMessage("\u26D4 " + exception.getLocalizedMessage()).queue(); - } - }); - //tryToDelete(msg); - } - - private boolean isDj(Member member) { - return member.getRoles().stream().anyMatch(r -> r.getName().equals("DJ")); - } - - private boolean isCurrentDj(Member member) { - return getTrackManager(member.getGuild()).getTrackInfo(getPlayer(member.getGuild()).getPlayingTrack()).getAuthor().equals(member); - } - - private boolean isIdle(InteractionHook chat, Guild guild) { - if (!hasPlayer(guild) || getPlayer(guild).getPlayingTrack() == null) { - chat.sendMessage("No music is being played at the moment!").queue(); - return true; - } - return false; - } - - private void forceSkipTrack(Guild guild, InteractionHook chat) { - getPlayer(guild).stopTrack(); - chat.sendMessage("\u23E9 Skipping track!").queue(); - } - - private String buildQueueMessage(AudioInfo info) { - var trackInfo = info.getTrack().getInfo(); - var title = trackInfo.title; - long length = trackInfo.length; - return "`[ " + getTimestamp(length) + " ]` " + title + "\n"; - } - - private String getTimestamp(long milis) { - long seconds = milis / 1000; - long hours = Math.floorDiv(seconds, 3600); - seconds = seconds - (hours * 3600); - long mins = Math.floorDiv(seconds, 60); - seconds = seconds - (mins * 60); - return (hours == 0 ? "" : hours + ":") + String.format("%02d", mins) + ":" + String.format("%02d", seconds); - } - - private String getOrNull(String s) { - return s.isEmpty() ? "N/A" : s; - } }