Version 1.4.0 merge from development. #6
2
pom.xml
2
pom.xml
|
@ -6,7 +6,7 @@
|
|||
|
||||
<groupId>com.github.andrewlalis</groupId>
|
||||
<artifactId>HandieBot</artifactId>
|
||||
<version>1.2.0</version>
|
||||
<version>1.3.0</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
|
|
@ -49,7 +49,6 @@ public class Commands {
|
|||
public static void executeCommand(String command, CommandContext context){
|
||||
for (Command cmd : commands) {
|
||||
if (cmd.getName().equals(command)){
|
||||
System.out.println(cmd.canUserExecute(context.getUser(), context.getGuild()));
|
||||
if (cmd instanceof StaticCommand){
|
||||
((StaticCommand)cmd).execute();
|
||||
return;
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
package handiebot.command;
|
||||
|
||||
import handiebot.HandieBot;
|
||||
import sx.blah.discord.handle.impl.events.guild.channel.message.reaction.ReactionEvent;
|
||||
import sx.blah.discord.handle.obj.IMessage;
|
||||
import sx.blah.discord.handle.obj.IReaction;
|
||||
import sx.blah.discord.handle.obj.IUser;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Andrew Lalis
|
||||
|
@ -8,12 +14,46 @@ import sx.blah.discord.handle.impl.events.guild.channel.message.reaction.Reactio
|
|||
*/
|
||||
public class ReactionHandler {
|
||||
|
||||
public static final String thumbsUp = "\uD83D\uDC4D";
|
||||
public static final String thumbsDown = "\uD83D\uDC4E";
|
||||
|
||||
/**
|
||||
* Processes a reaction.
|
||||
* @param event The reaction event to process.
|
||||
*/
|
||||
public static void handleReaction(ReactionEvent event){
|
||||
IMessage message = event.getMessage();
|
||||
IReaction reaction = event.getReaction();
|
||||
CommandContext context = new CommandContext(event.getUser(), event.getChannel(), event.getGuild(), new String[]{});
|
||||
if (reaction.toString().equals(thumbsDown)){
|
||||
onDownvote(context, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* What to do if someone downvotes a song.
|
||||
* If more than half of the people in the voice channel dislike the song, it will be skipped.
|
||||
* If not, then the bot will tell how many more people need to downvote.
|
||||
* @param context The context of the reaction.
|
||||
* @param message The messages that received a reaction.
|
||||
*/
|
||||
private static void onDownvote(CommandContext context, IMessage message){
|
||||
List<IUser> usersHere = HandieBot.musicPlayer.getVoiceChannel(context.getGuild()).getConnectedUsers();
|
||||
usersHere.removeIf(user -> user.getLongID() == HandieBot.client.getOurUser().getLongID());
|
||||
int userCount = usersHere.size();
|
||||
int userDownvotes = 0;
|
||||
IReaction reaction = message.getReactionByUnicode(thumbsDown);
|
||||
for (IUser user : reaction.getUsers()){
|
||||
if (usersHere.contains(user)){
|
||||
userDownvotes++;
|
||||
}
|
||||
}
|
||||
System.out.println("Valid downvotes: "+userDownvotes+" out of "+userCount+" people present.");
|
||||
if (userDownvotes > (userCount/2)){
|
||||
HandieBot.musicPlayer.skipTrack(context.getGuild());
|
||||
} else if (userDownvotes > 0) {
|
||||
context.getChannel().sendMessage((((userCount/2)+1) - userDownvotes)+" more people must downvote before the track is skipped.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -183,10 +183,10 @@ public class PlaylistCommand extends ContextCommand {
|
|||
return;
|
||||
Playlist playlist = new Playlist(context.getArgs()[1]);
|
||||
playlist.load();
|
||||
HandieBot.musicPlayer.getMusicManager(context.getGuild()).scheduler.setPlaylist(playlist);
|
||||
HandieBot.musicPlayer.getMusicManager(context.getGuild()).scheduler.nextTrack();
|
||||
log.log(BotLog.TYPE.INFO, MessageFormat.format(resourceBundle.getString("commands.command.playlist.play.log"), playlist.getName()));
|
||||
context.getChannel().sendMessage(MessageFormat.format(resourceBundle.getString("commands.command.playlist.play.message"), playlist.getName()));
|
||||
HandieBot.musicPlayer.getMusicManager(context.getGuild()).scheduler.setPlaylist(playlist);
|
||||
HandieBot.musicPlayer.getMusicManager(context.getGuild()).scheduler.nextTrack();
|
||||
} else {
|
||||
context.getChannel().sendMessage(MessageFormat.format(resourceBundle.getString("commands.command.playlist.error.playPlaylistNeeded"), getPlaylistShowString(context)));
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import static handiebot.HandieBot.resourceBundle;
|
|||
* Command to toggle repeating of the active playlist.
|
||||
*/
|
||||
public class RepeatCommand extends ContextCommand {
|
||||
|
||||
//TODO: make changing settings admin-only
|
||||
public RepeatCommand(){
|
||||
super("repeat",
|
||||
"[true|false]",
|
||||
|
|
|
@ -11,7 +11,7 @@ import static handiebot.HandieBot.resourceBundle;
|
|||
* Command to set shuffling of the active playlist.
|
||||
*/
|
||||
public class ShuffleCommand extends ContextCommand {
|
||||
|
||||
//TODO: make changes admin-only
|
||||
public ShuffleCommand(){
|
||||
super("shuffle",
|
||||
"[true|false]",
|
||||
|
|
|
@ -3,7 +3,6 @@ package handiebot.lavaplayer;
|
|||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
|
||||
import handiebot.HandieBot;
|
||||
import handiebot.command.Commands;
|
||||
import handiebot.lavaplayer.playlist.Playlist;
|
||||
import handiebot.lavaplayer.playlist.UnloadedTrack;
|
||||
|
@ -15,12 +14,14 @@ import sx.blah.discord.handle.obj.IGuild;
|
|||
import sx.blah.discord.handle.obj.IVoiceChannel;
|
||||
import sx.blah.discord.util.EmbedBuilder;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static handiebot.HandieBot.log;
|
||||
import static handiebot.HandieBot.resourceBundle;
|
||||
|
||||
/**
|
||||
* @author Andrew Lalis
|
||||
|
@ -65,7 +66,6 @@ public class MusicPlayer {
|
|||
*/
|
||||
public GuildMusicManager getMusicManager(IGuild guild){
|
||||
if (!this.musicManagers.containsKey(guild)){
|
||||
log.log(BotLog.TYPE.MUSIC, guild, "Creating new music manager and audio provider for guild: "+guild.getName());
|
||||
this.musicManagers.put(guild, new GuildMusicManager(this.playerManager, guild));
|
||||
guild.getAudioManager().setAudioProvider(this.musicManagers.get(guild).getAudioProvider());
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ public class MusicPlayer {
|
|||
if (!this.chatChannels.containsKey(guild)){
|
||||
List<IChannel> channels = guild.getChannelsByName(CHANNEL_NAME.toLowerCase());
|
||||
if (channels.isEmpty()){
|
||||
log.log(BotLog.TYPE.MUSIC, guild, "No chat channel found, creating a new one.");
|
||||
log.log(BotLog.TYPE.MUSIC, guild, resourceBundle.getString("log.creatingChatChannel"));
|
||||
this.chatChannels.put(guild, guild.createChannel(CHANNEL_NAME.toLowerCase()));
|
||||
} else {
|
||||
this.chatChannels.put(guild, channels.get(0));
|
||||
|
@ -101,7 +101,7 @@ public class MusicPlayer {
|
|||
if (!this.voiceChannels.containsKey(guild)){
|
||||
List<IVoiceChannel> channels = guild.getVoiceChannelsByName(CHANNEL_NAME);
|
||||
if (channels.isEmpty()){
|
||||
log.log(BotLog.TYPE.MUSIC, guild, "No voice channel found, creating a new one.");
|
||||
log.log(BotLog.TYPE.MUSIC, guild, resourceBundle.getString("log.newVoiceChannel"));
|
||||
this.voiceChannels.put(guild, guild.createVoiceChannel(CHANNEL_NAME));
|
||||
} else {
|
||||
this.voiceChannels.put(guild, channels.get(0));
|
||||
|
@ -125,8 +125,9 @@ public class MusicPlayer {
|
|||
*/
|
||||
public void setRepeat(IGuild guild, boolean value){
|
||||
getMusicManager(guild).scheduler.setRepeat(value);
|
||||
log.log(BotLog.TYPE.MUSIC, guild, "Set repeat to "+getMusicManager(guild).scheduler.isRepeating());
|
||||
getChatChannel(guild).sendMessage("Set repeat to "+getMusicManager(guild).scheduler.isRepeating());
|
||||
String message = MessageFormat.format(resourceBundle.getString("player.setRepeat"), getMusicManager(guild).scheduler.isRepeating());
|
||||
log.log(BotLog.TYPE.MUSIC, guild, message);
|
||||
getChatChannel(guild).sendMessage(":repeat: "+message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,8 +145,9 @@ public class MusicPlayer {
|
|||
*/
|
||||
public void setShuffle(IGuild guild, boolean value){
|
||||
getMusicManager(guild).scheduler.setShuffle(value);
|
||||
log.log(BotLog.TYPE.MUSIC, guild, "Set shuffle to "+Boolean.toString(HandieBot.musicPlayer.getMusicManager(guild).scheduler.isShuffling()));
|
||||
getChatChannel(guild).sendMessage("Set shuffle to "+Boolean.toString(HandieBot.musicPlayer.getMusicManager(guild).scheduler.isShuffling()));
|
||||
String message = MessageFormat.format(resourceBundle.getString("player.setShuffle"), getMusicManager(guild).scheduler.isShuffling());
|
||||
log.log(BotLog.TYPE.MUSIC, guild, message);
|
||||
getChatChannel(guild).sendMessage(":twisted_rightwards_arrows: "+message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,16 +156,16 @@ public class MusicPlayer {
|
|||
public void showQueueList(IGuild guild, boolean showAll) {
|
||||
List<UnloadedTrack> tracks = getMusicManager(guild).scheduler.queueList();
|
||||
if (tracks.size() == 0) {
|
||||
getChatChannel(guild).sendMessage("The queue is empty. Use `"+ Commands.get("play").getUsage()+"` to add songs.");
|
||||
getChatChannel(guild).sendMessage(MessageFormat.format(resourceBundle.getString("player.queueEmpty"), Commands.get("play").getUsage()));
|
||||
} else {
|
||||
if (tracks.size() > 10 && showAll) {
|
||||
String result = Pastebin.paste("Current queue for discord server: "+guild.getName()+".", getMusicManager(guild).scheduler.getActivePlaylist().toString());
|
||||
if (result != null && result.startsWith("https://pastebin.com/")){
|
||||
log.log(BotLog.TYPE.INFO, guild, "Queue uploaded to pastebin: "+result);
|
||||
log.log(BotLog.TYPE.INFO, guild, MessageFormat.format(resourceBundle.getString("player.queueUploaded"), result));
|
||||
//Only display the pastebin link for 10 minutes.
|
||||
new DisappearingMessage(getChatChannel(guild), "You may view the full queue by following the link: "+result+"\nNote that this link expires in 10 minutes.", 600000);
|
||||
new DisappearingMessage(getChatChannel(guild), MessageFormat.format(resourceBundle.getString("player.pastebinLink"), result), 600000);
|
||||
} else {
|
||||
log.log(BotLog.TYPE.ERROR, guild, "Unable to upload to pastebin: "+result);
|
||||
log.log(BotLog.TYPE.ERROR, guild, MessageFormat.format(resourceBundle.getString("player.pastebinError"), result));
|
||||
}
|
||||
} else {
|
||||
EmbedBuilder builder = new EmbedBuilder();
|
||||
|
@ -174,7 +176,7 @@ public class MusicPlayer {
|
|||
sb.append(tracks.get(i).getURL()).append(")");
|
||||
sb.append(tracks.get(i).getFormattedDuration()).append('\n');
|
||||
}
|
||||
builder.appendField("Showing " + (tracks.size() <= 10 ? tracks.size() : "the first 10") + " track" + (tracks.size() > 1 ? "s" : "") + " out of "+tracks.size()+".", sb.toString(), false);
|
||||
builder.appendField(MessageFormat.format(resourceBundle.getString("player.queueHeader"), tracks.size() <= 10 ? tracks.size() : "the first 10", tracks.size() > 1 ? "s" : "", tracks.size()), sb.toString(), false);
|
||||
getChatChannel(guild).sendMessage(builder.build());
|
||||
}
|
||||
}
|
||||
|
@ -195,7 +197,7 @@ public class MusicPlayer {
|
|||
//Build message.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (timeUntilPlay > 0) {
|
||||
sb.append("Added **").append(track.getTitle()).append("** to the queue.");
|
||||
sb.append(MessageFormat.format(resourceBundle.getString("player.addedToQueue"), track.getTitle()));
|
||||
}
|
||||
//If there's some tracks in the queue, get the time until this one plays.
|
||||
if (timeUntilPlay > 0){
|
||||
|
@ -215,6 +217,10 @@ public class MusicPlayer {
|
|||
* If possible, try to begin playing from the track scheduler's queue.
|
||||
*/
|
||||
public void playQueue(IGuild guild){
|
||||
if (getMusicManager(guild).scheduler.getActivePlaylist().getTrackCount() == 0){
|
||||
getChatChannel(guild).sendMessage(resourceBundle.getString("player.playQueueEmpty"));
|
||||
return;
|
||||
}
|
||||
IVoiceChannel vc = this.getVoiceChannel(guild);
|
||||
if (!vc.isConnected()){
|
||||
vc.join();
|
||||
|
@ -224,16 +230,17 @@ public class MusicPlayer {
|
|||
|
||||
public void clearQueue(IGuild guild){
|
||||
getMusicManager(guild).scheduler.clearQueue();
|
||||
getChatChannel(guild).sendMessage("Cleared the queue.");
|
||||
getChatChannel(guild).sendMessage(resourceBundle.getString("player.queueCleared"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips the current track.
|
||||
*/
|
||||
public void skipTrack(IGuild guild){
|
||||
String message = resourceBundle.getString("player.skippingCurrent");
|
||||
log.log(BotLog.TYPE.MUSIC, guild, message);
|
||||
getChatChannel(guild).sendMessage(":track_next: "+message);
|
||||
getMusicManager(guild).scheduler.nextTrack();
|
||||
log.log(BotLog.TYPE.MUSIC, guild, "Skipping the current track. ");
|
||||
getChatChannel(guild).sendMessage("Skipping the current track.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -242,8 +249,9 @@ public class MusicPlayer {
|
|||
*/
|
||||
public void stop(IGuild guild){
|
||||
getMusicManager(guild).scheduler.stop();
|
||||
getChatChannel(guild).sendMessage("Stopped playing music.");
|
||||
log.log(BotLog.TYPE.MUSIC, guild, "Stopped playing music.");
|
||||
String message = resourceBundle.getString("player.musicStopped");
|
||||
getChatChannel(guild).sendMessage(":stop_button: "+message);
|
||||
log.log(BotLog.TYPE.MUSIC, guild, message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,12 +15,19 @@ import sx.blah.discord.handle.obj.IMessage;
|
|||
import sx.blah.discord.handle.obj.IVoiceChannel;
|
||||
import sx.blah.discord.util.RequestBuffer;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
|
||||
import static handiebot.HandieBot.log;
|
||||
import static handiebot.HandieBot.resourceBundle;
|
||||
|
||||
/**
|
||||
* @author Andrew Lalis
|
||||
* Class to actually play music.
|
||||
* <p>
|
||||
* It holds an active playlist which it uses to pull songs from, and through the {@code MusicPlayer}, the
|
||||
* playlist can be modified.
|
||||
* </p>
|
||||
*/
|
||||
public class TrackScheduler extends AudioEventAdapter {
|
||||
|
||||
|
@ -180,10 +187,10 @@ public class TrackScheduler extends AudioEventAdapter {
|
|||
|
||||
@Override
|
||||
public void onTrackStart(AudioPlayer player, AudioTrack track) {
|
||||
log.log(BotLog.TYPE.MUSIC, this.guild, "Started audio track: "+track.getInfo().title);
|
||||
log.log(BotLog.TYPE.MUSIC, this.guild, MessageFormat.format(resourceBundle.getString("trackSchedule.trackStarted"), track.getInfo().title));
|
||||
List<IChannel> channels = this.guild.getChannelsByName(MusicPlayer.CHANNEL_NAME.toLowerCase());
|
||||
if (channels.size() > 0){
|
||||
IMessage message = channels.get(0).sendMessage("Now playing: **"+track.getInfo().title+"** "+new UnloadedTrack(track).getFormattedDuration()+"\n"+track.getInfo().uri);
|
||||
IMessage message = channels.get(0).sendMessage(MessageFormat.format(":arrow_forward: "+resourceBundle.getString("trackSchedule.nowPlaying"), track.getInfo().title, new UnloadedTrack(track).getFormattedDuration()));
|
||||
RequestBuffer.request(() -> {message.addReaction(":thumbsup:");}).get();
|
||||
RequestBuffer.request(() -> {message.addReaction(":thumbsdown:");}).get();
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import static handiebot.HandieBot.log;
|
|||
* on the playlist.
|
||||
*/
|
||||
public class Playlist {
|
||||
|
||||
//TODO: externalize strings
|
||||
private String name;
|
||||
|
||||
private List<UnloadedTrack> tracks;
|
||||
|
@ -121,7 +121,6 @@ public class Playlist {
|
|||
public static int getShuffledIndex(int listLength){
|
||||
float threshold = 0.2f;
|
||||
int trueLength = listLength - (int)(threshold*(float)listLength);
|
||||
log.log(BotLog.TYPE.INFO, "Shuffle results: Actual size: "+listLength+", Last Usable Index: "+trueLength);
|
||||
Random rand = new Random();
|
||||
//TODO Add in a small gradient in chance for a song to be picked.
|
||||
return rand.nextInt(trueLength);
|
||||
|
|
|
@ -17,7 +17,7 @@ import static handiebot.HandieBot.log;
|
|||
* This is useful for quickly loading playlists and only loading a track when it is needed.
|
||||
*/
|
||||
public class UnloadedTrack implements Cloneable {
|
||||
|
||||
//TODO: externalize strings
|
||||
private String title;
|
||||
private String url;
|
||||
private long duration;
|
||||
|
|
|
@ -7,6 +7,7 @@ import sx.blah.discord.handle.obj.IMessage;
|
|||
import sx.blah.discord.handle.obj.Permissions;
|
||||
|
||||
import static handiebot.HandieBot.log;
|
||||
import static handiebot.HandieBot.resourceBundle;
|
||||
|
||||
/**
|
||||
* @author Andrew Lalis
|
||||
|
@ -57,7 +58,7 @@ public class DisappearingMessage extends Thread implements Runnable {
|
|||
if (HandieBot.hasPermission(Permissions.MANAGE_MESSAGES, message.getChannel())){
|
||||
return true;
|
||||
} else {
|
||||
log.log(BotLog.TYPE.ERROR, message.getGuild(), "Unable to delete message. Please ensure that the bot has MANAGE_MESSAGES enabled, especially for this channel.");
|
||||
log.log(BotLog.TYPE.ERROR, message.getGuild(), resourceBundle.getString("log.deleteMessageError"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import static handiebot.HandieBot.log;
|
|||
* Class to simplify file operations.
|
||||
*/
|
||||
public class FileUtil {
|
||||
|
||||
//TODO: externalize strings
|
||||
public static String getDataDirectory(){
|
||||
return System.getProperty("user.home")+"/.handiebot/";
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
#Strings for HandieBot:
|
||||
# The following strings are organized in a way that it should be intuitive how it will be used.
|
||||
#Log
|
||||
log.loggingIn=Logging client in...
|
||||
log.init=HandieBot initialized.
|
||||
log.shuttingDown=Shutting down the bot.
|
||||
log.deleteMessageError=Unable to delete message. Please ensure that the bot has MANAGE_MESSAGES enabled, especially for this channel.
|
||||
log.creatingChatChannel=No chat channel found, creating a new one.
|
||||
log.newVoiceChannel=No voice channel found, creating a new one.
|
||||
#Window
|
||||
window.close.question=Are you sure you want to exit and shutdown the bot?
|
||||
window.close.title=Confirm shutdown
|
||||
|
@ -85,3 +90,20 @@ commands.command.shuffle.description=Sets shuffling.
|
|||
commands.command.skip.description=Skips the current song.
|
||||
#Stop
|
||||
commands.command.stop.description=Stops playing music.
|
||||
#Music Player
|
||||
player.setRepeat=Set repeat to {0}
|
||||
player.setShuffle=Set shuffle to {0}
|
||||
player.queueEmpty=The queue is empty. Use `{0}` to add songs.
|
||||
player.queueUploaded=Queue uploaded to pastebin: {0}
|
||||
player.pastebinLink=You may view the full queue by following the link: {0}\nNote that this link expires in 10 minutes.
|
||||
player.pastebinError=Unable to upload to pastebin: {0}
|
||||
player.queueHeader=Showing {0} track{1} out of {2}.
|
||||
player.addedToQueue=Added **{0}** to the queue.
|
||||
player.queueCleared=Cleared the queue.
|
||||
player.skippingCurrent=Skipping the current track.
|
||||
player.musicStopped=Stopped playing music.
|
||||
player.playQueueEmpty=There's nothing in the queue to play.
|
||||
#Track scheduler
|
||||
trackSchedule.trackStarted=Started audio track: {0}
|
||||
trackSchedule.nowPlaying=Now playing: **{0}** {1}
|
||||
|
||||
|
|
|
@ -80,4 +80,23 @@ commands.command.repeat.description=Sets repeating.
|
|||
commands.command.shuffle.description=Sets shuffling.
|
||||
commands.command.skip.description=Skips the current song.
|
||||
commands.command.stop.description=Stops playing music.
|
||||
log.deleteMessageError=Unable to delete message. Please ensure that the bot has MANAGE_MESSAGES enabled, especially for this channel.
|
||||
log.creatingChatChannel=No chat channel found, creating a new one.
|
||||
log.newVoiceChannel=No voice channel found, creating a new one.
|
||||
player.setRepeat=Set repeat to {0}
|
||||
player.setShuffle=Set shuffle to {0}
|
||||
player.queueEmpty=The queue is empty. Use `{0}` to add songs.
|
||||
player.queueUploaded=Queue uploaded to pastebin: {0}
|
||||
player.pastebinLink=You may view the full queue by following the link: {0}\
|
||||
Note that this link expires in 10 minutes.
|
||||
player.pastebinError=Unable to upload to pastebin: {0}
|
||||
player.queueHeader=Showing {0} track{1} out of {2}.
|
||||
player.addedToQueue=Added **{0}** to the queue.
|
||||
player.queueCleared=Cleared the queue.
|
||||
player.skippingCurrent=Skipping the current track.
|
||||
player.musicStopped=Stopped playing music.
|
||||
trackSchedule.trackStarted=Started audio track: {0}
|
||||
trackSchedule.nowPlaying=Now playing: **{0}** {1}\
|
||||
{2}
|
||||
player.playQueueEmpty=There's nothing in the queue to play.
|
||||
|
||||
|
|
Loading…
Reference in New Issue