diff --git a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java index cc190a3c5..efc4f1b12 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java @@ -7,12 +7,12 @@ import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.process.ProcessService; import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.service.BungeeService; +import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.task.LimboPlayerTaskManager; import fr.xephi.authme.util.PlayerUtils; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import javax.inject.Inject; @@ -33,17 +33,10 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess { @Inject private LimboPlayerTaskManager limboPlayerTaskManager; - ProcessSyncPasswordRegister() { - } + @Inject + private CommandManager commandManager; - private void forceCommands(Player player) { - for (String command : service.getProperty(RegistrationSettings.FORCE_REGISTER_COMMANDS)) { - player.performCommand(command.replace("%p", player.getName())); - } - for (String command : service.getProperty(RegistrationSettings.FORCE_REGISTER_COMMANDS_AS_CONSOLE)) { - Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), - command.replace("%p", player.getName())); - } + ProcessSyncPasswordRegister() { } /** @@ -83,7 +76,7 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess { } // Register is now finished; we can force all commands - forceCommands(player); + commandManager.runCommandsOnRegister(player); // Request login after registration if (service.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER)) { diff --git a/src/main/java/fr/xephi/authme/service/BukkitService.java b/src/main/java/fr/xephi/authme/service/BukkitService.java index 20aa23c20..603e39757 100644 --- a/src/main/java/fr/xephi/authme/service/BukkitService.java +++ b/src/main/java/fr/xephi/authme/service/BukkitService.java @@ -10,6 +10,7 @@ import org.bukkit.BanList; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.World; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.plugin.Plugin; @@ -273,6 +274,27 @@ public class BukkitService implements SettingsDependent { return Bukkit.getWorld(name); } + /** + * Dispatches a command on this server, and executes it if found. + * + * @param sender the apparent sender of the command + * @param commandLine the command + arguments. Example: test abc 123 + * @return returns false if no target is found + */ + public boolean dispatchCommand(CommandSender sender, String commandLine) { + return Bukkit.dispatchCommand(sender, commandLine); + } + + /** + * Dispatches a command to be run as console user on this server, and executes it if found. + * + * @param commandLine the command + arguments. Example: test abc 123 + * @return returns false if no target is found + */ + public boolean dispatchConsoleCommand(String commandLine) { + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), commandLine); + } + @Override public void reload(Settings settings) { useAsyncTasks = settings.getProperty(PluginSettings.USE_ASYNC_TASKS); @@ -309,13 +331,4 @@ public class BukkitService implements SettingsDependent { public BanEntry banIp(String ip, String reason, Date expires, String source) { return Bukkit.getServer().getBanList(BanList.Type.IP).addBan(ip, reason, expires, source); } - - /** - * Dispatch a command as console - * - * @param command the command - */ - public void dispatchConsoleCommand(String command) { - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command); - } } diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/Command.java b/src/main/java/fr/xephi/authme/settings/commandconfig/Command.java new file mode 100644 index 000000000..38bb59913 --- /dev/null +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/Command.java @@ -0,0 +1,28 @@ +package fr.xephi.authme.settings.commandconfig; + +/** + * Command to be run. + */ +public class Command { + + /** The command to execute. */ + private String command; + /** The executor of the command. */ + private Executor executor = Executor.PLAYER; + + public String getCommand() { + return command; + } + + public void setCommand(String command) { + this.command = command; + } + + public Executor getExecutor() { + return executor; + } + + public void setExecutor(Executor executor) { + this.executor = executor; + } +} diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandConfig.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandConfig.java new file mode 100644 index 000000000..00d35e2de --- /dev/null +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandConfig.java @@ -0,0 +1,40 @@ +package fr.xephi.authme.settings.commandconfig; + +import java.util.Collections; +import java.util.Map; + +/** + * Command configuration. + * + * @see CommandManager + */ +public class CommandConfig { + + private Map onJoin = Collections.emptyMap(); + private Map onLogin = Collections.emptyMap(); + private Map onRegister = Collections.emptyMap(); + + public Map getOnJoin() { + return onJoin; + } + + public void setOnJoin(Map onJoin) { + this.onJoin = onJoin; + } + + public Map getOnLogin() { + return onLogin; + } + + public void setOnLogin(Map onLogin) { + this.onLogin = onLogin; + } + + public Map getOnRegister() { + return onRegister; + } + + public void setOnRegister(Map onRegister) { + this.onRegister = onRegister; + } +} diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java new file mode 100644 index 000000000..ebfcce2a0 --- /dev/null +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java @@ -0,0 +1,65 @@ +package fr.xephi.authme.settings.commandconfig; + +import com.github.authme.configme.SettingsManager; +import com.github.authme.configme.resource.YamlFileResource; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.initialization.Reloadable; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.util.FileUtils; +import org.bukkit.entity.Player; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import java.io.File; +import java.util.Map; + +/** + * Manages configurable commands to be run when various events occur. + */ +public class CommandManager implements Reloadable { + + private CommandConfig commandConfig; + + @Inject + @DataFolder + private File dataFolder; + + @Inject + private BukkitService bukkitService; + + + CommandManager() { + } + + public void runCommandsOnJoin(Player player) { + executeCommands(player, commandConfig.getOnJoin()); + } + + public void runCommandsOnRegister(Player player) { + executeCommands(player, commandConfig.getOnRegister()); + } + + private void executeCommands(Player player, Map commands) { + for (Command command : commands.values()) { + final String execution = command.getCommand().replace("%p", player.getName()); + if (Executor.CONSOLE.equals(command.getExecutor())) { + bukkitService.dispatchConsoleCommand(execution); + } else { + bukkitService.dispatchCommand(player, execution); + } + } + } + + @PostConstruct + @Override + public void reload() { + File file = new File(dataFolder, "commands.yml"); + FileUtils.copyFileFromResource(file, "commands.yml"); + + SettingsManager settingsManager = new SettingsManager( + new YamlFileResource(file), null, CommandSettingsHolder.class); + commandConfig = settingsManager.getProperty(CommandSettingsHolder.COMMANDS); + } + + +} diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java new file mode 100644 index 000000000..4de2de5e1 --- /dev/null +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java @@ -0,0 +1,51 @@ +package fr.xephi.authme.settings.commandconfig; + +import com.github.authme.configme.SectionComments; +import com.github.authme.configme.SettingsHolder; +import com.github.authme.configme.beanmapper.BeanProperty; +import com.github.authme.configme.properties.Property; + +import java.util.HashMap; +import java.util.Map; + +/** + * Settings holder class for the commands.yml settings. + */ +public final class CommandSettingsHolder implements SettingsHolder { + + public static final Property COMMANDS = + new BeanProperty<>(CommandConfig.class, "", new CommandConfig()); + + + private CommandSettingsHolder() { + } + + @SectionComments + public static Map sectionComments() { + String[] comments = { + "This configuration file allows you to execute commands on various events.", + "%p in commands will be replaced with the player name.", + "For example, if you want to message a welcome message to a player who just registered:", + "onRegister:", + " welcome:", + " command: 'msg %p Welcome to the server!'", + " as: CONSOLE", + "", + "This will make the console execute the msg command to the player.", + "Each command under an event has a name you can choose freely (e.g. 'welcome' as above),", + "after which a mandatory 'command' field defines the command to run, ", + "and 'as' defines who will run the command (either PLAYER or CONSOLE). Longer example:", + "onLogin:", + " welcome:", + " command: 'msg %p Welcome back!'", + " # as: PLAYER # player is the default, you can leave this out if you want", + " broadcast:", + " command: 'broadcast %p has joined, welcome back!'", + " as: CONSOLE" + }; + Map commentMap = new HashMap<>(); + commentMap.put("onLogin", comments); + return commentMap; + } + +} diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/Executor.java b/src/main/java/fr/xephi/authme/settings/commandconfig/Executor.java new file mode 100644 index 000000000..c7043de2d --- /dev/null +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/Executor.java @@ -0,0 +1,14 @@ +package fr.xephi.authme.settings.commandconfig; + +/** + * The executor of the command. + */ +public enum Executor { + + /** The player of the event. */ + PLAYER, + + /** The console user. */ + CONSOLE + +} diff --git a/src/main/resources/commands.yml b/src/main/resources/commands.yml new file mode 100644 index 000000000..a7c703590 --- /dev/null +++ b/src/main/resources/commands.yml @@ -0,0 +1,24 @@ + +# This configuration file allows you to execute commands on various events. +# %p in commands will be replaced with the player name. +# For example, if you want to message a welcome message to a player who just registered: +# onRegister: +# welcome: +# command: 'msg %p Welcome to the server!' +# as: CONSOLE +# +# This will make the console execute the msg command to the player. +# Each command under an event has a name you can choose freely (e.g. 'welcome' as above), +# after which a mandatory 'command' field defines the command to run, +# and 'as' defines who will run the command (either PLAYER or CONSOLE). Longer example: +# onLogin: +# welcome: +# command: 'msg %p Welcome back!' +# # as: PLAYER # player is the default, you can leave this out if you want +# broadcast: +# command: 'broadcast %p has joined, welcome back!' +# as: CONSOLE +onLogin: + welcome: + command: 'msg %p Welcome back!' + executor: 'PLAYER' \ No newline at end of file diff --git a/src/test/java/fr/xephi/authme/datasource/FlatFileIntegrationTest.java b/src/test/java/fr/xephi/authme/datasource/FlatFileIntegrationTest.java index 7ce72f153..7e0bb1a51 100644 --- a/src/test/java/fr/xephi/authme/datasource/FlatFileIntegrationTest.java +++ b/src/test/java/fr/xephi/authme/datasource/FlatFileIntegrationTest.java @@ -3,8 +3,6 @@ package fr.xephi.authme.datasource; import com.google.common.io.Files; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; -import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.datasource.FlatFile; import org.junit.Before; import org.junit.Rule; import org.junit.Test; diff --git a/src/test/java/fr/xephi/authme/service/BukkitServiceTest.java b/src/test/java/fr/xephi/authme/service/BukkitServiceTest.java index d43f3c530..923d73e6c 100644 --- a/src/test/java/fr/xephi/authme/service/BukkitServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/BukkitServiceTest.java @@ -4,7 +4,12 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.PluginSettings; +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -17,6 +22,7 @@ import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Test for {@link BukkitService}. @@ -24,10 +30,21 @@ import static org.mockito.Mockito.mock; @RunWith(MockitoJUnitRunner.class) public class BukkitServiceTest { + private BukkitService bukkitService; + @Mock private AuthMe authMe; @Mock private Settings settings; + @Mock + private Server server; + + @Before + public void constructBukkitService() { + ReflectionTestUtils.setField(Bukkit.class, null, "server", server); + given(settings.getProperty(PluginSettings.USE_ASYNC_TASKS)).willReturn(true); + bukkitService = new BukkitService(authMe, settings); + } /** * Checks that {@link BukkitService#getOnlinePlayersIsCollection} is initialized to {@code true} on startup; @@ -35,11 +52,7 @@ public class BukkitServiceTest { */ @Test public void shouldHavePlayerListAsCollectionMethod() { - // given - given(settings.getProperty(PluginSettings.USE_ASYNC_TASKS)).willReturn(true); - BukkitService bukkitService = new BukkitService(authMe, settings); - - // when + // given / when boolean doesMethodReturnCollection = ReflectionTestUtils .getFieldValue(BukkitService.class, bukkitService, "getOnlinePlayersIsCollection"); @@ -50,8 +63,6 @@ public class BukkitServiceTest { @Test public void shouldRetrieveListOfOnlinePlayersFromReflectedMethod() { // given - given(settings.getProperty(PluginSettings.USE_ASYNC_TASKS)).willReturn(true); - BukkitService bukkitService = new BukkitService(authMe, settings); ReflectionTestUtils.setField(BukkitService.class, bukkitService, "getOnlinePlayersIsCollection", false); ReflectionTestUtils.setField(BukkitService.class, bukkitService, "getOnlinePlayers", ReflectionTestUtils.getMethod(BukkitServiceTest.class, "onlinePlayersImpl")); @@ -63,6 +74,33 @@ public class BukkitServiceTest { assertThat(players, hasSize(2)); } + @Test + public void shouldDispatchCommand() { + // given + CommandSender sender = mock(CommandSender.class); + String command = "help test abc"; + + // when + bukkitService.dispatchCommand(sender, command); + + // then + verify(server).dispatchCommand(sender, command); + } + + @Test + public void shouldDispatchConsoleCommand() { + // given + ConsoleCommandSender consoleSender = mock(ConsoleCommandSender.class); + given(server.getConsoleSender()).willReturn(consoleSender); + String command = "my command"; + + // when + bukkitService.dispatchConsoleCommand(command); + + // then + verify(server).dispatchCommand(consoleSender, command); + } + // Note: This method is used through reflections public static Player[] onlinePlayersImpl() { return new Player[]{ diff --git a/src/test/java/tools/filegeneration/GenerateCommandsYml.java b/src/test/java/tools/filegeneration/GenerateCommandsYml.java new file mode 100644 index 000000000..e6e691335 --- /dev/null +++ b/src/test/java/tools/filegeneration/GenerateCommandsYml.java @@ -0,0 +1,57 @@ +package tools.filegeneration; + +import com.github.authme.configme.SettingsManager; +import com.github.authme.configme.resource.YamlFileResource; +import com.google.common.collect.ImmutableMap; +import fr.xephi.authme.settings.commandconfig.Command; +import fr.xephi.authme.settings.commandconfig.CommandConfig; +import fr.xephi.authme.settings.commandconfig.CommandSettingsHolder; +import fr.xephi.authme.settings.commandconfig.Executor; +import tools.utils.AutoToolTask; +import tools.utils.ToolsConstants; + +import java.io.File; +import java.util.Scanner; + +/** + * Generates the commands.yml file that corresponds to the default in the code. + */ +public class GenerateCommandsYml implements AutoToolTask { + + private static final String COMMANDS_YML_FILE = ToolsConstants.MAIN_RESOURCES_ROOT + "commands.yml"; + + @Override + public void execute(Scanner scanner) { + executeDefault(); + } + + @Override + public void executeDefault() { + File file = new File(COMMANDS_YML_FILE); + + // Get default and add sample entry + CommandConfig commandConfig = CommandSettingsHolder.COMMANDS.getDefaultValue(); + commandConfig.setOnLogin( + ImmutableMap.of("welcome", newCommand("msg %p Welcome back!", Executor.PLAYER))); + + // Export the value to the file + SettingsManager settingsManager = new SettingsManager( + new YamlFileResource(file), null, CommandSettingsHolder.class); + settingsManager.setProperty(CommandSettingsHolder.COMMANDS, commandConfig); + settingsManager.save(); + + System.out.println("Updated " + COMMANDS_YML_FILE); + } + + @Override + public String getTaskName() { + return "generateCommandsYml"; + } + + private static Command newCommand(String commandLine, Executor executor) { + Command command = new Command(); + command.setCommand(commandLine); + command.setExecutor(executor); + return command; + } +}