kiafumi/src/main/java/moe/oko/Kiafumi/command/music/MusicCommand.java

235 lines
11 KiB
Java

package moe.oko.Kiafumi.command.music;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import moe.oko.Kiafumi.command.CommandClass;
import moe.oko.Kiafumi.model.audio.AudioInfo;
import moe.oko.Kiafumi.model.audio.AudioPlayerSendHandler;
import moe.oko.Kiafumi.model.audio.TrackManager;
import moe.oko.Kiafumi.util.CommandInfo;
import moe.oko.Kiafumi.util.CommandType;
import moe.oko.Kiafumi.util.EmbedUI;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.commands.OptionType;
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.info;
import static moe.oko.Kiafumi.util.LoggingManager.slashLog;
/**
* Music Command
* Some code may be taken from SHIRO Project (ISC License still applies)
* @author Kay, oko
*/
public class MusicCommand extends CommandClass {
private final AudioPlayerManager audioPlayerManager = new DefaultAudioPlayerManager();
private final Map<String, Map.Entry<AudioPlayer, TrackManager>> players = new HashMap<>();
public MusicCommand() { AudioSourceManagers.registerRemoteSources(audioPlayerManager); }
@Override
public boolean isEnabled() { return true; }
@Override
public String getName() { return "Music"; }
@Override
public void newCommand(String name, SlashCommandInteractionEvent e) {
switch (name) {
case "play" -> {
e.deferReply().queue();
var input = e.getOption("url").getAsString();
slashLog(e, "with search \"%s\".".formatted(input));
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("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 (%s/%s)"
.formatted(e.getUser().getName(), votes, requiredVotes)).queue();
}
}
}
}
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();
final short[] trackSize = {-1};
queuedTracks.forEach(audioInfo -> {
trackList.append(buildQueueString(audioInfo));
trackSize[0]++;
});
var eb = new EmbedBuilder().setColor(EmbedUI.SUCCESS);
if (trackList.length() >= 990) {
eb.addField("Queue", "**>** " + trackList.toString(), false);
} else {
eb.addField("Queue", "**>** " + getPlayer(e.getGuild()).getPlayingTrack().getInfo().title
+ "\n" + trackSize[0] + " other tracks..", false).build();
}
e.getHook().sendMessageEmbeds(eb.build()).queue();
}
}
}
}
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.getTracks().get(0), author);
var trackInfo = audioPlaylist.getTracks().get(0).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());
} else {
for (AudioTrack audioTrack : audioPlaylist.getTracks()) {
getTrackManager(server).queue(audioTrack, 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("Unable to play track.").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<>();
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;
}
}