arguments) {
+ String option = arguments.size() > 1 ? arguments.get(1) : null;
+ bukkitService.runTaskOptionallyAsync(
+ () -> executeCommand(sender, arguments.get(0), option));
+ }
+
+ private void executeCommand(CommandSender sender, String name, String option) {
+ if ("force".equals(option) || !dataSource.isAuthAvailable(name)) {
+ OfflinePlayer offlinePlayer = bukkitService.getOfflinePlayer(name);
+ purgeExecutor.executePurge(singletonList(offlinePlayer), singletonList(name.toLowerCase()));
+ sender.sendMessage("Purged data for player " + name);
+ } else {
+ sender.sendMessage("This player is still registered! Are you sure you want to proceed? "
+ + "Use '/authme purgeplayer " + name + " force' to run the command anyway");
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/permission/AdminPermission.java b/src/main/java/fr/xephi/authme/permission/AdminPermission.java
index 4f0957a0a..14baf3ae9 100644
--- a/src/main/java/fr/xephi/authme/permission/AdminPermission.java
+++ b/src/main/java/fr/xephi/authme/permission/AdminPermission.java
@@ -85,6 +85,11 @@ public enum AdminPermission implements PermissionNode {
*/
PURGE_BANNED_PLAYERS("authme.admin.purgebannedplayers"),
+ /**
+ * Administrator command to purge a given player.
+ */
+ PURGE_PLAYER("authme.admin.purgeplayer"),
+
/**
* Administrator command to toggle the AntiBot protection status.
*/
diff --git a/src/main/java/fr/xephi/authme/service/BukkitService.java b/src/main/java/fr/xephi/authme/service/BukkitService.java
index 45ea4b4b8..f5b03348a 100644
--- a/src/main/java/fr/xephi/authme/service/BukkitService.java
+++ b/src/main/java/fr/xephi/authme/service/BukkitService.java
@@ -198,6 +198,23 @@ public class BukkitService implements SettingsDependent {
return authMe.getServer().getPlayerExact(name);
}
+ /**
+ * Gets the player by the given name, regardless if they are offline or
+ * online.
+ *
+ * This method may involve a blocking web request to get the UUID for the
+ * given name.
+ *
+ * This will return an object even if the player does not exist. To this
+ * method, all players will exist.
+ *
+ * @param name the name the player to retrieve
+ * @return an offline player
+ */
+ public OfflinePlayer getOfflinePlayer(String name) {
+ return authMe.getServer().getOfflinePlayer(name);
+ }
+
/**
* Gets a set containing all banned players.
*
diff --git a/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java b/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java
index 2c62454c8..0064fd40f 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java
@@ -24,7 +24,7 @@ public final class PurgeSettings implements SettingsHolder {
public static final Property REMOVE_ESSENTIALS_FILES =
newProperty("Purge.removeEssentialsFile", false);
- @Comment("World where are players.dat stores")
+ @Comment("World in which the players.dat are stored")
public static final Property DEFAULT_WORLD =
newProperty("Purge.defaultWorld", "world");
diff --git a/src/main/java/fr/xephi/authme/task/purge/PurgeExecutor.java b/src/main/java/fr/xephi/authme/task/purge/PurgeExecutor.java
index 0fddecd57..6e6e5836f 100644
--- a/src/main/java/fr/xephi/authme/task/purge/PurgeExecutor.java
+++ b/src/main/java/fr/xephi/authme/task/purge/PurgeExecutor.java
@@ -21,7 +21,7 @@ import static fr.xephi.authme.util.FileUtils.makePath;
/**
* Executes the purge operations.
*/
-class PurgeExecutor {
+public class PurgeExecutor {
@Inject
private Settings settings;
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 8629a4535..9822005f1 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -17,7 +17,7 @@ softdepend:
commands:
authme:
description: AuthMe op commands
- usage: /authme register|unregister|forcelogin|password|lastlogin|accounts|email|setemail|getip|spawn|setspawn|firstspawn|setfirstspawn|purge|backup|resetpos|purgebannedplayers|switchantibot|reload|version|converter|messages|debug
+ usage: /authme register|unregister|forcelogin|password|lastlogin|accounts|email|setemail|getip|spawn|setspawn|firstspawn|setfirstspawn|purge|purgeplayer|backup|resetpos|purgebannedplayers|switchantibot|reload|version|converter|messages|debug
login:
description: Login command
usage: /login
@@ -67,6 +67,7 @@ permissions:
authme.admin.purge: true
authme.admin.purgebannedplayers: true
authme.admin.purgelastpos: true
+ authme.admin.purgeplayer: true
authme.admin.register: true
authme.admin.reload: true
authme.admin.seeotheraccounts: true
@@ -118,6 +119,9 @@ permissions:
authme.admin.purgelastpos:
description: Administrator command to purge the last position of a user.
default: op
+ authme.admin.purgeplayer:
+ description: Administrator command to purge a given player.
+ default: op
authme.admin.register:
description: Administrator command to register a new user.
default: op
diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/PurgePlayerCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/PurgePlayerCommandTest.java
new file mode 100644
index 000000000..c37317cf5
--- /dev/null
+++ b/src/test/java/fr/xephi/authme/command/executable/authme/PurgePlayerCommandTest.java
@@ -0,0 +1,91 @@
+package fr.xephi.authme.command.executable.authme;
+
+import fr.xephi.authme.datasource.DataSource;
+import fr.xephi.authme.service.BukkitService;
+import fr.xephi.authme.task.purge.PurgeExecutor;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.command.CommandSender;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import static fr.xephi.authme.TestHelper.runOptionallyAsyncTask;
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.hamcrest.Matchers.containsString;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+
+/**
+ * Test for {@link PurgePlayerCommand}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class PurgePlayerCommandTest {
+
+ @InjectMocks
+ private PurgePlayerCommand command;
+
+ @Mock
+ private BukkitService bukkitService;
+
+ @Mock
+ private PurgeExecutor purgeExecutor;
+
+ @Mock
+ private DataSource dataSource;
+
+ @Test
+ public void shouldNotExecutePurgeForRegisteredPlayer() {
+ // given
+ String name = "Bobby";
+ given(dataSource.isAuthAvailable(name)).willReturn(true);
+ CommandSender sender = mock(CommandSender.class);
+
+ // when
+ command.executeCommand(sender, singletonList(name));
+ runOptionallyAsyncTask(bukkitService);
+
+ // then
+ verify(sender).sendMessage(argThat(containsString("This player is still registered")));
+ verifyZeroInteractions(purgeExecutor);
+ }
+
+ @Test
+ public void shouldExecutePurge() {
+ // given
+ String name = "Frank";
+ given(dataSource.isAuthAvailable(name)).willReturn(false);
+ OfflinePlayer player = mock(OfflinePlayer.class);
+ given(bukkitService.getOfflinePlayer(name)).willReturn(player);
+ CommandSender sender = mock(CommandSender.class);
+
+ // when
+ command.executeCommand(sender, singletonList(name));
+ runOptionallyAsyncTask(bukkitService);
+
+ // then
+ verify(dataSource).isAuthAvailable(name);
+ verify(purgeExecutor).executePurge(singletonList(player), singletonList(name.toLowerCase()));
+ }
+
+ @Test
+ public void shouldExecutePurgeOfRegisteredPlayer() {
+ // given
+ String name = "GhiJKlmn7";
+ OfflinePlayer player = mock(OfflinePlayer.class);
+ given(bukkitService.getOfflinePlayer(name)).willReturn(player);
+ CommandSender sender = mock(CommandSender.class);
+
+ // when
+ command.executeCommand(sender, asList(name, "force"));
+ runOptionallyAsyncTask(bukkitService);
+
+ // then
+ verify(purgeExecutor).executePurge(singletonList(player), singletonList(name.toLowerCase()));
+ }
+}