Version 1.4.0 merge from development. #6

Merged
andrewlalis merged 13 commits from development into master 2017-07-03 11:28:00 +00:00
18 changed files with 92 additions and 65 deletions
Showing only changes of commit 8f670601b3 - Show all commits

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
!.jar
.idea/ .idea/
modules/ modules/
src/test/ src/test/

View File

@ -29,35 +29,39 @@ queue all
Because the play command is defined as `play [URL]`, and the queue command is defined as `queue [all]`. Because the play command is defined as `play [URL]`, and the queue command is defined as `queue [all]`.
Commands shown in **`bold`** can only be executed by an administrator, for security reasons.
### General ### General
* `info` - Displays the most common commands, and some basic information about the bot. * `info` - Displays the most common commands, and some basic information about the bot.
* `help` - Sends a private message to whoever issues this command. The message contains an in-depth list of all commands and their proper usage. * `help` - Sends a private message to whoever issues this command. The message contains an in-depth list of all commands and their proper usage.
* `setprefix <PREFIX>` - Sets the prefix for all commands. Be careful, as some values will cause irreversible damage, if for example, a prefix conflicts with another bot's prefix. * **`setprefix <PREFIX>`** - Sets the prefix for all commands. Be careful, as some values will cause irreversible damage, if for example, a prefix conflicts with another bot's prefix.
### Music ### Music
* `play [URL]` - Starts playback from the queue, or if a URL is defined, then it will attempt to play that song, or add it to the queue, depending on if a song is already playing. If a song is already playing, you should receive an estimate of when your song should begin playing. * `play [URL]` - Starts playback from the queue, or if a URL is defined, then it will attempt to play that song, or add it to the queue, depending on if a song is already playing. If a song is already playing, you should receive an estimate of when your song should begin playing.
* `stop` - If music is playing, this will stop it. * **`stop`** - If music is playing, this will stop it.
* `skip` - If a song is playing, the bot will skip it and play the next song in the queue. * **`skip`** - If a song is playing, the bot will skip it and play the next song in the queue.
* `queue [all|clear|save]` - Lists up to the first 10 items on the queue, if no argument is given. * `queue [all|clear|save]` - Lists up to the first 10 items on the queue, if no argument is given.
* `all` - The bot will upload a list to [PasteBin](http://pastebin.com) of the entire queue, provided it is greater than 10 elements, and give you a link which expires in 10 minutes. * `all` - The bot will upload a list to [PasteBin](http://pastebin.com) of the entire queue, provided it is greater than 10 elements, and give you a link which expires in 10 minutes.
* `clear` - The queue will be cleared and the current song will be stopped. * **`clear`** - The queue will be cleared and the current song will be stopped.
* `save <PLAYLIST>` - The queue will be saved as a playlist with the given name. * **`save <PLAYLIST>`** - The queue will be saved as a playlist with the given name.
* `repeat [true|false]` - Sets the bot to repeat the playlist, as in once a song is removed from the queue to be played, it is added back to the end of the playlist. * `repeat [true|false]` - Sets the bot to repeat the playlist, as in once a song is removed from the queue to be played, it is added back to the end of the playlist. If no argument is given, then this shows if the queue is currently repeating.
* `shuffle [true|false]` - Sets the bot to shuffle the playlist, as in pull a random song from the playlist, with some filters to prevent repeating songs. * `shuffle [true|false]` - Sets the bot to shuffle the playlist, as in pull a random song from the playlist, with some filters to prevent repeating songs. If no argument is given, then this shows if the queue is currently shuffling.
* `playlist <create|show|play|delete|add|remove|rename>` - Various commands to manipulate playlists. The specific sub-commands are explained below. >Note that for `repeat` and `shuffle`, anyone may view the status of these values, but only administrators may set them.
* **`playlist <create|show|play|delete|add|remove|rename|move>`** - Various commands to manipulate playlists. The specific sub-commands are explained below.
* `create <PLAYLIST> [URL]...` - Creates a new playlist, optionally with some starting URLs. * `create <PLAYLIST> [URL]...` - Creates a new playlist, optionally with some starting URLs.
* `delete <PLAYLIST>` - Deletes a playlist with the given name. * `delete <PLAYLIST>` - Deletes a playlist with the given name.
@ -74,3 +78,10 @@ Because the play command is defined as `play [URL]`, and the queue command is de
* `move <PLAYLIST> <SONGNUMBER> <NEWNUMBER>` - Moves a song from one index to another index, shifting other elements as necessary. * `move <PLAYLIST> <SONGNUMBER> <NEWNUMBER>` - Moves a song from one index to another index, shifting other elements as necessary.
### Miscellaneous
* `tengwar <to|from> <TEXT>` - Uses the [TengwarTranslatorLibrary](https://github.com/andrewlalis/TengwarTranslatorLibrary) to translate text into a Tengwar script equivalent, or translate from Tengwar to normal text. Be aware that due to the nature of this font, capitalization is not saved in Tengwar. For more information on how this works, check out my [TengwarTranslator](https://github.com/andrewlalis/TengwarTranslator).
* `to <TEXT>` - Translates some text to tengwar, and responds with both the raw, UTF-8 string, and an image generated using a Tengwar font.
* `from <TEXT>` - Translates some tengwar text to normal, human readable text.

View File

@ -6,7 +6,7 @@
<groupId>com.github.andrewlalis</groupId> <groupId>com.github.andrewlalis</groupId>
<artifactId>HandieBot</artifactId> <artifactId>HandieBot</artifactId>
<version>1.3.1</version> <version>1.4.0</version>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
@ -70,7 +70,7 @@
<dependency> <dependency>
<groupId>com.github.andrewlalis</groupId> <groupId>com.github.andrewlalis</groupId>
<artifactId>TengwarTranslatorLibrary</artifactId> <artifactId>TengwarTranslatorLibrary</artifactId>
<version>1.2</version> <version>1.3</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -16,10 +16,7 @@ import sx.blah.discord.handle.obj.Permissions;
import sx.blah.discord.util.DiscordException; import sx.blah.discord.util.DiscordException;
import sx.blah.discord.util.RateLimitException; import sx.blah.discord.util.RateLimitException;
import java.util.ArrayList; import java.util.*;
import java.util.EnumSet;
import java.util.List;
import java.util.ResourceBundle;
/** /**
* @author Andrew Lalis * @author Andrew Lalis
@ -33,7 +30,7 @@ public class HandieBot {
private static final String TOKEN = "MjgzNjUyOTg5MjEyNjg4Mzg0.C45A_Q.506b0G6my1FEFa7_YY39lxLBHUY"; private static final String TOKEN = "MjgzNjUyOTg5MjEyNjg4Mzg0.C45A_Q.506b0G6my1FEFa7_YY39lxLBHUY";
private static boolean USE_GUI = true; private static boolean USE_GUI = true;
public static ResourceBundle resourceBundle = ResourceBundle.getBundle("Strings"); public static final ResourceBundle resourceBundle = ResourceBundle.getBundle("Strings");
//Discord client object. //Discord client object.
public static IDiscordClient client; public static IDiscordClient client;
@ -46,7 +43,7 @@ public class HandieBot {
public static MusicPlayer musicPlayer; public static MusicPlayer musicPlayer;
//List of all permissions needed to operate this bot. //List of all permissions needed to operate this bot.
private static int permissionsNumber = 0; private static final int permissionsNumber;
static { static {
List<Permissions> requiredPermissions = new ArrayList<>(); List<Permissions> requiredPermissions = new ArrayList<>();
requiredPermissions.add(Permissions.CHANGE_NICKNAME); requiredPermissions.add(Permissions.CHANGE_NICKNAME);
@ -87,13 +84,13 @@ public class HandieBot {
musicPlayer = new MusicPlayer(); musicPlayer = new MusicPlayer();
if (args.length >= 1) { List<String> argsList = Arrays.asList(args);
if (args[0].equalsIgnoreCase("-nogui")){
if (argsList.contains("-nogui")) {
System.out.println("Starting with no GUI."); System.out.println("Starting with no GUI.");
USE_GUI = false; USE_GUI = false;
log = new BotLog(null); log = new BotLog(null);
} }
}
if (USE_GUI){ if (USE_GUI){
window = new BotWindow(); window = new BotWindow();

View File

@ -77,9 +77,7 @@ public class CommandHandler {
String[] words = message.getContent().split(" "); String[] words = message.getContent().split(" ");
if (words[0].startsWith(PREFIXES.get(message.getGuild()))){ if (words[0].startsWith(PREFIXES.get(message.getGuild()))){
String[] args = new String[words.length-1]; String[] args = new String[words.length-1];
for (int i = 0; i < words.length-1; i++){ System.arraycopy(words, 1, args, 0, words.length - 1);
args[i] = words[i+1];
}
return args; return args;
} }
return new String[0]; return new String[0];

View File

@ -25,7 +25,7 @@ import static handiebot.HandieBot.resourceBundle;
*/ */
public class Commands { public class Commands {
public static List<Command> commands = new ArrayList<Command>(); public static List<Command> commands = new ArrayList<>();
static { static {
//Music commands. //Music commands.

View File

@ -286,11 +286,12 @@ public class PlaylistCommand extends ContextCommand {
* @return True if the playlist exists, false otherwise. * @return True if the playlist exists, false otherwise.
*/ */
private boolean checkForPlaylist(CommandContext context){ private boolean checkForPlaylist(CommandContext context){
if (!Playlist.playlistExists(context.getArgs()[1])){ if (Playlist.playlistExists(context.getArgs()[1])){
return true;
} else {
new DisappearingMessage(context.getChannel(), MessageFormat.format(resourceBundle.getString("commands.command.playlist.error.playlistDoesNotExist"), getPlaylistShowString(context)), 3000); new DisappearingMessage(context.getChannel(), MessageFormat.format(resourceBundle.getString("commands.command.playlist.error.playlistDoesNotExist"), getPlaylistShowString(context)), 3000);
return false; return false;
} }
return true;
} }
/** /**

View File

@ -6,7 +6,8 @@ import sx.blah.discord.handle.audio.AudioEncodingType;
import sx.blah.discord.handle.audio.IAudioProvider; import sx.blah.discord.handle.audio.IAudioProvider;
/** /**
* Created by Andrew's Computer on 18-Jun-17. * @author Andrew Lalis
* Class to provide audio bytes to the music player.
*/ */
public class AudioProvider implements IAudioProvider { public class AudioProvider implements IAudioProvider {
private final AudioPlayer audioPlayer; private final AudioPlayer audioPlayer;

View File

@ -175,6 +175,7 @@ public class MusicPlayer {
public void showQueueList(IGuild guild, boolean showAll) { public void showQueueList(IGuild guild, boolean showAll) {
List<UnloadedTrack> tracks = getMusicManager(guild).scheduler.queueList(); List<UnloadedTrack> tracks = getMusicManager(guild).scheduler.queueList();
if (tracks.size() == 0) { if (tracks.size() == 0) {
//noinspection ConstantConditions
getChatChannel(guild).sendMessage(MessageFormat.format(resourceBundle.getString("player.queueEmpty"), Commands.get("play").getUsage())); getChatChannel(guild).sendMessage(MessageFormat.format(resourceBundle.getString("player.queueEmpty"), Commands.get("play").getUsage()));
} else { } else {
if (tracks.size() > 10 && showAll) { if (tracks.size() > 10 && showAll) {
@ -295,9 +296,7 @@ public class MusicPlayer {
* Performs the same functions as stop, but with every guild. * Performs the same functions as stop, but with every guild.
*/ */
public void quitAll(){ public void quitAll(){
this.musicManagers.forEach((guild, musicManager) -> { this.musicManagers.forEach((guild, musicManager) -> musicManager.scheduler.stop());
musicManager.scheduler.stop();
});
this.playerManager.shutdown(); this.playerManager.shutdown();
} }

View File

@ -197,8 +197,8 @@ public class TrackScheduler extends AudioEventAdapter {
if (channels.size() > 0){ if (channels.size() > 0){
IMessage message = channels.get(0).sendMessage(MessageFormat.format(":arrow_forward: "+resourceBundle.getString("trackSchedule.nowPlaying"), track.getInfo().title, new UnloadedTrack(track).getFormattedDuration())); IMessage message = channels.get(0).sendMessage(MessageFormat.format(":arrow_forward: "+resourceBundle.getString("trackSchedule.nowPlaying"), track.getInfo().title, new UnloadedTrack(track).getFormattedDuration()));
this.activePlayMessageId = message.getLongID(); this.activePlayMessageId = message.getLongID();
RequestBuffer.request(() -> {message.addReaction(":thumbsup:");}).get(); RequestBuffer.request(() -> message.addReaction(":thumbsup:")).get();
RequestBuffer.request(() -> {message.addReaction(":thumbsdown:");}).get(); RequestBuffer.request(() -> message.addReaction(":thumbsdown:")).get();
} }
} }

View File

@ -12,6 +12,7 @@ import java.util.List;
import java.util.Random; import java.util.Random;
import static handiebot.HandieBot.log; import static handiebot.HandieBot.log;
import static handiebot.HandieBot.resourceBundle;
/** /**
* @author Andrew Lalis * @author Andrew Lalis
@ -21,7 +22,7 @@ import static handiebot.HandieBot.log;
* on the playlist. * on the playlist.
*/ */
public class Playlist { public class Playlist {
//TODO: externalize strings
private String name; private String name;
private List<UnloadedTrack> tracks; private List<UnloadedTrack> tracks;
@ -186,7 +187,7 @@ public class Playlist {
*/ */
public static List<String> getAvailablePlaylists(){ public static List<String> getAvailablePlaylists(){
File playlistFolder = new File(System.getProperty("user.home")+"/.handiebot/playlist"); File playlistFolder = new File(System.getProperty("user.home")+"/.handiebot/playlist");
List<String> names = new ArrayList<String>(Arrays.asList(playlistFolder.list())); @SuppressWarnings("ConstantConditions") List<String> names = new ArrayList<>(Arrays.asList(playlistFolder.list()));
for (int i = 0; i < names.size(); i++){ for (int i = 0; i < names.size(); i++){
String name = names.get(i); String name = names.get(i);
name = name.replace(".txt", ""); name = name.replace(".txt", "");
@ -215,7 +216,7 @@ public class Playlist {
public String toString(){ public String toString(){
StringBuilder sb = new StringBuilder("Playlist: "+this.getName()+'\n'); StringBuilder sb = new StringBuilder("Playlist: "+this.getName()+'\n');
if (this.getTrackCount() == 0){ if (this.getTrackCount() == 0){
sb.append("There are no songs in this playlist."); sb.append(resourceBundle.getString("playlist.empty"));
} else { } else {
for (int i = 0; i < this.getTrackCount(); i++) { for (int i = 0; i < this.getTrackCount(); i++) {
sb.append(i + 1).append(". ").append(this.tracks.get(i).getTitle()).append(" ").append(this.tracks.get(i).getFormattedDuration()).append("\n"); sb.append(i + 1).append(". ").append(this.tracks.get(i).getTitle()).append(" ").append(this.tracks.get(i).getFormattedDuration()).append("\n");

View File

@ -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. * This is useful for quickly loading playlists and only loading a track when it is needed.
*/ */
public class UnloadedTrack implements Cloneable { public class UnloadedTrack implements Cloneable {
//TODO: externalize strings
private String title; private String title;
private String url; private String url;
private long duration; private long duration;

View File

@ -30,7 +30,7 @@ public class DisappearingMessage extends Thread implements Runnable {
e.printStackTrace(); e.printStackTrace();
} }
if (canDelete(sentMessage)) if (canDelete(sentMessage))
RequestBuffer.request(() -> sentMessage.delete()); RequestBuffer.request(sentMessage::delete);
} }
/** /**
@ -46,7 +46,7 @@ public class DisappearingMessage extends Thread implements Runnable {
e.printStackTrace(); e.printStackTrace();
} }
if (canDelete(message)) if (canDelete(message))
RequestBuffer.request(() -> message.delete()); RequestBuffer.request(message::delete);
}).start(); }).start();
} }

View File

@ -7,17 +7,19 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.file.Files; import java.nio.file.Files;
import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static handiebot.HandieBot.log; import static handiebot.HandieBot.log;
import static handiebot.HandieBot.resourceBundle;
/** /**
* @author Andrew Lalis * @author Andrew Lalis
* Class to simplify file operations. * Class to simplify file operations.
*/ */
public class FileUtil { public class FileUtil {
//TODO: externalize strings
public static String getDataDirectory(){ public static String getDataDirectory(){
return System.getProperty("user.home")+"/.handiebot/"; return System.getProperty("user.home")+"/.handiebot/";
} }
@ -39,12 +41,12 @@ public class FileUtil {
try { try {
boolean success = file.createNewFile(); boolean success = file.createNewFile();
if (!success) { if (!success) {
log.log(BotLog.TYPE.ERROR, "Unable to create file. "+file.getAbsolutePath()); log.log(BotLog.TYPE.ERROR, MessageFormat.format(resourceBundle.getString("fileutil.fileCreateError"), file.getAbsolutePath()));
return; return;
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
log.log(BotLog.TYPE.ERROR, "Unable to create file. "+file.getAbsolutePath()); log.log(BotLog.TYPE.ERROR, MessageFormat.format(resourceBundle.getString("fileutil.fileCreateError"), file.getAbsolutePath()));
return; return;
} }
} }
@ -54,7 +56,7 @@ public class FileUtil {
} }
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
log.log(BotLog.TYPE.ERROR, "Unable to write to file. "+file.getAbsolutePath()); log.log(BotLog.TYPE.ERROR, MessageFormat.format(resourceBundle.getString("fileutil.writeError"), file.getAbsolutePath()));
} }
} }

View File

@ -22,7 +22,7 @@ import java.util.List;
*/ */
public class Pastebin { public class Pastebin {
private static String PASTEBIN_KEY = "769adc01154922ece448cabd7a33b57c"; private static final String PASTEBIN_KEY = "769adc01154922ece448cabd7a33b57c";
public static String paste(String title, String content){ public static String paste(String title, String content){
HttpClient client = HttpClients.createDefault(); HttpClient client = HttpClients.createDefault();

View File

@ -25,9 +25,7 @@ public class CommandLineListener implements KeyListener {
commandLine.setText(null); commandLine.setText(null);
String command = words[0]; String command = words[0];
String[] args = new String[words.length-1]; String[] args = new String[words.length-1];
for (int i = 1; i < words.length; i++) { System.arraycopy(words, 1, args, 0, words.length - 1);
args[i-1] = words[i];
}
executeCommand(command, args); executeCommand(command, args);
} }
} }

View File

@ -111,5 +111,10 @@ player.playQueueEmpty=There's nothing in the queue to play.
#Track scheduler #Track scheduler
trackSchedule.trackStarted=Started audio track: {0} trackSchedule.trackStarted=Started audio track: {0}
trackSchedule.nowPlaying=Now playing: **{0}** {1} trackSchedule.nowPlaying=Now playing: **{0}** {1}
#File utils
fileutil.fileCreateError=Unable to create file. {0}
fileutil.writeError=Unable to write to file. {0}
#Playlist strings
playlist.empty=There are no songs in this playlist.

View File

@ -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
log.loggingIn=Logging client in... log.loggingIn=Logging client in...
log.init=HandieBot initialized. log.init=HandieBot initialized.
log.shuttingDown=Shutting down the bot. 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
window.close.question=Are you sure you want to exit and shutdown the bot? window.close.question=Are you sure you want to exit and shutdown the bot?
window.close.title=Confirm shutdown window.close.title=Confirm shutdown
@ -11,6 +16,7 @@ menu.filemenu.quit=Quit
#Generic Command Messages #Generic Command Messages
commands.noPermission.message=You do not have permission to use the command `{0}`. commands.noPermission.message=You do not have permission to use the command `{0}`.
commands.noPermission.log=User {0} does not have permission to execute {1} commands.noPermission.log=User {0} does not have permission to execute {1}
commands.noPermission.subcommand=You don't have permission to do that.
commands.invalidCommand.noContext=Invalid command issued: {0} commands.invalidCommand.noContext=Invalid command issued: {0}
commands.invalidCommand.context=Invalid command: {0} issued by: {1} commands.invalidCommand.context=Invalid command: {0} issued by: {1}
#Messages for specific commands. #Messages for specific commands.
@ -69,6 +75,7 @@ commands.command.playlist.move.message=Moved song *{0}* from position {1} to pos
commands.command.playlist.move.log=Moved song {0} from position {1} to position {2} commands.command.playlist.move.log=Moved song {0} from position {1} to position {2}
commands.command.playlist.error.moveInvalidIndex=The song indices are invalid. You specified moving song {0} to position {1}. commands.command.playlist.error.moveInvalidIndex=The song indices are invalid. You specified moving song {0} to position {1}.
commands.command.playlist.error.moveBadArgs=You must provide a playlist name, followed by the song index, and a new index for that song. commands.command.playlist.error.moveBadArgs=You must provide a playlist name, followed by the song index, and a new index for that song.
#Queue
commands.command.queue.description.main=Shows the first 10 songs in the queue. commands.command.queue.description.main=Shows the first 10 songs in the queue.
commands.command.queue.description.all=Shows all songs. commands.command.queue.description.all=Shows all songs.
commands.command.queue.description.clear=Clears the queue and stops playing. commands.command.queue.description.clear=Clears the queue and stops playing.
@ -76,30 +83,38 @@ commands.command.queue.description.save=Saves the queue to a playlist.
commands.command.queue.clear=Cleared queue. commands.command.queue.clear=Cleared queue.
commands.command.queue.save.message=Saved {0} tracks to playlist **{1}**. commands.command.queue.save.message=Saved {0} tracks to playlist **{1}**.
commands.command.queue.save.log=Saved queue to playlist [{0}]. commands.command.queue.save.log=Saved queue to playlist [{0}].
#Repeat
commands.command.repeat.description=Sets repeating. commands.command.repeat.description=Sets repeating.
#Shuffle
commands.command.shuffle.description=Sets shuffling. commands.command.shuffle.description=Sets shuffling.
#Skip
commands.command.skip.description=Skips the current song. commands.command.skip.description=Skips the current song.
#Stop
commands.command.stop.description=Stops playing music. 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. #Tengwar translator
log.creatingChatChannel=No chat channel found, creating a new one. commands.command.tengwar.description=Translates text to tengwar, or decodes tengwar text back into human readable form.
log.newVoiceChannel=No voice channel found, creating a new one. #Music Player
player.setRepeat=Set repeat to {0} player.setRepeat=Set **Repeat** to *{0}*.
player.setShuffle=Set shuffle to {0} player.setShuffle=Set **Shuffle** to *{0}*.
player.getRepeat=**Repeat** is set to *{0}*.
player.getShuffle=**Shuffle** is set to *{0}*.
player.queueEmpty=The queue is empty. Use `{0}` to add songs. player.queueEmpty=The queue is empty. Use `{0}` to add songs.
player.queueUploaded=Queue uploaded to pastebin: {0} player.queueUploaded=Queue uploaded to pastebin: {0}.
player.pastebinLink=You may view the full queue by following the link: {0}\ player.pastebinLink=You may view the full queue by following the link: {0}\nNote that this link expires in 10 minutes.
Note that this link expires in 10 minutes. player.pastebinError=Unable to upload to pastebin: {0}.
player.pastebinError=Unable to upload to pastebin: {0}
player.queueHeader=Showing {0} track{1} out of {2}. player.queueHeader=Showing {0} track{1} out of {2}.
player.addedToQueue=Added **{0}** to the queue. player.addedToQueue={0} added **{1}** to the queue.
player.queueCleared=Cleared the queue. player.queueCleared=Cleared the queue.
player.skippingCurrent=Skipping the current track. player.skippingCurrent=Skipping the current track.
player.musicStopped=Stopped playing music. 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. player.playQueueEmpty=There's nothing in the queue to play.
commands.command.tengwar.description=Translates text to tengwar, or decodes tengwar text back into human readable form. #Track scheduler
commands.noPermissions=You don't have permission to do that. trackSchedule.trackStarted=Started audio track: {0}
player.getRepeat=Repeat is set to *{0}*. trackSchedule.nowPlaying=Now playing: **{0}** {1}
#File utils
fileutil.fileCreateError=Unable to create file. {0}
fileutil.writeError=Unable to write to file. {0}
#Playlist strings
playlist.empty=There are no songs in this playlist.