diff --git a/FredBoat/src/main/java/fredboat/audio/queue/AudioLoader.java b/FredBoat/src/main/java/fredboat/audio/queue/AudioLoader.java index a7dde0e43..dc872e5ab 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/AudioLoader.java +++ b/FredBoat/src/main/java/fredboat/audio/queue/AudioLoader.java @@ -39,17 +39,19 @@ import fredboat.feature.togglz.FeatureFlags; import fredboat.jda.JdaEntityProvider; import fredboat.messaging.CentralMessaging; +import fredboat.util.PlayerUtil; import fredboat.util.TextUtils; import fredboat.util.ratelimit.Ratelimiter; import fredboat.util.rest.YoutubeAPI; import fredboat.util.rest.YoutubeVideo; import net.dv8tion.jda.core.MessageBuilder; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; import java.util.ArrayList; -import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -61,6 +63,7 @@ public class AudioLoader implements AudioLoadResultHandler { //Matches a timestamp and the description private static final Pattern SPLIT_DESCRIPTION_PATTERN = Pattern.compile("(.*?)[( \\[]*((?:\\d?\\d:)?\\d?\\d:\\d\\d)[) \\]]*(.*)"); private static final int QUEUE_TRACK_LIMIT = 10000; + private static final int MAX_QUEUE_MESSAGE_DISPLAY = 3; private final JdaEntityProvider jdaEntityProvider; private final Ratelimiter ratelimiter; @@ -182,11 +185,8 @@ public void trackLoaded(AudioTrack at) { } else { if (!context.isQuiet()) { - context.reply(gplayer.isPlaying() ? - context.i18nFormat("loadSingleTrack", TextUtils.escapeAndDefuse(at.getInfo().title)) - : - context.i18nFormat("loadSingleTrackAndPlay", TextUtils.escapeAndDefuse(at.getInfo().title)) - ); + context.reply(this.buildMusicQueueMessage(at.getInfo().title, at.getInfo().length)); + } else { log.info("Quietly loaded " + at.getIdentifier()); } @@ -208,18 +208,30 @@ public void trackLoaded(AudioTrack at) { public void playlistLoaded(AudioPlaylist ap) { Metrics.tracksLoaded.inc(ap.getTracks() == null ? 0 : ap.getTracks().size()); try { + int statusMessageCount = 0; if(context.isSplit()){ context.reply(context.i18n("loadPlaySplitListFail")); loadNextAsync(); return; } - List toAdd = new ArrayList<>(); + StringBuilder replyMessage = new StringBuilder(); for (AudioTrack at : ap.getTracks()) { - toAdd.add(new AudioTrackContext(jdaEntityProvider, at, context.getMember())); + if (statusMessageCount < MAX_QUEUE_MESSAGE_DISPLAY) { + statusMessageCount++; + + replyMessage.append(this.buildMusicQueueMessage(at.getInfo().title, at.getInfo().length)) + .append("\n\n"); + } + + trackProvider.add(new AudioTrackContext(jdaEntityProvider, at, context.getMember())); } - trackProvider.addAll(toAdd); - context.reply(context.i18nFormat("loadListSuccess", ap.getTracks().size(), ap.getName())); + + if (ap.getTracks().size() > MAX_QUEUE_MESSAGE_DISPLAY) { + replyMessage.append("...\n"); + context.reply(replyMessage + context.i18nFormat("loadListSuccess", ap.getTracks().size(), ap.getName())); + } + if (!gplayer.isPaused()) { gplayer.play(); } @@ -276,8 +288,6 @@ private void loadSplit(AudioTrack at, IdentifierContext ic){ } else { pairs.add(new ImmutablePair<>(timestamp, title2)); } - - } if(pairs.size() < 2) { @@ -285,9 +295,9 @@ private void loadSplit(AudioTrack at, IdentifierContext ic){ return; } - ArrayList list = new ArrayList<>(); - int i = 0; + MessageBuilder mb = CentralMessaging.getClearThreadLocalMessageBuilder(); + for(Pair pair : pairs){ long startPos; long endPos; @@ -307,26 +317,27 @@ private void loadSplit(AudioTrack at, IdentifierContext ic){ SplitAudioTrackContext atc = new SplitAudioTrackContext(jdaEntityProvider, newAt, ic.getMember(), startPos, endPos, pair.getRight()); - list.add(atc); + if (i < MAX_QUEUE_MESSAGE_DISPLAY + && !StringUtils.isBlank(atc.getEffectiveTitle())) { + + mb.append(this.buildMusicQueueMessage(atc.getEffectiveTitle(), atc.getEffectiveDuration())) + .append("\n\n"); + } + gplayer.queue(atc); i++; } - MessageBuilder mb = CentralMessaging.getClearThreadLocalMessageBuilder() - .append(ic.i18n("loadFollowingTracksAdded")).append("\n"); - for(SplitAudioTrackContext atc : list) { - mb.append("`[") - .append(TextUtils.formatTime(atc.getEffectiveDuration())) - .append("]` ") - .append(TextUtils.escapeAndDefuse(atc.getEffectiveTitle())) - .append("\n"); + if (i > MAX_QUEUE_MESSAGE_DISPLAY) { + mb.append("...\n"); + mb.append(context.i18nFormat("loadListSuccess", i, at.getInfo().title)); } - //This is pretty spammy .. let's use a shorter one - if(mb.length() > 800){ + // This is pretty spammy .. let's use a shorter one + if (mb.length() > 800) { mb = CentralMessaging.getClearThreadLocalMessageBuilder() - .append(ic.i18nFormat("loadPlaylistTooMany", list.size())); + .append(ic.i18nFormat("loadPlaylistTooMany", i)); } context.reply(mb.build()); @@ -362,4 +373,26 @@ private void handleThrowable(IdentifierContext ic, Throwable th) { } } + /** + * Build queue message based on title and duration. + * It will use player and context object to determine if something is playing. + *

+ * NOTE: It will not add a new line for each string, this assume the caller will handle newline. + *

+ * + * @param title Title of the music. + * @param duration Duration of the music. + * @return String object representing the message to reply for each queue. + */ + private String buildMusicQueueMessage(@Nonnull String title, long duration){ + + String playingStatusOrQueueTime = PlayerUtil.resolveStatusOrQueueMessage(gplayer, context); + String songTitleAndMusic = + TextUtils.boldenText(TextUtils.escapeAndDefuse(title)) + " " + + "(" + TextUtils.formatTime(duration) + ")"; + + return playingStatusOrQueueTime + + "\n\t" + + songTitleAndMusic; + } } diff --git a/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.java b/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.java index 4e6331edf..128708b34 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.java +++ b/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.java @@ -34,10 +34,11 @@ import fredboat.commandmeta.abs.CommandContext; import fredboat.commandmeta.abs.ICommandRestricted; import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.definitions.PermissionLevel; -import fredboat.main.Launcher; import fredboat.messaging.CentralMessaging; import fredboat.messaging.internal.Context; +import fredboat.util.PlayerUtil; +import fredboat.definitions.PermissionLevel; +import fredboat.main.Launcher; import fredboat.util.TextUtils; import net.dv8tion.jda.core.entities.Member; import net.dv8tion.jda.core.entities.TextChannel; @@ -109,13 +110,31 @@ static void select(CommandContext context, VideoSelectionCache videoSelectionCac for (int i = 0; i < validChoices.size(); i++) { selectedTracks[i] = selection.choices.get(validChoices.get(i) - 1); - String msg = context.i18nFormat("selectSuccess", validChoices.get(i), - TextUtils.escapeAndDefuse(selectedTracks[i].getInfo().title), - TextUtils.formatTime(selectedTracks[i].getInfo().length)); + String playingStatusOrQueueTime = PlayerUtil.resolveStatusOrQueueMessage(player, context); + + // Print the selection string. + String selectionSuccessString = context.i18nFormat( + "selectSuccess", + TextUtils.boldenText("\\#" + validChoices.get(i)) + + " ~ " + playingStatusOrQueueTime); + outputMsgBuilder.append(selectionSuccessString); + outputMsgBuilder.append("\n"); + + // Print the song title and length. + outputMsgBuilder.append("\t\t"); + + // Merge title and the length in one string. + String songTitleAndMusic = + TextUtils.boldenText(TextUtils.escapeAndDefuse(selectedTracks[i].getInfo().title)) + " " + + "(" + TextUtils.formatTime(selectedTracks[i].getInfo().length) + ")"; + + outputMsgBuilder.append(songTitleAndMusic); + outputMsgBuilder.append("\n"); + + // if there are more selections. if (i < validChoices.size()) { outputMsgBuilder.append("\n"); } - outputMsgBuilder.append(msg); player.queue(new AudioTrackContext(Launcher.getBotController().getJdaEntityProvider(), selectedTracks[i], invoker)); diff --git a/FredBoat/src/main/java/fredboat/util/PlayerUtil.java b/FredBoat/src/main/java/fredboat/util/PlayerUtil.java new file mode 100644 index 000000000..beb360a1b --- /dev/null +++ b/FredBoat/src/main/java/fredboat/util/PlayerUtil.java @@ -0,0 +1,51 @@ +package fredboat.util; + +import fredboat.audio.player.GuildPlayer; +import fredboat.messaging.internal.Context; + +import javax.annotation.Nonnull; + +public class PlayerUtil { + + /** + * No initialization! + */ + private PlayerUtil() { + } + + /** + * Resolve which message to be replying to the discord channel based on how many audio is in the queue. + * + * @param player Guild player. + * @param context Context object to be used for retrieving i18n strings. + * @return String represent of the current state of the player for adding audio. + */ + public static String resolveStatusOrQueueMessage(@Nonnull GuildPlayer player, @Nonnull Context context) { + String playingStatusOrQueueTime; + int positionInQueue = player.getTrackCount() + 1; + if (player.getTrackCount() < 1) { + playingStatusOrQueueTime = TextUtils.italicizeText(context.i18n("selectSuccessPartNowPlaying")); + } else { + if (player.getRemainingTracks() + .stream() + .noneMatch( + audioTrackContext -> audioTrackContext.getTrack().getInfo().isStream)) { + + // Currently is not playing any live stream. + long remainingTimeInMillis = player.getTotalRemainingMusicTimeMillis(); + String remainingTime = TextUtils.formatTime(remainingTimeInMillis); + playingStatusOrQueueTime = context.i18nFormat( + "selectSuccessPartQueueWaitTime", + TextUtils.boldenText(positionInQueue), + TextUtils.boldenText(remainingTime)); + + } else { + playingStatusOrQueueTime = context.i18nFormat( + "selectSuccessPartQueueHasStream", + TextUtils.boldenText(positionInQueue)); + } + } + + return playingStatusOrQueueTime; + } +} diff --git a/FredBoat/src/main/java/fredboat/util/TextUtils.java b/FredBoat/src/main/java/fredboat/util/TextUtils.java index cbdc60dae..ea0ad57fc 100644 --- a/FredBoat/src/main/java/fredboat/util/TextUtils.java +++ b/FredBoat/src/main/java/fredboat/util/TextUtils.java @@ -374,6 +374,28 @@ public static String shorten(@Nonnull String input, int size) { return shortened.toString(); } + /** + * Wraps input with discord's markdown bold marker. + * + * @param input Object#toString() to be wrapped in bold. Must be non null. + * @return String with ** wrapped. + */ + @Nonnull + public static String boldenText(@Nonnull T input) { + return "**" + input + "**"; + } + + /** + * Wraps input with discord's markdown italic marker. + * + * @param input Object#toString() to be wrapped in italic marker. Must be non null. + * @return String with * wrapped. + */ + @Nonnull + public static String italicizeText(@Nonnull T input) { + return "*" + input + "*"; + } + /** * @return the input, with escaped markdown and defused mentions and URLs * It is a good idea to use this on any user generated values that we reply in plain text. @@ -414,6 +436,7 @@ private static String defuseUrls(@Nonnull String input) { .filteredBy(CharacterPredicates.LETTERS, CharacterPredicates.DIGITS) .build(); + @Nonnull public static String randomAlphaNumericString(int length) { if (length < 1) { throw new IllegalArgumentException(); diff --git a/FredBoat/src/main/resources/lang/en_US.properties b/FredBoat/src/main/resources/lang/en_US.properties index 7ea8286b4..19ad6673f 100644 --- a/FredBoat/src/main/resources/lang/en_US.properties +++ b/FredBoat/src/main/resources/lang/en_US.properties @@ -14,7 +14,10 @@ pauseSuccess=The player is now paused. You can unpause it with `{0}unpause`. repeatOnSingle=The player will now repeat the current track. repeatOnAll=The player will now repeat the queue. repeatOff=The player is no longer on repeat. -selectSuccess=Song **\#{0}** has been selected\: **{1}** ({2}) +selectSuccess=Song selected {0} +selectSuccessPartNowPlaying=Now playing +selectSuccessPartQueueWaitTime=Queued as #{0}, playing in: {1}. +selectSuccessPartQueueHasStream=Queued as #{0}, playing after live stream. selectInterval=Must be a number 1-{0}. selectSelectionNotGiven=You must first be given a selection to choose from. shuffleOn=The player is now shuffled.