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