From f06ee7c56051e465c00120b1cea6efe928768d80 Mon Sep 17 00:00:00 2001 From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com> Date: Thu, 15 Oct 2026 17:50:28 +0000 Subject: [PATCH] fix: complete authorized token and player argument routes --- .../tfmcweb/managers/LinkDiscordCommand.java | 10 +++++++- .../tfmcweb/managers/TokenCommand.java | 11 ++++----- .../managers/UnlinkDiscordCommand.java | 10 +++++++- .../tfmcweb/managers/WarningCommand.java | 13 ++++++++++- .../managers/PlayerAndAdminCommandsTest.java | 23 +++++++++++++++++++ .../tfmcweb/managers/TokenCommandTest.java | 16 +++++++++++++ 6 files changed, 73 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/tfminecraft/tfmcweb/managers/LinkDiscordCommand.java b/src/main/java/net/tfminecraft/tfmcweb/managers/LinkDiscordCommand.java index 171f5e8..0b48449 100644 --- a/src/main/java/net/tfminecraft/tfmcweb/managers/LinkDiscordCommand.java +++ b/src/main/java/net/tfminecraft/tfmcweb/managers/LinkDiscordCommand.java @@ -1,10 +1,13 @@ package net.tfminecraft.tfmcweb.managers; +import java.util.List; + import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; @@ -17,7 +20,7 @@ /** * /linkdiscord — start Discord account link (async HTTP). */ -public final class LinkDiscordCommand implements CommandExecutor { +public final class LinkDiscordCommand implements CommandExecutor, TabCompleter { private final JavaPlugin plugin; private final LinkCache linkCache; @@ -33,6 +36,11 @@ public LinkDiscordCommand( this.gateService = gateService; } + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + return List.of(); + } + // Keep the existing legacy text representation, formatting, and exact-string comparisons. @SuppressWarnings("deprecation") @Override diff --git a/src/main/java/net/tfminecraft/tfmcweb/managers/TokenCommand.java b/src/main/java/net/tfminecraft/tfmcweb/managers/TokenCommand.java index 1fd2387..f067150 100644 --- a/src/main/java/net/tfminecraft/tfmcweb/managers/TokenCommand.java +++ b/src/main/java/net/tfminecraft/tfmcweb/managers/TokenCommand.java @@ -376,20 +376,17 @@ public List onTabComplete( if (args.length == 2 && "create".equalsIgnoreCase(args[0])) { String p = args[1].toLowerCase(Locale.ROOT); List out = new ArrayList<>(); + if (((canCreate && Cache.isTokenScopeEnabled("skin")) + || (canStaff && Cache.isTokenScopeEnabled("skin_staff"))) && "skin".startsWith(p)) { + out.add("skin"); + } if (canCreate) { - if (Cache.isTokenScopeEnabled("skin") && "skin".startsWith(p)) { - out.add("skin"); - } if (Cache.isTokenScopeEnabled("drink") && "drink".startsWith(p)) { out.add("drink"); } if (Cache.isTokenScopeEnabled("profile") && "profile".startsWith(p)) { out.add("profile"); } - } else if (canStaff - && Cache.isTokenScopeEnabled("skin_staff") - && "skin".startsWith(p)) { - out.add("skin"); } return out; } diff --git a/src/main/java/net/tfminecraft/tfmcweb/managers/UnlinkDiscordCommand.java b/src/main/java/net/tfminecraft/tfmcweb/managers/UnlinkDiscordCommand.java index 4268d7d..780a23d 100644 --- a/src/main/java/net/tfminecraft/tfmcweb/managers/UnlinkDiscordCommand.java +++ b/src/main/java/net/tfminecraft/tfmcweb/managers/UnlinkDiscordCommand.java @@ -1,10 +1,13 @@ package net.tfminecraft.tfmcweb.managers; +import java.util.List; + import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; @@ -16,7 +19,7 @@ /** * /unlinkdiscord — remove Discord link for this Minecraft UUID (async HTTP). */ -public final class UnlinkDiscordCommand implements CommandExecutor { +public final class UnlinkDiscordCommand implements CommandExecutor, TabCompleter { private final JavaPlugin plugin; private final LinkCache linkCache; @@ -32,6 +35,11 @@ public UnlinkDiscordCommand( this.gateService = gateService; } + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + return List.of(); + } + // Keep the existing legacy text representation, formatting, and exact-string comparisons. @SuppressWarnings("deprecation") @Override diff --git a/src/main/java/net/tfminecraft/tfmcweb/managers/WarningCommand.java b/src/main/java/net/tfminecraft/tfmcweb/managers/WarningCommand.java index f151316..b71d132 100644 --- a/src/main/java/net/tfminecraft/tfmcweb/managers/WarningCommand.java +++ b/src/main/java/net/tfminecraft/tfmcweb/managers/WarningCommand.java @@ -1,6 +1,8 @@ package net.tfminecraft.tfmcweb.managers; import java.util.Arrays; +import java.util.List; +import java.util.Locale; import java.util.UUID; import java.util.stream.Collectors; @@ -10,6 +12,7 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; @@ -21,7 +24,7 @@ /** * /warning <player> <reason…> — in-game chat + web store + Discord DM via bot. */ -public final class WarningCommand implements CommandExecutor { +public final class WarningCommand implements CommandExecutor, TabCompleter { private final JavaPlugin plugin; private final LinkCache linkCache; @@ -31,6 +34,14 @@ public WarningCommand(JavaPlugin plugin, LinkCache linkCache) { this.linkCache = linkCache; } + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + if (args.length != 1 || !sender.hasPermission("tfmcweb.warning")) return List.of(); + String prefix = args[0].toLowerCase(Locale.ROOT); + return Bukkit.getOnlinePlayers().stream().map(Player::getName) + .filter(name -> name.toLowerCase(Locale.ROOT).startsWith(prefix)).toList(); + } + // Keep the existing legacy text representation, formatting, and exact-string comparisons. @SuppressWarnings("deprecation") @Override diff --git a/src/test/java/net/tfminecraft/tfmcweb/managers/PlayerAndAdminCommandsTest.java b/src/test/java/net/tfminecraft/tfmcweb/managers/PlayerAndAdminCommandsTest.java index 9cb111f..b135c95 100644 --- a/src/test/java/net/tfminecraft/tfmcweb/managers/PlayerAndAdminCommandsTest.java +++ b/src/test/java/net/tfminecraft/tfmcweb/managers/PlayerAndAdminCommandsTest.java @@ -19,6 +19,29 @@ class PlayerAndAdminCommandsTest { @BeforeEach void setup() throws Exception {f=new CommandFixture();} @AfterEach void cleanup() throws Exception {f.close();} void run(CommandExecutor command,org.bukkit.command.CommandSender sender,String...args){assertTrue(command.onCommand(sender,null,"cmd",args));} + @Test void operandFreeDiscordCommandsNeverSuggestPlayerNames() { + var link=new LinkDiscordCommand(f.plugin,f.cache,f.gate); + var unlink=new UnlinkDiscordCommand(f.plugin,f.cache,f.gate); + for(var sender:List.of(f.player,f.console)) { + for(String[] args:List.of(new String[0],new String[]{""},new String[]{"A"},new String[]{"Ada",""})) { + assertTrue(link.onTabComplete(sender,null,"linkdiscord",args).isEmpty()); + assertTrue(unlink.onTabComplete(sender,null,"unlinkdiscord",args).isEmpty()); + } + } + } + + @Test void warningOnlyCompletesTheAuthorizedTargetArgument() { + var warning=new WarningCommand(f.plugin,f.cache); + assertTrue(warning.onTabComplete(f.console,null,"warning",new String[]{""}).isEmpty()); + f.permissions(f.console,true); + assertEquals(List.of("Ada"),warning.onTabComplete(f.console,null,"warning",new String[]{""})); + assertEquals(List.of("Ada"),warning.onTabComplete(f.console,null,"warning",new String[]{"aD"})); + assertTrue(warning.onTabComplete(f.console,null,"warning",new String[]{"zzz"}).isEmpty()); + for(String[] args:List.of(new String[0],new String[]{"Ada",""},new String[]{"Ada","reason",""})) { + assertTrue(warning.onTabComplete(f.console,null,"warning",args).isEmpty()); + } + } + @Test void linkShowsCodesFailuresOrExistingIdentityAndIgnoresOfflinePlayer() { var cmd=new LinkDiscordCommand(f.plugin,f.cache,f.gate);run(cmd,f.console);assertTrue(f.contains("Players only")); for(CodeResult result:List.of(CodeResult.fail(null),CodeResult.fail("backend"),CodeResult.alreadyLinked(" AdaDiscord "),CodeResult.alreadyLinked(" "),CodeResult.success("ABC",Instant.now().plusSeconds(4000).toString()),CodeResult.success("ABC",null))){ diff --git a/src/test/java/net/tfminecraft/tfmcweb/managers/TokenCommandTest.java b/src/test/java/net/tfminecraft/tfmcweb/managers/TokenCommandTest.java index 5b32409..dba7131 100644 --- a/src/test/java/net/tfminecraft/tfmcweb/managers/TokenCommandTest.java +++ b/src/test/java/net/tfminecraft/tfmcweb/managers/TokenCommandTest.java @@ -66,6 +66,22 @@ class TokenCommandTest { when(f.player.isOnline()).thenReturn(false);f.messages.clear();run(f.player,"resetcooldowns","Ada");assertTrue(f.messages.isEmpty()); f.api.when(()->ProvinceSystemClient.resetCosmeticMintCooldowns(f.id.toString(),f.id.toString())).thenReturn(SimpleResult.success());run(f.player,"resetcooldowns","Ada");assertTrue(f.messages.isEmpty()); } + @Test void ordinaryCreatePermissionDoesNotHideTheEnabledStaffSkinRoute() { + f.permissions(f.player,true); + Cache.tokenEnabledScopes=List.of("skin_staff"); + when(f.player.hasPermission("tfmcweb.token.create")).thenReturn(false); + assertEquals(List.of("skin"),command.onTabComplete(f.player,null,"token",new String[]{"create",""})); + when(f.player.hasPermission("tfmcweb.token.create")).thenReturn(true); + assertEquals(List.of("skin"),command.onTabComplete(f.player,null,"token",new String[]{"create",""})); + assertEquals(List.of("skin"),command.onTabComplete(f.player,null,"token",new String[]{"CREATE","S"})); + assertEquals(List.of("staff"),command.onTabComplete(f.player,null,"token",new String[]{"create","skin",""})); + when(f.player.hasPermission("tfmcweb.token.create.staff")).thenReturn(false); + assertTrue(command.onTabComplete(f.player,null,"token",new String[]{"create",""}).isEmpty()); + Cache.tokenEnabledScopes=List.of("skin","skin_staff"); + when(f.player.hasPermission("tfmcweb.token.create.staff")).thenReturn(true); + assertEquals(List.of("skin"),command.onTabComplete(f.player,null,"token",new String[]{"create",""})); + } + @Test void completionsRespectPermissionsEnabledScopesAndPrefix() { assertTrue(command.onTabComplete(f.player,null,"token",new String[]{""}).isEmpty());f.permissions(f.player,true); assertEquals(List.of("create","resetcooldowns"),command.onTabComplete(f.player,null,"token",new String[]{""}));