Version 1.3.0: Many changes:

* Externalized strings, so that translating phrases is much easier. There are a few log strings that must still be externalized, but all messages to discord are.
* Added downvote skipping: if more than half of the people listening to a song downvote it, the song is skipped.
* Added emojis to prominent commands to make it easier to understand what the bot is doing without having to read a lot.
* Re-ordered execution and notification to users for some commands, so that the user has a better experience.
This commit is contained in:
Andrew Lalis 2017-06-29 13:29:35 +02:00
parent ce618179fd
commit fb430ebf5b
14 changed files with 127 additions and 32 deletions

View File

@ -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>

View File

@ -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;

View File

@ -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.");
}
}
}

View File

@ -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)));
}

View File

@ -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]",

View File

@ -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]",

View File

@ -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);
}
/**

View File

@ -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();
}

View File

@ -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);

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.
*/
public class UnloadedTrack implements Cloneable {
//TODO: externalize strings
private String title;
private String url;
private long duration;

View File

@ -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;
}
}

View File

@ -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/";
}

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.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}

View File

@ -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.