Add unit tests and refactor music service classes
This commit is contained in:
1 parent
b8786c8dc9
commit
1b981abdf7
13 files changed
+1085
-133
No files matched your search
@@ -24,11 +24,12 @@ import org.apache.logging.log4j.Logger;
|
||||
* Per-guild music player and queue owner.
|
||||
*
|
||||
* <p>The shared {@link AudioPlayerManager} and the manager cache live on {@link MusicServiceImpl}.
|
||||
* Obtain instances via {@link me.bobert.netneutrality.music.MusicService#get(Guild)}.
|
||||
* Obtain instances via {@link MusicService#get(Guild)}.
|
||||
*/
|
||||
public class MusicManager {
|
||||
public class MusicManager implements MusicManagerCallback {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(MusicManager.class);
|
||||
static final int DEFAULT_VOLUME = 50;
|
||||
private final Guild guild;
|
||||
private final AudioPlayerManager playerManager;
|
||||
private final AudioPlayer player;
|
||||
@@ -36,6 +37,7 @@ public class MusicManager {
|
||||
private final int baseVolume;
|
||||
private final GuildService guildService;
|
||||
private final TaskManager taskManager;
|
||||
private final VoteSkipService voteSkipService;
|
||||
|
||||
MusicManager(Guild guild, AudioPlayerManager playerManager, GuildService guildService, TaskManager taskManager) {
|
||||
this.guild = guild;
|
||||
@@ -44,7 +46,8 @@ public class MusicManager {
|
||||
this.scheduler = new TrackScheduler(this);
|
||||
this.guildService = guildService;
|
||||
this.taskManager = taskManager;
|
||||
this.baseVolume = Integer.parseInt(guildService.getGuildSettingService().getSetting(GSettings.MUSIC_VOLUME));
|
||||
this.voteSkipService = new VoteSkipService();
|
||||
this.baseVolume = parseBaseVolume(guildService);
|
||||
|
||||
guild.getAudioManager().setSendingHandler(new AudioPlayerSendHandler(player));
|
||||
player.addListener(scheduler);
|
||||
@@ -86,7 +89,7 @@ public class MusicManager {
|
||||
guild.getAudioManager().setSelfDeafened(true);
|
||||
player.setVolume(baseVolume);
|
||||
} else {
|
||||
throw new RuntimeException("Error connecting to voice channel");
|
||||
throw new IllegalArgumentException("audioChannel must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,11 +107,11 @@ public class MusicManager {
|
||||
return guild.getAudioManager().getConnectedChannel() != null;
|
||||
}
|
||||
|
||||
public synchronized AudioTrack getCurrentlyPlaying() {
|
||||
public AudioTrack getCurrentlyPlaying() {
|
||||
return player.getPlayingTrack();
|
||||
}
|
||||
|
||||
public synchronized boolean isCurrentlyPlaying() {
|
||||
public boolean isCurrentlyPlaying() {
|
||||
return player.getPlayingTrack() != null;
|
||||
}
|
||||
|
||||
@@ -132,58 +135,38 @@ public class MusicManager {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void stopTrack() {
|
||||
public void stopTrack() {
|
||||
player.stopTrack();
|
||||
}
|
||||
|
||||
public synchronized CommandExecutionResult resume() {
|
||||
public CommandExecutionResult resume() {
|
||||
return getQueueManager().resume();
|
||||
}
|
||||
|
||||
public synchronized CommandExecutionResult voteSkip(CommandContext context) {
|
||||
AudioChannel audioChannel = guild.getAudioManager().getConnectedChannel();
|
||||
if (audioChannel == null) {
|
||||
return CommandExecutionResult.warn("Bot is not connected to a voice channel");
|
||||
}
|
||||
|
||||
if (!isCurrentlyPlaying()) {
|
||||
return CommandExecutionResult.warn("No tracks are currently playing");
|
||||
}
|
||||
|
||||
TrackInfo info = getQueueManager().peekHeadOfQueue();
|
||||
if (info == null) {
|
||||
return CommandExecutionResult.warn("Queue is empty");
|
||||
}
|
||||
|
||||
// Minus the bot itself
|
||||
int connected = audioChannel.getMembers().size() - 1;
|
||||
|
||||
if (connected < 3) {
|
||||
getQueueManager().skip();
|
||||
|
||||
return CommandExecutionResult.info("Skipping current track since not enough people are connected to the voice channel.");
|
||||
}
|
||||
|
||||
double needed = connected / 2.1;
|
||||
|
||||
info.addSkip(context.getGuildUser());
|
||||
logger.debug("VoteSkip: guild={}, connected={}, needed={}, skips={}, user={}", context.getGuild().getId(), connected, needed, info.getSkips().size(), context.getGuildUser().getId());
|
||||
|
||||
if (info.getSkips().size() > needed) {
|
||||
return getQueueManager().skip();
|
||||
}
|
||||
|
||||
return CommandExecutionResult.success(String.format("Adding your vote to skip the current track. Current votes: %d/%d",
|
||||
info.getSkips().size(),
|
||||
(int) Math.ceil(needed))
|
||||
);
|
||||
return voteSkipService.voteSkip(context, getQueueManager(), audioChannel, isCurrentlyPlaying());
|
||||
}
|
||||
|
||||
public synchronized void setVolume(int volume) {
|
||||
public void setVolume(int volume) {
|
||||
player.setVolume(volume);
|
||||
}
|
||||
|
||||
public int getQueueSize() {
|
||||
return getQueueManager().getQueueSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueueEmpty() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
private static int parseBaseVolume(GuildService guildService) {
|
||||
try {
|
||||
return Integer.parseInt(guildService.getGuildSettingService().getSetting(GSettings.MUSIC_VOLUME));
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("Invalid MUSIC_VOLUME setting, falling back to default ({})", DEFAULT_VOLUME, e);
|
||||
return DEFAULT_VOLUME;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package me.bobert.netneutrality.music;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* Callback interface that breaks the circular dependency between
|
||||
* {@link QueueManager} / {@link TrackScheduler} and {@link MusicManager}.
|
||||
*/
|
||||
public interface MusicManagerCallback {
|
||||
|
||||
void onQueueEmpty();
|
||||
}
|
||||
@@ -76,12 +76,6 @@ public class QueueManager {
|
||||
}
|
||||
|
||||
public CommandExecutionResult clearQueue() {
|
||||
scheduler.getMusicManager().disconnect();
|
||||
|
||||
if (queue.isEmpty()) {
|
||||
return CommandExecutionResult.warn("Queue is empty!");
|
||||
}
|
||||
|
||||
synchronized (queue) {
|
||||
int queueSize = queue.size();
|
||||
if (queueSize == 0) {
|
||||
@@ -89,17 +83,14 @@ public class QueueManager {
|
||||
}
|
||||
|
||||
queue.clear();
|
||||
return CommandExecutionResult.success(String.format("Removed %d track%s from the queue", queueSize--, (queueSize > 1 ? "s" : "")));
|
||||
scheduler.getCallback().onQueueEmpty();
|
||||
return CommandExecutionResult.success(String.format("Removed %d track%s from the queue", queueSize, (queueSize > 1 ? "s" : "")));
|
||||
}
|
||||
}
|
||||
|
||||
public CommandExecutionResult removeTracksFromUser(Member member) {
|
||||
if (queue.stream().filter(trackInfo -> member.equals(trackInfo.getRequester())).toList().isEmpty()) {
|
||||
return CommandExecutionResult.info(String.format("No tracks added by user %s", member.getEffectiveName()));
|
||||
}
|
||||
|
||||
synchronized (queue) {
|
||||
int size = queue.stream().filter(trackInfo -> member.equals(trackInfo.getRequester())).toList().size();
|
||||
int size = (int) queue.stream().filter(trackInfo -> member.equals(trackInfo.getRequester())).count();
|
||||
if (size == 0) {
|
||||
return CommandExecutionResult.info(String.format("No tracks added by user %s", member.getEffectiveName()));
|
||||
}
|
||||
@@ -134,9 +125,6 @@ public class QueueManager {
|
||||
|
||||
public void queue(AudioTrack track, Member member) {
|
||||
TrackInfo info = new TrackInfo(track, member);
|
||||
if (queue.contains(info)) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (queue) {
|
||||
if (queue.contains(info)) {
|
||||
@@ -145,14 +133,12 @@ public class QueueManager {
|
||||
|
||||
if (player.getPlayingTrack() == null && !queue.isEmpty()) {
|
||||
queue.addFirst(info);
|
||||
}
|
||||
|
||||
if ((player.getPlayingTrack() == null && queue.isEmpty()) || player.getPlayingTrack() != null) {
|
||||
} else {
|
||||
queue.add(info);
|
||||
}
|
||||
|
||||
if (player.getPlayingTrack() == null) {
|
||||
AudioTrack toPlay = (queue.peek() == null ? null : queue.peek().getTrack());
|
||||
AudioTrack toPlay = queue.peek() == null ? null : queue.peek().getTrack();
|
||||
player.startTrack(toPlay, true);
|
||||
}
|
||||
}
|
||||
@@ -194,11 +180,6 @@ public class QueueManager {
|
||||
nbTrack = 1;
|
||||
}
|
||||
|
||||
if (queue.isEmpty()) {
|
||||
scheduler.getMusicManager().disconnect();
|
||||
return CommandExecutionResult.info("Queue is empty, leaving voice channel");
|
||||
}
|
||||
|
||||
synchronized (queue) {
|
||||
if (queue.isEmpty()) {
|
||||
scheduler.getMusicManager().disconnect();
|
||||
@@ -206,7 +187,7 @@ public class QueueManager {
|
||||
}
|
||||
|
||||
if (nbTrack > queue.size()) {
|
||||
nbTrack = 1;
|
||||
nbTrack = queue.size();
|
||||
}
|
||||
|
||||
logger.debug("Guild {}: skipping {} tracks", guild.getId(), nbTrack);
|
||||
@@ -226,10 +207,6 @@ public class QueueManager {
|
||||
}
|
||||
|
||||
public CommandExecutionResult shuffle() {
|
||||
if (queue.size() <= 3) {
|
||||
return CommandExecutionResult.warn("The queue is too small to be shuffled");
|
||||
}
|
||||
|
||||
synchronized (queue) {
|
||||
if (queue.size() <= 3) {
|
||||
return CommandExecutionResult.warn("The queue is too small to be shuffled");
|
||||
@@ -251,12 +228,8 @@ public class QueueManager {
|
||||
}
|
||||
|
||||
public CommandExecutionResult removeFromQueue(int index) {
|
||||
if (queue.isEmpty() || index < 1) {
|
||||
return CommandExecutionResult.none();
|
||||
}
|
||||
|
||||
synchronized (queue) {
|
||||
if (queue.isEmpty()) {
|
||||
if (queue.isEmpty() || index < 1) {
|
||||
return CommandExecutionResult.none();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package me.bobert.netneutrality.music;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import me.bobert.netneutrality.core.LogHandler;
|
||||
import me.bobert.netneutrality.core.context.Context;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Encapsulates the retry-or-skip logic for stuck tracks, extracted from
|
||||
* {@link TrackScheduler#onTrackStuck(AudioPlayer, AudioTrack, long)}.
|
||||
*/
|
||||
class StuckTrackRecoveryHandler {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(StuckTrackRecoveryHandler.class);
|
||||
|
||||
private final AudioPlayer player;
|
||||
private final QueueManager queueManager;
|
||||
|
||||
private AudioTrack lastStuckTrack;
|
||||
|
||||
StuckTrackRecoveryHandler(AudioPlayer player, QueueManager queueManager) {
|
||||
this.player = player;
|
||||
this.queueManager = queueManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the last-stuck state so that a subsequent stuck event on the same
|
||||
* identifier is treated as a first attempt again.
|
||||
*/
|
||||
void reset() {
|
||||
lastStuckTrack = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a stuck-track event. On the first occurrence for a given track
|
||||
* identifier the handler attempts to clone and re-queue the track; on the
|
||||
* second consecutive occurrence it skips to the next track.
|
||||
*/
|
||||
void handle(AudioTrack track, Context context) {
|
||||
if (lastStuckTrack == null || !lastStuckTrack.getIdentifier().equals(track.getIdentifier())) {
|
||||
lastStuckTrack = track;
|
||||
attemptRecovery(track, context);
|
||||
} else {
|
||||
context.reply("Current track is stuck, skipping to next..");
|
||||
queueManager.startNextTrack();
|
||||
}
|
||||
}
|
||||
|
||||
private void attemptRecovery(AudioTrack track, Context context) {
|
||||
logger.debug("Trying to load stuck track again: {}", track.getIdentifier());
|
||||
|
||||
context.async("Current track is stuck, trying to load it again", asyncContext -> {
|
||||
try {
|
||||
TrackInfo info = queueManager.peekHeadOfQueue();
|
||||
if (info != null && info.getTrack().getIdentifier().equals(track.getIdentifier())) {
|
||||
AudioTrack clone = track.makeClone();
|
||||
long lastPosition = track.getPosition();
|
||||
|
||||
player.stopTrack();
|
||||
|
||||
if (queueManager.peekHeadOfQueue() != null
|
||||
&& queueManager.peekHeadOfQueue().getTrack().getIdentifier().equals(track.getIdentifier())) {
|
||||
queueManager.removeHeadOfQueue();
|
||||
}
|
||||
|
||||
queueManager.queue(clone, info.getRequester());
|
||||
|
||||
AudioTrack playing = player.getPlayingTrack();
|
||||
if (playing != null && playing.getIdentifier().equals(track.getIdentifier())) {
|
||||
playing.setPosition(lastPosition);
|
||||
lastStuckTrack = null;
|
||||
} else {
|
||||
asyncContext.edit("Failed to seek track to last timestamp, the track will play from the beginning");
|
||||
}
|
||||
} else {
|
||||
asyncContext.edit("Failed to load last playing track, skipping to next..");
|
||||
queueManager.startNextTrack();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogHandler.log(e);
|
||||
asyncContext.edit("Failed to load last playing track, skipping to next..");
|
||||
queueManager.startNextTrack();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,4 @@
|
||||
package me.bobert.netneutrality.music;/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
package me.bobert.netneutrality.music;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
@@ -15,6 +10,7 @@ import net.dv8tion.jda.api.entities.Member;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
@@ -23,7 +19,7 @@ public class TrackInfo {
|
||||
private final AudioTrack track;
|
||||
private final Member member;
|
||||
private final Set<String> skips;
|
||||
private static final Logger logger = LogManager.getLogger(QueueManager.class);
|
||||
private static final Logger logger = LogManager.getLogger(TrackInfo.class);
|
||||
|
||||
public TrackInfo(AudioTrack track, Member member) {
|
||||
this.track = track;
|
||||
@@ -36,7 +32,11 @@ public class TrackInfo {
|
||||
}
|
||||
|
||||
public Set<String> getSkips() {
|
||||
return skips;
|
||||
return Collections.unmodifiableSet(skips);
|
||||
}
|
||||
|
||||
public int getSkipCount() {
|
||||
return skips.size();
|
||||
}
|
||||
|
||||
public void addSkip(Member member) {
|
||||
|
||||
@@ -10,8 +10,8 @@ import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
|
||||
import me.bobert.netneutrality.core.LogHandler;
|
||||
import me.bobert.netneutrality.context.MusicChannelContext;
|
||||
import me.bobert.netneutrality.core.LogHandler;
|
||||
import me.bobert.netneutrality.core.context.Context;
|
||||
import me.bobert.netneutrality.exceptions.ContextException;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
@@ -23,16 +23,19 @@ public class TrackScheduler extends AudioEventAdapter {
|
||||
private static final Logger logger = LogManager.getLogger(TrackScheduler.class);
|
||||
private final QueueManager queueManager;
|
||||
private final MusicManager manager;
|
||||
private final MusicManagerCallback callback;
|
||||
private final AudioPlayer player;
|
||||
private final Guild guild;
|
||||
private Context context;
|
||||
private AudioTrack lastStuckTrack = null;
|
||||
private volatile Context context;
|
||||
private final StuckTrackRecoveryHandler stuckTrackRecoveryHandler;
|
||||
|
||||
public TrackScheduler(MusicManager manager) {
|
||||
this.manager = manager;
|
||||
this.callback = manager;
|
||||
this.player = manager.getPlayer();
|
||||
this.guild = manager.getGuild();
|
||||
this.queueManager = new QueueManager(this);
|
||||
this.stuckTrackRecoveryHandler = new StuckTrackRecoveryHandler(player, queueManager);
|
||||
}
|
||||
|
||||
public QueueManager getQueueManager() {
|
||||
@@ -43,6 +46,10 @@ public class TrackScheduler extends AudioEventAdapter {
|
||||
return manager;
|
||||
}
|
||||
|
||||
public MusicManagerCallback getCallback() {
|
||||
return callback;
|
||||
}
|
||||
|
||||
public AudioPlayer getPlayer() {
|
||||
return player;
|
||||
}
|
||||
@@ -52,8 +59,9 @@ public class TrackScheduler extends AudioEventAdapter {
|
||||
}
|
||||
|
||||
public Member getDj() {
|
||||
if (context != null) {
|
||||
return context.getGuildUser();
|
||||
Context ctx = context;
|
||||
if (ctx != null) {
|
||||
return ctx.getGuildUser();
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -71,6 +79,10 @@ public class TrackScheduler extends AudioEventAdapter {
|
||||
return context;
|
||||
}
|
||||
|
||||
StuckTrackRecoveryHandler getStuckTrackRecoveryHandler() {
|
||||
return stuckTrackRecoveryHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerPause(AudioPlayer player) {
|
||||
// Player was paused
|
||||
@@ -84,6 +96,7 @@ public class TrackScheduler extends AudioEventAdapter {
|
||||
@Override
|
||||
public void onTrackStart(AudioPlayer player, AudioTrack track) {
|
||||
logger.debug("Guild {}: starting track: {}", guild.getId(), track.getIdentifier());
|
||||
stuckTrackRecoveryHandler.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,47 +135,6 @@ public class TrackScheduler extends AudioEventAdapter {
|
||||
@Override
|
||||
public void onTrackStuck(AudioPlayer player, AudioTrack track, long thresholdMs) {
|
||||
logger.error("Guild {}: currently playing track is stuck: {}", guild.getId(), track.getIdentifier());
|
||||
|
||||
if (lastStuckTrack == null || !lastStuckTrack.getIdentifier().equals(track.getIdentifier())) {
|
||||
lastStuckTrack = track;
|
||||
|
||||
logger.debug("Trying to load stuck track again: {}", track.getIdentifier());
|
||||
context.async("Current track is stuck, trying to load it again", asyncContext -> {
|
||||
try {
|
||||
TrackInfo info = queueManager.peekHeadOfQueue();
|
||||
if (info != null && info.getTrack().getIdentifier().equals(track.getIdentifier())) {
|
||||
AudioTrack clone = track.makeClone();
|
||||
long lastPosition = track.getPosition();
|
||||
|
||||
player.stopTrack();
|
||||
|
||||
if (queueManager.peekHeadOfQueue() != null && queueManager.peekHeadOfQueue().getTrack().getIdentifier().equals(track.getIdentifier())) {
|
||||
queueManager.removeHeadOfQueue();
|
||||
}
|
||||
|
||||
queueManager.queue(clone, info.getRequester());
|
||||
|
||||
AudioTrack playing = player.getPlayingTrack();
|
||||
if (playing != null && playing.getIdentifier().equals(track.getIdentifier())) {
|
||||
playing.setPosition(lastPosition);
|
||||
lastStuckTrack = null;
|
||||
} else {
|
||||
asyncContext.edit("Failed to seek track to last timestamp, the track will play from the beginning");
|
||||
}
|
||||
} else {
|
||||
asyncContext.edit("Failed to load last playing track, skipping to next..");
|
||||
queueManager.startNextTrack();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogHandler.log(e);
|
||||
asyncContext.edit("Failed to load last playing track, skipping to next..");
|
||||
queueManager.startNextTrack();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
context.reply("Current track is stuck, skipping to next..");
|
||||
queueManager.startNextTrack();
|
||||
}
|
||||
|
||||
stuckTrackRecoveryHandler.handle(track, context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package me.bobert.netneutrality.music;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class VoteSkipService {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(VoteSkipService.class);
|
||||
|
||||
static final double VOTE_THRESHOLD_DIVISOR = 2.1;
|
||||
static final int MIN_MEMBERS_FOR_VOTE = 3;
|
||||
|
||||
/**
|
||||
* Records a vote-skip from the invoking user and, if the threshold is met, skips the track.
|
||||
*
|
||||
* <p><b>Concurrency note:</b> The {@code addSkip} call and the subsequent threshold check are
|
||||
* <em>not</em> performed atomically. In practice this is acceptable because duplicate votes
|
||||
* from the same user are idempotent and the worst-case outcome is a single extra "vote
|
||||
* recorded" message before the skip fires on the next invocation.</p>
|
||||
*/
|
||||
public CommandExecutionResult voteSkip(CommandContext context, QueueManager queueManager, AudioChannel audioChannel, boolean isCurrentlyPlaying) {
|
||||
if (audioChannel == null) {
|
||||
return CommandExecutionResult.warn("Bot is not connected to a voice channel");
|
||||
}
|
||||
|
||||
if (!isCurrentlyPlaying) {
|
||||
return CommandExecutionResult.warn("No tracks are currently playing");
|
||||
}
|
||||
|
||||
TrackInfo info = queueManager.peekHeadOfQueue();
|
||||
if (info == null) {
|
||||
return CommandExecutionResult.warn("Queue is empty");
|
||||
}
|
||||
|
||||
// Minus the bot itself
|
||||
int connected = audioChannel.getMembers().size() - 1;
|
||||
|
||||
if (connected < MIN_MEMBERS_FOR_VOTE) {
|
||||
queueManager.skip();
|
||||
return CommandExecutionResult.info("Skipping current track since not enough people are connected to the voice channel.");
|
||||
}
|
||||
|
||||
double needed = connected / VOTE_THRESHOLD_DIVISOR;
|
||||
|
||||
info.addSkip(context.getGuildUser());
|
||||
logger.debug("VoteSkip: guild={}, connected={}, needed={}, skips={}, user={}", context.getGuild().getId(), connected, needed, info.getSkipCount(), context.getGuildUser().getId());
|
||||
|
||||
if (info.getSkipCount() > needed) {
|
||||
return queueManager.skip();
|
||||
}
|
||||
|
||||
return CommandExecutionResult.success(String.format("Adding your vote to skip the current track. Current votes: %d/%d",
|
||||
info.getSkipCount(),
|
||||
(int) Math.ceil(needed))
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user