diff --git a/src/main/java/handiebot/HandieBot.java b/src/main/java/handiebot/HandieBot.java index d973651..20b1e3d 100644 --- a/src/main/java/handiebot/HandieBot.java +++ b/src/main/java/handiebot/HandieBot.java @@ -30,7 +30,9 @@ import java.util.*; */ public class HandieBot { + //Application name is the name of application as it appears on display window. 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; static { TOKEN = readToken(); @@ -39,15 +41,16 @@ public class HandieBot { System.exit(-1); } } + //Variable to enable or disable GUI. private static boolean USE_GUI = true; - - public static final ResourceBundle resourceBundle = ResourceBundle.getBundle("Strings"); + //Resource bundle for localized strings. + public static ResourceBundle resourceBundle = ResourceBundle.getBundle("Strings"); //Discord client object. public static IDiscordClient client; //Display objects. - private static BotWindow window; + public static BotWindow window; public static BotLog log; //The cross-guild music player. diff --git a/src/main/java/handiebot/command/Commands.java b/src/main/java/handiebot/command/Commands.java index a5459fc..051fcea 100644 --- a/src/main/java/handiebot/command/Commands.java +++ b/src/main/java/handiebot/command/Commands.java @@ -1,5 +1,6 @@ package handiebot.command; +import handiebot.command.commands.admin.BroadcastCommand; import handiebot.command.commands.admin.QuitCommand; import handiebot.command.commands.admin.SetPrefixCommand; import handiebot.command.commands.misc.TengwarCommand; @@ -42,6 +43,7 @@ public class Commands { commands.add(new InfoCommand()); commands.add(new SetPrefixCommand()); commands.add(new QuitCommand()); + commands.add(new BroadcastCommand()); commands.add(new TengwarCommand()); } diff --git a/src/main/java/handiebot/command/commands/admin/BroadcastCommand.java b/src/main/java/handiebot/command/commands/admin/BroadcastCommand.java new file mode 100644 index 0000000..763a179 --- /dev/null +++ b/src/main/java/handiebot/command/commands/admin/BroadcastCommand.java @@ -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", + "", + 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)); + } + } + +} diff --git a/src/main/java/handiebot/command/commands/admin/QuitCommand.java b/src/main/java/handiebot/command/commands/admin/QuitCommand.java index ae27f8f..2ddd76a 100644 --- a/src/main/java/handiebot/command/commands/admin/QuitCommand.java +++ b/src/main/java/handiebot/command/commands/admin/QuitCommand.java @@ -1,6 +1,7 @@ package handiebot.command.commands.admin; import handiebot.HandieBot; +import handiebot.command.types.CommandLineCommand; import handiebot.command.types.StaticCommand; import static handiebot.HandieBot.resourceBundle; @@ -9,7 +10,7 @@ import static handiebot.HandieBot.resourceBundle; * @author Andrew Lalis * 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() { super("quit", diff --git a/src/main/java/handiebot/command/commands/music/PlaylistCommand.java b/src/main/java/handiebot/command/commands/music/PlaylistCommand.java index 1b0b369..af53b0c 100644 --- a/src/main/java/handiebot/command/commands/music/PlaylistCommand.java +++ b/src/main/java/handiebot/command/commands/music/PlaylistCommand.java @@ -20,8 +20,7 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; -import static handiebot.HandieBot.log; -import static handiebot.HandieBot.resourceBundle; +import static handiebot.HandieBot.*; import static handiebot.utils.MessageUtils.sendMessage; import static handiebot.utils.YoutubeSearch.WATCH_URL; @@ -101,13 +100,16 @@ public class PlaylistCommand extends ContextCommand { if (context.getArgs().length >= 2) { Playlist playlist = new Playlist(context.getArgs()[1]); playlist.save(); - for (int i = 2; i < context.getArgs().length; i++){ - String url = context.getArgs()[i]; - playlist.loadTrack(url); + if (context.getArgs().length > 2) { + for (int i = 2; i < context.getArgs().length; i++) { + String url = context.getArgs()[i]; + 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())); 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 { sendMessage(resourceBundle.getString("commands.command.playlist.error.createPlaylistName"), context.getChannel()); } @@ -126,6 +128,7 @@ public class PlaylistCommand extends ContextCommand { if (success){ 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()); + window.updatePlaylistNames();//Refresh the list of names in the GUI. } else { 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()); diff --git a/src/main/java/handiebot/command/types/CommandLineCommand.java b/src/main/java/handiebot/command/types/CommandLineCommand.java new file mode 100644 index 0000000..7337a7b --- /dev/null +++ b/src/main/java/handiebot/command/types/CommandLineCommand.java @@ -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 { +} diff --git a/src/main/java/handiebot/command/types/ReactionListener.java b/src/main/java/handiebot/command/types/ReactionListener.java index c246081..2dfe35e 100644 --- a/src/main/java/handiebot/command/types/ReactionListener.java +++ b/src/main/java/handiebot/command/types/ReactionListener.java @@ -8,6 +8,6 @@ import sx.blah.discord.handle.impl.events.guild.channel.message.reaction.Reactio */ public interface ReactionListener { - public void onReactionEvent(ReactionEvent event); + void onReactionEvent(ReactionEvent event); } diff --git a/src/main/java/handiebot/lavaplayer/playlist/Playlist.java b/src/main/java/handiebot/lavaplayer/playlist/Playlist.java index 22b3645..7e91c9e 100644 --- a/src/main/java/handiebot/lavaplayer/playlist/Playlist.java +++ b/src/main/java/handiebot/lavaplayer/playlist/Playlist.java @@ -22,7 +22,7 @@ import static handiebot.HandieBot.resourceBundle; * on the playlist. */ public class Playlist { - +//TODO: Externalize strings. private String name; private List tracks; @@ -119,7 +119,7 @@ public class Playlist { * @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. */ - public static int getShuffledIndex(int listLength){ + private static int getShuffledIndex(int listLength){ float threshold = 0.2f; int trueLength = listLength - (int)(threshold*(float)listLength); Random rand = new Random(); @@ -160,13 +160,11 @@ public class Playlist { */ public void load(){ 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); if (playlistFile.exists()){ try { List lines = Files.readAllLines(Paths.get(playlistFile.toURI())); int trackCount = Integer.parseInt(lines.remove(0)); - this.name = name; this.tracks = new ArrayList<>(trackCount); for (int i = 0; i < trackCount; i++){ String[] words = lines.remove(0).split(" / "); diff --git a/src/main/java/handiebot/view/BotWindow.java b/src/main/java/handiebot/view/BotWindow.java index ef06b58..222e1f1 100644 --- a/src/main/java/handiebot/view/BotWindow.java +++ b/src/main/java/handiebot/view/BotWindow.java @@ -2,7 +2,6 @@ package handiebot.view; import handiebot.HandieBot; import handiebot.lavaplayer.playlist.Playlist; -import handiebot.lavaplayer.playlist.UnloadedTrack; import javax.imageio.ImageIO; import javax.swing.*; @@ -30,6 +29,7 @@ public class BotWindow extends JFrame { private JList playlistNamesList; private JList currentPlaylistList; private ListSelectionListener playlistListener; + private JPanel playlistDisplayPanel; public BotWindow(){ super(HandieBot.APPLICATION_NAME); @@ -37,6 +37,7 @@ public class BotWindow extends JFrame { //Output area. outputArea = new JTextPane(); outputArea.setBackground(Color.white); + outputArea.setEditable(false); JScrollPane scrollPane = new JScrollPane(); scrollPane.setViewportView(outputArea); scrollPane.setAutoscrolls(true); @@ -49,24 +50,24 @@ public class BotWindow extends JFrame { this.playlistNamesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); this.currentPlaylistList = new JList<>(this.currentPlaylistModel); 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. - JScrollPane songNamesScrollPane = new JScrollPane(this.currentPlaylistList); - songNamesScrollPane.setPreferredSize(new Dimension(250, 200)); - playlistDisplayPanel.add(songNamesScrollPane, BorderLayout.PAGE_END); + //Create the panel to hold both of the sub-panels. + playlistDisplayPanel = new JPanel(); + playlistDisplayPanel.setPreferredSize(new Dimension(250, 0)); + playlistDisplayPanel.setLayout(new BorderLayout()); //Playlist name scroll pane. JScrollPane playlistNamesScrollPane = new JScrollPane(playlistNamesList); - playlistNamesScrollPane.setPreferredSize(new Dimension(250, 1000)); - playlistDisplayPanel.add(playlistNamesScrollPane, BorderLayout.CENTER); + playlistNamesScrollPane.setColumnHeaderView(new JLabel("Playlists")); + 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); @@ -99,7 +100,6 @@ public class BotWindow extends JFrame { e.printStackTrace(); } setJMenuBar(new MenuBar(this)); - //SelectionController controller = new SelectionController(); setPreferredSize(new Dimension(800, 600)); pack(); 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 playlistNames = Playlist.getAvailablePlaylists(); - this.playlistNamesList.removeListSelectionListener(this.playlistListener); this.playlistNamesModel.clear(); for (String name : playlistNames){ 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 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 -> { - String name = playlistNamesList.getSelectedValue(); - String path = System.getProperty("user.home") + "/.handiebot/playlist/" + name + ".txt"; - File playlistFile = new File(path); - if (playlistFile.exists()) { - try { - List 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(); - } - } - });*/ + } + + /** + * Sets the playlists panel as visible or invisible. + */ + public void togglePlaylistsVisibility(){ + this.playlistDisplayPanel.setVisible(!this.playlistDisplayPanel.isVisible()); } public JTextPane getOutputArea(){ diff --git a/src/main/java/handiebot/view/CommandLineListener.java b/src/main/java/handiebot/view/CommandLineListener.java index d671bbf..9435b35 100644 --- a/src/main/java/handiebot/view/CommandLineListener.java +++ b/src/main/java/handiebot/view/CommandLineListener.java @@ -1,11 +1,19 @@ 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 java.awt.event.KeyEvent; import java.awt.event.KeyListener; +import static handiebot.HandieBot.log; +import static handiebot.command.Commands.commands; + /** * @author Andrew Lalis * Class to listen for commands from the console command line. @@ -26,7 +34,7 @@ public class CommandLineListener implements KeyListener { String command = words[0]; String[] args = new String[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 args The list of arguments for the command. + * @param context The list of arguments for the command. */ - private void executeCommand(String command, String[] args) { - switch (command) { - case "quit": - Commands.executeCommand("quit", null); - break; + private void executeCommand(String command, CommandContext context) { + for (Command cmd : commands){ + if (cmd.getName().equals(command) && (cmd instanceof CommandLineCommand)){ + log.log(BotLog.TYPE.COMMAND, "Command issued: "+command); + if (cmd instanceof StaticCommand){ + ((StaticCommand) cmd).execute(); + } else if (cmd instanceof ContextCommand){ + ((ContextCommand) cmd).execute(context); + } + } } } } diff --git a/src/main/java/handiebot/view/MenuBar.java b/src/main/java/handiebot/view/MenuBar.java index 6440b4a..7a337fd 100644 --- a/src/main/java/handiebot/view/MenuBar.java +++ b/src/main/java/handiebot/view/MenuBar.java @@ -5,6 +5,8 @@ import handiebot.view.actions.ActionItem; import handiebot.view.actions.CommandAction; import javax.swing.*; +import java.util.Locale; +import java.util.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. */ public class MenuBar extends JMenuBar { - +//TODO: Implement a way to restart the program in nederlands. private BotWindow window; private int language; @@ -24,6 +26,10 @@ public class MenuBar extends JMenuBar { this.add(fileMenu); JMenu viewMenu = new JMenu(resourceBundle.getString("menu.viewMenu.view")); 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); } diff --git a/src/main/java/handiebot/view/PlaylistSelectionListener.java b/src/main/java/handiebot/view/PlaylistSelectionListener.java new file mode 100644 index 0000000..8135911 --- /dev/null +++ b/src/main/java/handiebot/view/PlaylistSelectionListener.java @@ -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 songsListModel; + + public PlaylistSelectionListener(DefaultListModel songsListModel){ + this.songsListModel = songsListModel; + } + + @Override + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting()){ + updatePlaylistData((JList) e.getSource()); + } + } + + /** + * Updates the list of songs for a selected playlist. + * Does not update the list of playlists. + */ + private void updatePlaylistData(JList playlistNamesList) { + String selectedValue = playlistNamesList.getSelectedValue(); + if (selectedValue != null && Playlist.playlistExists(selectedValue)){ + Playlist playlist = new Playlist(selectedValue); + playlist.load(); + List tracks = playlist.getTracks(); + songsListModel.clear(); + for (int i = 0; i < playlist.getTrackCount(); i++){ + songsListModel.addElement(tracks.get(i).getTitle()); + } + } + } + +} diff --git a/src/main/resources/Strings.properties b/src/main/resources/Strings.properties index fbb662f..6d23d25 100644 --- a/src/main/resources/Strings.properties +++ b/src/main/resources/Strings.properties @@ -16,9 +16,11 @@ window.close.title=Confirm shutdown #MenuBar menu.fileMenu.title=File menu.fileMenu.quit=Quit -menu.editMenu.edit=Edit menu.viewMenu.view=View menu.viewMenu.language=Language +menu.viewMenu.language.english=English +menu.viewMenu.language.dutch=Dutch +menu.viewMenu.playlistsVisible=Playlists #Actions action.menu.playlist=Edit playlists 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.queueCommand=Show a list of songs that will soon be played. 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.changed=Changed command prefix to "{0}" commands.command.setPrefix.noPrefixError=You must provide a new prefix.