Finalized Zino's changes, and many other things.

* added broadcast command
* added interface for distinguishing command line commands
* added button to switch from english to dutch (work in progress)
* fixed playlist viewer and now uses list models.
This commit is contained in:
Andrew Lalis 2017-07-22 15:46:03 +02:00 committed by Andrew Lalis
parent b842ce17c2
commit 117254e27d
13 changed files with 168 additions and 79 deletions

View File

@ -30,7 +30,9 @@ import java.util.*;
*/ */
public class HandieBot { public class HandieBot {
//Application name is the name of application as it appears on display window.
public static final String APPLICATION_NAME = "HandieBot"; public static final String APPLICATION_NAME = "HandieBot";
//The token required for logging into Discord. This is secure and must not be in the source code on GitHub.
private static final String TOKEN; private static final String TOKEN;
static { static {
TOKEN = readToken(); TOKEN = readToken();
@ -39,15 +41,16 @@ public class HandieBot {
System.exit(-1); System.exit(-1);
} }
} }
//Variable to enable or disable GUI.
private static boolean USE_GUI = true; private static boolean USE_GUI = true;
//Resource bundle for localized strings.
public static final ResourceBundle resourceBundle = ResourceBundle.getBundle("Strings"); public static ResourceBundle resourceBundle = ResourceBundle.getBundle("Strings");
//Discord client object. //Discord client object.
public static IDiscordClient client; public static IDiscordClient client;
//Display objects. //Display objects.
private static BotWindow window; public static BotWindow window;
public static BotLog log; public static BotLog log;
//The cross-guild music player. //The cross-guild music player.

View File

@ -1,5 +1,6 @@
package handiebot.command; package handiebot.command;
import handiebot.command.commands.admin.BroadcastCommand;
import handiebot.command.commands.admin.QuitCommand; import handiebot.command.commands.admin.QuitCommand;
import handiebot.command.commands.admin.SetPrefixCommand; import handiebot.command.commands.admin.SetPrefixCommand;
import handiebot.command.commands.misc.TengwarCommand; import handiebot.command.commands.misc.TengwarCommand;
@ -42,6 +43,7 @@ public class Commands {
commands.add(new InfoCommand()); commands.add(new InfoCommand());
commands.add(new SetPrefixCommand()); commands.add(new SetPrefixCommand());
commands.add(new QuitCommand()); commands.add(new QuitCommand());
commands.add(new BroadcastCommand());
commands.add(new TengwarCommand()); commands.add(new TengwarCommand());
} }

View File

@ -0,0 +1,33 @@
package handiebot.command.commands.admin;
import handiebot.command.CommandContext;
import handiebot.command.types.CommandLineCommand;
import handiebot.command.types.ContextCommand;
import handiebot.utils.MessageUtils;
import sx.blah.discord.handle.obj.IGuild;
import static handiebot.HandieBot.*;
import static handiebot.utils.MessageUtils.sendMessage;
/**
* @author Andrew Lalis
* Command to broadcast a message to all guilds the bot is connected to.
*/
public class BroadcastCommand extends ContextCommand implements CommandLineCommand {
public BroadcastCommand() {
super("broadcast",
"<message>",
resourceBundle.getString("commands.command.broadcast.description"),
8);
}
@Override
public void execute(CommandContext context) {
String message = MessageUtils.getTextFromArgs(context.getArgs(), 0);
for (IGuild guild : client.getGuilds()){
sendMessage(message, musicPlayer.getChatChannel(guild));
}
}
}

View File

@ -1,6 +1,7 @@
package handiebot.command.commands.admin; package handiebot.command.commands.admin;
import handiebot.HandieBot; import handiebot.HandieBot;
import handiebot.command.types.CommandLineCommand;
import handiebot.command.types.StaticCommand; import handiebot.command.types.StaticCommand;
import static handiebot.HandieBot.resourceBundle; import static handiebot.HandieBot.resourceBundle;
@ -9,7 +10,7 @@ import static handiebot.HandieBot.resourceBundle;
* @author Andrew Lalis * @author Andrew Lalis
* Command to quit the entire bot. This shuts down every guild's support, and the GUI. * Command to quit the entire bot. This shuts down every guild's support, and the GUI.
*/ */
public class QuitCommand extends StaticCommand { public class QuitCommand extends StaticCommand implements CommandLineCommand {
public QuitCommand() { public QuitCommand() {
super("quit", super("quit",

View File

@ -20,8 +20,7 @@ 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.*;
import static handiebot.HandieBot.resourceBundle;
import static handiebot.utils.MessageUtils.sendMessage; import static handiebot.utils.MessageUtils.sendMessage;
import static handiebot.utils.YoutubeSearch.WATCH_URL; import static handiebot.utils.YoutubeSearch.WATCH_URL;
@ -101,13 +100,16 @@ public class PlaylistCommand extends ContextCommand {
if (context.getArgs().length >= 2) { if (context.getArgs().length >= 2) {
Playlist playlist = new Playlist(context.getArgs()[1]); Playlist playlist = new Playlist(context.getArgs()[1]);
playlist.save(); playlist.save();
if (context.getArgs().length > 2) {
for (int i = 2; i < context.getArgs().length; i++) { for (int i = 2; i < context.getArgs().length; i++) {
String url = context.getArgs()[i]; String url = context.getArgs()[i];
playlist.loadTrack(url); playlist.loadTrack(url);
} }
playlist.save(); playlist.save();
}
log.log(BotLog.TYPE.INFO, MessageFormat.format(resourceBundle.getString("commands.command.playlist.createdPlaylist.log"), playlist.getName(), playlist.getTrackCount())); log.log(BotLog.TYPE.INFO, MessageFormat.format(resourceBundle.getString("commands.command.playlist.createdPlaylist.log"), playlist.getName(), playlist.getTrackCount()));
sendMessage(MessageFormat.format(resourceBundle.getString("commands.command.playlist.createdPlaylist.message"), playlist.getName(), this.getPrefixedName(context.getGuild()), playlist.getName()), context.getChannel()); sendMessage(MessageFormat.format(resourceBundle.getString("commands.command.playlist.createdPlaylist.message"), playlist.getName(), this.getPrefixedName(context.getGuild()), playlist.getName()), context.getChannel());
window.updatePlaylistNames();//Refresh the list of names in the GUI.
} else { } else {
sendMessage(resourceBundle.getString("commands.command.playlist.error.createPlaylistName"), context.getChannel()); sendMessage(resourceBundle.getString("commands.command.playlist.error.createPlaylistName"), context.getChannel());
} }
@ -126,6 +128,7 @@ public class PlaylistCommand extends ContextCommand {
if (success){ if (success){
log.log(BotLog.TYPE.INFO, MessageFormat.format(resourceBundle.getString("commands.command.playlist.delete.log"), context.getArgs()[1])); log.log(BotLog.TYPE.INFO, MessageFormat.format(resourceBundle.getString("commands.command.playlist.delete.log"), context.getArgs()[1]));
sendMessage(MessageFormat.format(resourceBundle.getString("commands.command.playlist.delete.message"), context.getArgs()[1]), context.getChannel()); sendMessage(MessageFormat.format(resourceBundle.getString("commands.command.playlist.delete.message"), context.getArgs()[1]), context.getChannel());
window.updatePlaylistNames();//Refresh the list of names in the GUI.
} else { } else {
log.log(BotLog.TYPE.ERROR, MessageFormat.format(resourceBundle.getString("commands.command.playlist.error.delete.log"), context.getArgs()[1])); log.log(BotLog.TYPE.ERROR, MessageFormat.format(resourceBundle.getString("commands.command.playlist.error.delete.log"), context.getArgs()[1]));
sendMessage(resourceBundle.getString("commands.command.playlist.error.delete.message"), context.getChannel()); sendMessage(resourceBundle.getString("commands.command.playlist.error.delete.message"), context.getChannel());

View File

@ -0,0 +1,8 @@
package handiebot.command.types;
/**
* @author Andrew Lalis
* If a command implements this Interface, it is marked as safe to use in the command line.
*/
public interface CommandLineCommand {
}

View File

@ -8,6 +8,6 @@ import sx.blah.discord.handle.impl.events.guild.channel.message.reaction.Reactio
*/ */
public interface ReactionListener { public interface ReactionListener {
public void onReactionEvent(ReactionEvent event); void onReactionEvent(ReactionEvent event);
} }

View File

@ -22,7 +22,7 @@ import static handiebot.HandieBot.resourceBundle;
* 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;
@ -119,7 +119,7 @@ public class Playlist {
* @param listLength The number of items in a potential list to choose from. * @param listLength The number of items in a potential list to choose from.
* @return A pseudo-random choice as to which item to pick from the list. * @return A pseudo-random choice as to which item to pick from the list.
*/ */
public static int getShuffledIndex(int listLength){ private static int getShuffledIndex(int listLength){
float threshold = 0.2f; float threshold = 0.2f;
int trueLength = listLength - (int)(threshold*(float)listLength); int trueLength = listLength - (int)(threshold*(float)listLength);
Random rand = new Random(); Random rand = new Random();
@ -160,13 +160,11 @@ public class Playlist {
*/ */
public void load(){ public void load(){
String path = System.getProperty("user.home")+"/.handiebot/playlist/"+name.replace(" ", "_")+".txt"; String path = System.getProperty("user.home")+"/.handiebot/playlist/"+name.replace(" ", "_")+".txt";
log.log(BotLog.TYPE.MUSIC, "Loading playlist from: "+path);
File playlistFile = new File(path); File playlistFile = new File(path);
if (playlistFile.exists()){ if (playlistFile.exists()){
try { try {
List<String> lines = Files.readAllLines(Paths.get(playlistFile.toURI())); List<String> lines = Files.readAllLines(Paths.get(playlistFile.toURI()));
int trackCount = Integer.parseInt(lines.remove(0)); int trackCount = Integer.parseInt(lines.remove(0));
this.name = name;
this.tracks = new ArrayList<>(trackCount); this.tracks = new ArrayList<>(trackCount);
for (int i = 0; i < trackCount; i++){ for (int i = 0; i < trackCount; i++){
String[] words = lines.remove(0).split(" / "); String[] words = lines.remove(0).split(" / ");

View File

@ -2,7 +2,6 @@ package handiebot.view;
import handiebot.HandieBot; import handiebot.HandieBot;
import handiebot.lavaplayer.playlist.Playlist; import handiebot.lavaplayer.playlist.Playlist;
import handiebot.lavaplayer.playlist.UnloadedTrack;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.swing.*; import javax.swing.*;
@ -30,6 +29,7 @@ public class BotWindow extends JFrame {
private JList<String> playlistNamesList; private JList<String> playlistNamesList;
private JList<String> currentPlaylistList; private JList<String> currentPlaylistList;
private ListSelectionListener playlistListener; private ListSelectionListener playlistListener;
private JPanel playlistDisplayPanel;
public BotWindow(){ public BotWindow(){
super(HandieBot.APPLICATION_NAME); super(HandieBot.APPLICATION_NAME);
@ -37,6 +37,7 @@ public class BotWindow extends JFrame {
//Output area. //Output area.
outputArea = new JTextPane(); outputArea = new JTextPane();
outputArea.setBackground(Color.white); outputArea.setBackground(Color.white);
outputArea.setEditable(false);
JScrollPane scrollPane = new JScrollPane(); JScrollPane scrollPane = new JScrollPane();
scrollPane.setViewportView(outputArea); scrollPane.setViewportView(outputArea);
scrollPane.setAutoscrolls(true); scrollPane.setAutoscrolls(true);
@ -49,24 +50,24 @@ public class BotWindow extends JFrame {
this.playlistNamesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); this.playlistNamesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
this.currentPlaylistList = new JList<>(this.currentPlaylistModel); this.currentPlaylistList = new JList<>(this.currentPlaylistModel);
this.currentPlaylistList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); this.currentPlaylistList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
this.playlistListener = e -> {
System.out.println("user updated list.");
updatePlaylistData();
};
this.playlistNamesList.addListSelectionListener(this.playlistListener);
updatePlaylistData();
JPanel playlistDisplayPanel = new JPanel(new BorderLayout()); updatePlaylistNames();
this.playlistNamesList.addListSelectionListener(new PlaylistSelectionListener(this.currentPlaylistModel));
//Song names scroll pane. //Create the panel to hold both of the sub-panels.
JScrollPane songNamesScrollPane = new JScrollPane(this.currentPlaylistList); playlistDisplayPanel = new JPanel();
songNamesScrollPane.setPreferredSize(new Dimension(250, 200)); playlistDisplayPanel.setPreferredSize(new Dimension(250, 0));
playlistDisplayPanel.add(songNamesScrollPane, BorderLayout.PAGE_END); playlistDisplayPanel.setLayout(new BorderLayout());
//Playlist name scroll pane. //Playlist name scroll pane.
JScrollPane playlistNamesScrollPane = new JScrollPane(playlistNamesList); JScrollPane playlistNamesScrollPane = new JScrollPane(playlistNamesList);
playlistNamesScrollPane.setPreferredSize(new Dimension(250, 1000)); playlistNamesScrollPane.setColumnHeaderView(new JLabel("Playlists"));
playlistDisplayPanel.add(playlistNamesScrollPane, BorderLayout.CENTER); playlistDisplayPanel.add(playlistNamesScrollPane, BorderLayout.PAGE_START);
//Song names scroll pane.
JScrollPane songNamesScrollPane = new JScrollPane(this.currentPlaylistList);
songNamesScrollPane.setColumnHeaderView(new JLabel("Selected Playlist"));
playlistDisplayPanel.add(songNamesScrollPane, BorderLayout.CENTER);
getContentPane().add(playlistDisplayPanel, BorderLayout.EAST); getContentPane().add(playlistDisplayPanel, BorderLayout.EAST);
@ -99,7 +100,6 @@ public class BotWindow extends JFrame {
e.printStackTrace(); e.printStackTrace();
} }
setJMenuBar(new MenuBar(this)); setJMenuBar(new MenuBar(this));
//SelectionController controller = new SelectionController();
setPreferredSize(new Dimension(800, 600)); setPreferredSize(new Dimension(800, 600));
pack(); pack();
setLocationRelativeTo(null); setLocationRelativeTo(null);
@ -107,50 +107,21 @@ public class BotWindow extends JFrame {
} }
/** /**
* Sets the playlist data in the window. * Updates the list of playlist names.
*/ */
private void updatePlaylistData() { public void updatePlaylistNames(){
List<String> playlistNames = Playlist.getAvailablePlaylists(); List<String> playlistNames = Playlist.getAvailablePlaylists();
this.playlistNamesList.removeListSelectionListener(this.playlistListener);
this.playlistNamesModel.clear(); this.playlistNamesModel.clear();
for (String name : playlistNames){ for (String name : playlistNames){
this.playlistNamesModel.addElement(name); this.playlistNamesModel.addElement(name);
} }
this.playlistNamesList.addListSelectionListener(this.playlistListener);
String selectedValue = this.playlistNamesList.getSelectedValue();
System.out.println("selected value: "+selectedValue);
if (selectedValue != null && Playlist.playlistExists(selectedValue)){
Playlist playlist = new Playlist(selectedValue);
playlist.load();
List<UnloadedTrack> tracks = playlist.getTracks();
this.currentPlaylistModel.clear();
for (int i = 0; i < playlist.getTrackCount(); i++){
this.currentPlaylistModel.addElement(tracks.get(i).getTitle());
} }
}
/* /**
this.playlistNamesList.addListSelectionListener(e -> { * Sets the playlists panel as visible or invisible.
String name = playlistNamesList.getSelectedValue(); */
String path = System.getProperty("user.home") + "/.handiebot/playlist/" + name + ".txt"; public void togglePlaylistsVisibility(){
File playlistFile = new File(path); this.playlistDisplayPanel.setVisible(!this.playlistDisplayPanel.isVisible());
if (playlistFile.exists()) {
try {
List<String> lines = Files.readAllLines(Paths.get(playlistFile.toURI()));
int trackCount = Integer.parseInt(lines.remove(0));
String[] words = {"A"};
StringBuilder sb = new StringBuilder();
for (int i1 = 0; i1 < trackCount; i1++) {
words = lines.remove(0).split(" / ");
sb.append(i1 +1).append(". ").append(words[0]).append("\n");
}
if(!words[0].equals("A")) {
pane.setText(sb.toString());
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
});*/
} }
public JTextPane getOutputArea(){ public JTextPane getOutputArea(){

View File

@ -1,11 +1,19 @@
package handiebot.view; package handiebot.view;
import handiebot.command.Commands; import handiebot.HandieBot;
import handiebot.command.CommandContext;
import handiebot.command.types.Command;
import handiebot.command.types.CommandLineCommand;
import handiebot.command.types.ContextCommand;
import handiebot.command.types.StaticCommand;
import javax.swing.*; import javax.swing.*;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.awt.event.KeyListener; import java.awt.event.KeyListener;
import static handiebot.HandieBot.log;
import static handiebot.command.Commands.commands;
/** /**
* @author Andrew Lalis * @author Andrew Lalis
* Class to listen for commands from the console command line. * Class to listen for commands from the console command line.
@ -26,7 +34,7 @@ public class CommandLineListener implements KeyListener {
String command = words[0]; String command = words[0];
String[] args = new String[words.length-1]; String[] args = new String[words.length-1];
System.arraycopy(words, 1, args, 0, words.length - 1); System.arraycopy(words, 1, args, 0, words.length - 1);
executeCommand(command, args); executeCommand(command, new CommandContext(HandieBot.client.getOurUser(), null, null, args));
} }
} }
@ -35,15 +43,21 @@ public class CommandLineListener implements KeyListener {
} }
/** /**
* Executes a given command on the command line. * Executes a given command on the command line. This must be written separate from the {@code executeCommand}
* method in {@code Commands}, because here, no permissions may be checked.
* @param command The first word typed, or the command itself. * @param command The first word typed, or the command itself.
* @param args The list of arguments for the command. * @param context The list of arguments for the command.
*/ */
private void executeCommand(String command, String[] args) { private void executeCommand(String command, CommandContext context) {
switch (command) { for (Command cmd : commands){
case "quit": if (cmd.getName().equals(command) && (cmd instanceof CommandLineCommand)){
Commands.executeCommand("quit", null); log.log(BotLog.TYPE.COMMAND, "Command issued: "+command);
break; if (cmd instanceof StaticCommand){
((StaticCommand) cmd).execute();
} else if (cmd instanceof ContextCommand){
((ContextCommand) cmd).execute(context);
}
}
} }
} }
} }

View File

@ -5,6 +5,8 @@ import handiebot.view.actions.ActionItem;
import handiebot.view.actions.CommandAction; import handiebot.view.actions.CommandAction;
import javax.swing.*; import javax.swing.*;
import java.util.Locale;
import java.util.ResourceBundle;
import static handiebot.HandieBot.resourceBundle; import static handiebot.HandieBot.resourceBundle;
@ -13,7 +15,7 @@ import static handiebot.HandieBot.resourceBundle;
* Custom menu bar to be added to the console control panel. * Custom menu bar to be added to the console control panel.
*/ */
public class MenuBar extends JMenuBar { public class MenuBar extends JMenuBar {
//TODO: Implement a way to restart the program in nederlands.
private BotWindow window; private BotWindow window;
private int language; private int language;
@ -24,6 +26,10 @@ public class MenuBar extends JMenuBar {
this.add(fileMenu); this.add(fileMenu);
JMenu viewMenu = new JMenu(resourceBundle.getString("menu.viewMenu.view")); JMenu viewMenu = new JMenu(resourceBundle.getString("menu.viewMenu.view"));
JMenu language = new JMenu(resourceBundle.getString("menu.viewMenu.language")); JMenu language = new JMenu(resourceBundle.getString("menu.viewMenu.language"));
language.add(new ActionItem(resourceBundle.getString("menu.viewMenu.language.english"), e -> resourceBundle = ResourceBundle.getBundle("Strings", Locale.US)));
language.add(new ActionItem(resourceBundle.getString("menu.viewMenu.language.dutch"), e -> resourceBundle = ResourceBundle.getBundle("Strings", Locale.forLanguageTag("nl"))));
viewMenu.add(language);
viewMenu.add(new ActionItem(resourceBundle.getString("menu.viewMenu.playlistsVisible"), e -> window.togglePlaylistsVisibility()));
this.add(viewMenu); this.add(viewMenu);
} }

View File

@ -0,0 +1,47 @@
package handiebot.view;
import handiebot.lavaplayer.playlist.Playlist;
import handiebot.lavaplayer.playlist.UnloadedTrack;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.util.List;
/**
* @author Andrew Lalis
* Listens for if the user selects a playlist from the list.
*/
public class PlaylistSelectionListener implements ListSelectionListener {
private DefaultListModel<String> songsListModel;
public PlaylistSelectionListener(DefaultListModel<String> songsListModel){
this.songsListModel = songsListModel;
}
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()){
updatePlaylistData((JList<String>) e.getSource());
}
}
/**
* Updates the list of songs for a selected playlist.
* Does not update the list of playlists.
*/
private void updatePlaylistData(JList<String> playlistNamesList) {
String selectedValue = playlistNamesList.getSelectedValue();
if (selectedValue != null && Playlist.playlistExists(selectedValue)){
Playlist playlist = new Playlist(selectedValue);
playlist.load();
List<UnloadedTrack> tracks = playlist.getTracks();
songsListModel.clear();
for (int i = 0; i < playlist.getTrackCount(); i++){
songsListModel.addElement(tracks.get(i).getTitle());
}
}
}
}

View File

@ -16,9 +16,11 @@ window.close.title=Confirm shutdown
#MenuBar #MenuBar
menu.fileMenu.title=File menu.fileMenu.title=File
menu.fileMenu.quit=Quit menu.fileMenu.quit=Quit
menu.editMenu.edit=Edit
menu.viewMenu.view=View menu.viewMenu.view=View
menu.viewMenu.language=Language menu.viewMenu.language=Language
menu.viewMenu.language.english=English
menu.viewMenu.language.dutch=Dutch
menu.viewMenu.playlistsVisible=Playlists
#Actions #Actions
action.menu.playlist=Edit playlists action.menu.playlist=Edit playlists
action.menu.playlist.add=Add action.menu.playlist.add=Add
@ -40,6 +42,7 @@ commands.command.info.embed.helpCommand=Receive a message with a detailed list o
commands.command.info.embed.playCommand=Play a song, or add it to the queue if one is already playing. A URL can be a YouTube or SoundCloud link. commands.command.info.embed.playCommand=Play a song, or add it to the queue if one is already playing. A URL can be a YouTube or SoundCloud link.
commands.command.info.embed.queueCommand=Show a list of songs that will soon be played. commands.command.info.embed.queueCommand=Show a list of songs that will soon be played.
commands.command.quit.description=Shuts down the bot on all servers. commands.command.quit.description=Shuts down the bot on all servers.
commands.command.broadcast.description=Broadcasts a message to every server the bot is connected to.
commands.command.setPrefix.description=Sets the prefix for commands. commands.command.setPrefix.description=Sets the prefix for commands.
commands.command.setPrefix.changed=Changed command prefix to "{0}" commands.command.setPrefix.changed=Changed command prefix to "{0}"
commands.command.setPrefix.noPrefixError=You must provide a new prefix. commands.command.setPrefix.noPrefixError=You must provide a new prefix.