Initial music rewrite

This commit is contained in:
Anya 2022-11-09 16:11:12 -08:00
parent 93c9a797ee
commit 9544aa677f
1 changed files with 156 additions and 288 deletions

View File

@ -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<String, Map.Entry<AudioPlayer, TrackManager>> players = new HashMap<>();
private final AudioPlayerManager audioPlayerManager = new DefaultAudioPlayerManager();
private final Map<String, Map.Entry<AudioPlayer, TrackManager>> 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<AudioInfo> 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<CommandInfo> getSlashCommandInfo() {
List<CommandInfo> 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;
}
}