235 lines
11 KiB
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;
|
|
}
|
|
}
|