Compare commits

..

31 Commits

Author SHA1 Message Date
YouHaveTrouble b7d7646641 fix subcommand blocker
subcommandblocker broke when command included part of the subcommand, so for ex. /help he would remove "he" from the command and it would return as "/lp".
2021-07-02 16:14:20 +02:00
YouHaveTrouble 697a5e5720 priority shift 2021-06-02 21:22:12 +02:00
YouHaveTrouble 598756fec0 version bump 2021-06-02 16:00:36 +02:00
YouHaveTrouble 95ea1d8319 this probably needs to be handled anyway
ignoreCancelled seems to introduce the issue of completions not being filtered in some cases, and it shouldn't cause problems by running anyway
2021-06-02 15:52:13 +02:00
YouHaveTrouble 9da87b2769 fixed missing ignoreCancelled 2021-05-22 15:53:11 +02:00
YouHaveTrouble 12ed028460 guess what, legacy issues again. 2021-05-16 19:17:45 +02:00
YouHaveTrouble 66e6bff28e no idea how they managed to dispatch player command without / but ok 2021-05-07 00:43:54 +02:00
YouHaveTrouble 332e98f4a0 add fancy server counters 2021-03-30 16:34:34 +02:00
YouHaveTrouble 2085002c59 fix preProcessEvent no being registered in legacy mode 2021-03-26 15:36:05 +01:00
YouHaveTrouble 38d9d5115e add github actions 2021-03-15 15:08:23 +01:00
YouHaveTrouble 2fda6ac23e Merge remote-tracking branch 'origin/master' 2021-03-15 15:06:22 +01:00
YouHaveTrouble 6a4a27e7b3 fix legacy mode event registration 2021-03-15 15:01:17 +01:00
YouHaveTrouble 42bfaa2efb version bump and update depends 2021-03-15 15:00:46 +01:00
YouHaveTrouble adbf02a4c3 Create FUNDING.yml 2021-02-21 14:12:58 +01:00
YouHaveTrouble 94d5afdb47 version bump 2021-02-02 22:37:04 +01:00
YouHaveTrouble c943455392 remove all copies of completion 2021-02-02 22:29:49 +01:00
YouHaveTrouble e823aa91aa added optional usage of protocollib for command filter 2021-01-07 19:02:38 +01:00
YouHaveTrouble eb0f53f19d version bump 2020-12-24 20:11:19 +01:00
YouHaveTrouble 103a793a63 basic velocity support 2020-12-16 05:32:29 +01:00
YouHaveTrouble 2a93f8c636 default commands no longer check permission 2020-12-15 22:10:01 +01:00
YouHaveTrouble c135815ce3 improved code readability 2020-12-15 21:05:56 +01:00
YouHaveTrouble 9254c7fc7b tab completion now fails gracefully if command is not registered (cannot block completions that are not registered) 2020-12-06 14:47:56 +01:00
YouHaveTrouble b9db85278c Merge remote-tracking branch 'origin/master' 2020-11-25 19:23:34 +01:00
YouHaveTrouble 87e59e3f2b version bump 2020-11-25 19:23:11 +01:00
YouHaveTrouble 292cac7415 prevented saving duplicate command entries 2020-11-25 19:22:51 +01:00
YouHaveTrouble 7629b580ae Update issue templates 2020-11-18 13:14:04 +01:00
YouHaveTrouble 7101d3d713 Update readme.md 2020-11-18 12:25:19 +01:00
YouHaveTrouble 6487255d04 version bump 2020-11-17 17:12:59 +01:00
YouHaveTrouble c57a58eb41 made config more clear 2020-11-17 17:05:48 +01:00
YouHaveTrouble 8fac940359 added spigot subcommand blocking system 2020-11-17 03:06:19 +01:00
YouHaveTrouble fe0b8c416d update commands on add/remove 2020-11-02 15:42:29 +01:00
27 changed files with 624 additions and 82 deletions
+12
View File
@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: YouHaveTrouble
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+28
View File
@@ -0,0 +1,28 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: YouHaveTrouble
---
**Describe the bug**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: YouHaveTrouble
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+10
View File
@@ -0,0 +1,10 @@
---
name: Other issue
about: For other issues
title: ''
labels: ''
assignees: YouHaveTrouble
---
+36
View File
@@ -0,0 +1,36 @@
name: Build CommandWhitelist Jar
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Git repo
uses: actions/checkout@v1
- name: Restore Maven cache
uses: actions/cache@v1
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Maven
run: mvn package --file pom.xml
- name: Copy artifacts
uses: actions/upload-artifact@master
with:
name: CommandWhitelist
path: target/CommandWhitelist*.jar
+13 -3
View File
@@ -6,7 +6,7 @@
<groupId>eu.endermite</groupId> <groupId>eu.endermite</groupId>
<artifactId>CommandWhitelist</artifactId> <artifactId>CommandWhitelist</artifactId>
<version>1.4.0</version> <version>1.7.8</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>CommandWhitelist</name> <name>CommandWhitelist</name>
@@ -74,13 +74,17 @@
<id>papermc</id> <id>papermc</id>
<url>https://papermc.io/repo/repository/maven-public/</url> <url>https://papermc.io/repo/repository/maven-public/</url>
</repository> </repository>
<repository>
<id>velocity</id>
<url>https://repo.velocitypowered.com/snapshots/</url>
</repository>
</repositories> </repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.16.3-R0.1-SNAPSHOT</version> <version>1.16.5-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -95,10 +99,16 @@
<version>1.16-R0.4-SNAPSHOT</version> <version>1.16-R0.4-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>com.velocitypowered</groupId>
<artifactId>velocity-api</artifactId>
<version>1.1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>com.comphenix.protocol</groupId> <groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId> <artifactId>ProtocolLib</artifactId>
<version>4.5.0</version> <version>4.6.0</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
+4
View File
@@ -1,6 +1,9 @@
Command Whitelist is a plugin that allows you to control Command Whitelist is a plugin that allows you to control
precisely what commands players can see and use. precisely what commands players can see and use.
<img src="https://img.shields.io/bstats/servers/8705?label=Spigot%20servers%20using%20CommandWhitelist&style=for-the-badge">
<img src="https://img.shields.io/bstats/servers/8704?label=Proxy%20servers%20using%20CommandWhitelist&style=for-the-badge">
<h3>Plugin Features</h3> <h3>Plugin Features</h3>
<ul> <ul>
@@ -8,6 +11,7 @@ precisely what commands players can see and use.
<li>Overwrites default "no such command" message with your branding <li>Overwrites default "no such command" message with your branding
<li>Blocks tab completion on spigot and bungeecord*</li> <li>Blocks tab completion on spigot and bungeecord*</li>
<li>Blocks command execution on spigot and bungeecord</li> <li>Blocks command execution on spigot and bungeecord</li>
<li>Blocks completion and execution of specific subcommands (spigot only)</li>
</ul> </ul>
\*This only works on Waterfall and its forks \*This only works on Waterfall and its forks
@@ -2,34 +2,72 @@ package eu.endermite.commandwhitelist.api;
import eu.endermite.commandwhitelist.bungee.CommandWhitelistBungee; import eu.endermite.commandwhitelist.bungee.CommandWhitelistBungee;
import eu.endermite.commandwhitelist.spigot.CommandWhitelist; import eu.endermite.commandwhitelist.spigot.CommandWhitelist;
import net.md_5.bungee.api.connection.ProxiedPlayer; import eu.endermite.commandwhitelist.velocity.CommandWhitelistVelocity;
import org.bukkit.entity.Player;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class CommandsList { public class CommandsList {
public static List<String> getCommands(Player player) { public static List<String> getCommands(org.bukkit.entity.Player player) {
List<String> commandList = new ArrayList<>(); List<String> commandList = new ArrayList<>();
for (Map.Entry<String, List<String>> s : CommandWhitelist.getConfigCache().getPermList().entrySet()) { for (Map.Entry<String, List<String>> s : CommandWhitelist.getConfigCache().getPermList().entrySet()) {
if (player.hasPermission("commandwhitelist.commands." + s.getKey())) { if (s.getKey().equalsIgnoreCase("default"))
commandList.addAll(s.getValue());
else if (player.hasPermission("commandwhitelist.commands." + s.getKey()))
commandList.addAll(s.getValue()); commandList.addAll(s.getValue());
}
} }
return commandList; return commandList;
} }
public static List<String> getCommands(ProxiedPlayer player) { public static List<String> getCommands(net.md_5.bungee.api.connection.ProxiedPlayer player) {
List<String> commandList = new ArrayList<>(); List<String> commandList = new ArrayList<>();
for (Map.Entry<String, List<String>> s : CommandWhitelistBungee.getConfigCache().getPermList().entrySet()) { for (Map.Entry<String, List<String>> s : CommandWhitelistBungee.getConfigCache().getPermList().entrySet()) {
if (player.hasPermission("commandwhitelist.commands." + s.getKey())) { if (s.getKey().equalsIgnoreCase("default"))
commandList.addAll(s.getValue());
else if (player.hasPermission("commandwhitelist.commands." + s.getKey()))
commandList.addAll(s.getValue()); commandList.addAll(s.getValue());
}
} }
return commandList; return commandList;
} }
public static List<String> getCommands(com.velocitypowered.api.proxy.Player player) {
List<String> commandList = new ArrayList<>();
for (Map.Entry<String, List<String>> s : CommandWhitelistVelocity.getConfigCache().getPermList().entrySet()) {
if (s.getKey().equalsIgnoreCase("default"))
commandList.addAll(s.getValue());
else if (player.hasPermission("commandwhitelist.commands." + s.getKey()))
commandList.addAll(s.getValue());
}
return commandList;
}
public static List<String> getSuggestions(org.bukkit.entity.Player player) {
List<String> suggestionList = new ArrayList<>();
for (Map.Entry<String, List<String>> s : CommandWhitelist.getConfigCache().getPermSubList().entrySet()) {
if (player.hasPermission("commandwhitelist.subcommands." + s.getKey()))
continue;
suggestionList.addAll(s.getValue());
}
return suggestionList;
}
public static String getLastArgument(String cmd) {
String[] parts = cmd.split(" ");
if (parts.length <= 1)
return "";
String last = "";
for (String part : parts) {
last = part;
}
return last;
}
public static String getCommandLabel(String cmd) {
String[] parts = cmd.split(" ");
if (parts[0].startsWith("/"))
parts[0] = parts[0].substring(1);
return parts[0];
}
} }
@@ -24,7 +24,6 @@ public final class CommandWhitelistBungee extends Plugin {
@Override @Override
public void onEnable() { public void onEnable() {
plugin = this; plugin = this;
getLogger().info("Running on "+ ChatColor.DARK_AQUA+getProxy().getName()); getLogger().info("Running on "+ ChatColor.DARK_AQUA+getProxy().getName());
loadConfig(); loadConfig();
@@ -40,18 +39,15 @@ public final class CommandWhitelistBungee extends Plugin {
int pluginId = 8704; int pluginId = 8704;
new BungeeMetrics(this, pluginId); new BungeeMetrics(this, pluginId);
} }
public static CommandWhitelistBungee getPlugin() { public static CommandWhitelistBungee getPlugin() {
return plugin; return plugin;
} }
public static BungeeConfigCache getConfigCache() { public static BungeeConfigCache getConfigCache() {
return configCache; return configCache;
} }
public void loadConfig() { public void loadConfig() {
try { try {
if (!getDataFolder().exists()) { if (!getDataFolder().exists()) {
@@ -81,5 +77,4 @@ public final class CommandWhitelistBungee extends Plugin {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', CommandWhitelistBungee.getConfigCache().getPrefix() + CommandWhitelistBungee.getConfigCache().getConfigReloaded())); sender.sendMessage(ChatColor.translateAlternateColorCodes('&', CommandWhitelistBungee.getConfigCache().getPrefix() + CommandWhitelistBungee.getConfigCache().getConfigReloaded()));
}); });
} }
} }
@@ -9,7 +9,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class BungeeMainCommand extends Command implements TabExecutor { public class BungeeMainCommand extends Command implements TabExecutor {
public BungeeMainCommand(String name) { public BungeeMainCommand(String name) {
@@ -14,28 +14,20 @@ public class BungeeChatEventListener implements Listener {
@EventHandler @EventHandler
public void onChatEvent(net.md_5.bungee.api.event.ChatEvent event) { public void onChatEvent(net.md_5.bungee.api.event.ChatEvent event) {
if (event.isCancelled()) if (event.isCancelled())
return; return;
if (!(event.getSender() instanceof ProxiedPlayer)) if (!(event.getSender() instanceof ProxiedPlayer))
return; return;
if (!event.isProxyCommand()) if (!event.isProxyCommand())
return; return;
ProxiedPlayer player = (ProxiedPlayer) event.getSender(); ProxiedPlayer player = (ProxiedPlayer) event.getSender();
if (player.hasPermission("commandwhitelist.bypass"))
if (player.hasPermission("commandwhitelist.bypass")) {
return; return;
}
String command = event.getMessage().toLowerCase(); String command = event.getMessage().toLowerCase();
boolean found = false; boolean found = false;
for (Map.Entry<String, List<String>> s : CommandWhitelistBungee.getConfigCache().getPermList().entrySet()) { for (Map.Entry<String, List<String>> s : CommandWhitelistBungee.getConfigCache().getPermList().entrySet()) {
if (!player.hasPermission("commandwhitelist.commands." + s.getKey())) if (!player.hasPermission("commandwhitelist.commands." + s.getKey()))
continue; continue;
for (String comm : s.getValue()) { for (String comm : s.getValue()) {
comm = comm.toLowerCase(); comm = comm.toLowerCase();
if (command.equalsIgnoreCase("/" + comm)) { if (command.equalsIgnoreCase("/" + comm)) {
@@ -13,25 +13,17 @@ public class WaterfallDefineCommandsListener implements Listener {
@EventHandler @EventHandler
public void onProxyDefineCommandsEvent(io.github.waterfallmc.waterfall.event.ProxyDefineCommandsEvent event) { public void onProxyDefineCommandsEvent(io.github.waterfallmc.waterfall.event.ProxyDefineCommandsEvent event) {
if (event.getReceiver() instanceof ProxiedPlayer) { if (event.getReceiver() instanceof ProxiedPlayer) {
ProxiedPlayer player = (ProxiedPlayer) event.getReceiver(); ProxiedPlayer player = (ProxiedPlayer) event.getReceiver();
if (player.hasPermission("commandwhitelist.bypass"))
if (player.hasPermission("commandwhitelist.bypass")) {
return; return;
}
HashMap<String, Command> commandHashMap = new HashMap<>(); HashMap<String, Command> commandHashMap = new HashMap<>();
CommandsList.getCommands(player).forEach(cmdName -> CommandsList.getCommands(player).forEach(cmdName ->
CommandWhitelistBungee.getPlugin().getProxy().getPluginManager().getCommands() CommandWhitelistBungee.getPlugin().getProxy().getPluginManager().getCommands()
.stream() .stream()
.filter(commandEntry -> cmdName.equalsIgnoreCase(commandEntry.getValue().getName())) .filter(commandEntry -> cmdName.equalsIgnoreCase(commandEntry.getValue().getName()))
.forEach(commandEntry -> commandHashMap.put(commandEntry.getKey(), commandEntry.getValue()))); .forEach(commandEntry -> commandHashMap.put(commandEntry.getKey(), commandEntry.getValue())));
event.getCommands().values().removeIf((cmd) -> !commandHashMap.containsValue(cmd)); event.getCommands().values().removeIf((cmd) -> !commandHashMap.containsValue(cmd));
} }
} }
} }
@@ -2,14 +2,13 @@ package eu.endermite.commandwhitelist.spigot;
import eu.endermite.commandwhitelist.spigot.command.MainCommand; import eu.endermite.commandwhitelist.spigot.command.MainCommand;
import eu.endermite.commandwhitelist.spigot.config.ConfigCache; import eu.endermite.commandwhitelist.spigot.config.ConfigCache;
import eu.endermite.commandwhitelist.spigot.listeners.LegacyPlayerTabChatCompleteListener; import eu.endermite.commandwhitelist.spigot.listeners.*;
import eu.endermite.commandwhitelist.spigot.listeners.PlayerCommandPreProcessListener;
import eu.endermite.commandwhitelist.spigot.listeners.PlayerCommandSendListener;
import eu.endermite.commandwhitelist.spigot.metrics.BukkitMetrics; import eu.endermite.commandwhitelist.spigot.metrics.BukkitMetrics;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
public class CommandWhitelist extends JavaPlugin { public class CommandWhitelist extends JavaPlugin {
@@ -27,12 +26,21 @@ public class CommandWhitelist extends JavaPlugin {
reloadPluginConfig(); reloadPluginConfig();
Plugin protocollib = getServer().getPluginManager().getPlugin("ProtocolLib");
getServer().getPluginManager().registerEvents(new PlayerCommandPreProcessListener(), this); getServer().getPluginManager().registerEvents(new PlayerCommandPreProcessListener(), this);
if (!isLegacy) { if (!isLegacy) {
if (!getConfigCache().isUseProtocolLib() || protocollib == null || !protocollib.isEnabled()) {
getServer().getPluginManager().registerEvents(new PlayerCommandSendListener(), this); getServer().getPluginManager().registerEvents(new PlayerCommandSendListener(), this);
} else {
PacketCommandSendListener.protocol(this);
getLogger().info(ChatColor.AQUA+"Using ProtocolLib for command filter!");
}
getServer().getPluginManager().registerEvents(new TabCompleteBlockerListener(), this);
} else { } else {
getLogger().info(ChatColor.AQUA+"Running in legacy mode..."); getLogger().info(ChatColor.AQUA+"Running in legacy mode...");
if (getServer().getPluginManager().getPlugin("ProtocolLib") != null) { if (protocollib != null) {
LegacyPlayerTabChatCompleteListener.protocol(this); LegacyPlayerTabChatCompleteListener.protocol(this);
} else { } else {
getLogger().info(ChatColor.YELLOW+"ProtocolLib is required for tab completion blocking!"); getLogger().info(ChatColor.YELLOW+"ProtocolLib is required for tab completion blocking!");
@@ -58,7 +58,6 @@ public class MainCommand implements TabExecutor {
} else { } else {
sender.sendMessage("/cw remove <group> <command>"); sender.sendMessage("/cw remove <group> <command>");
} }
} else { } else {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', CommandWhitelist.getConfigCache().getPrefix() + CommandWhitelist.getConfigCache().getNoSubCommand())); sender.sendMessage(ChatColor.translateAlternateColorCodes('&', CommandWhitelist.getConfigCache().getPrefix() + CommandWhitelist.getConfigCache().getNoSubCommand()));
} }
@@ -69,6 +68,7 @@ public class MainCommand implements TabExecutor {
} }
if (sender.hasPermission("commandwhitelist.admin")) { if (sender.hasPermission("commandwhitelist.admin")) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&9/cw add <group> <command> &b- Add command to group")); sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&9/cw add <group> <command> &b- Add command to group"));
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', "&9/cw remove <group> <command> &b- Remove command from a group"));
} }
} }
return true; return true;
@@ -1,7 +1,9 @@
package eu.endermite.commandwhitelist.spigot.config; package eu.endermite.commandwhitelist.spigot.config;
import eu.endermite.commandwhitelist.spigot.CommandWhitelist; import eu.endermite.commandwhitelist.spigot.CommandWhitelist;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -11,38 +13,57 @@ public class ConfigCache {
private FileConfiguration config; private FileConfiguration config;
private HashMap<String, List<String>> permList = new HashMap<>(); private HashMap<String, List<String>> permList = new HashMap<>();
private HashMap<String, List<String>> permSubList = new HashMap<>();
private final String prefix, commandDenied, noPermission, noSubCommand, configReloaded, whitelistedCommand, private final String prefix, commandDenied, noPermission, noSubCommand, configReloaded, whitelistedCommand,
removedWhitelistedCommand, noSuchGroup; removedWhitelistedCommand, noSuchGroup, subCommandDenied;
private final List<String> commandDeniedList; private final List<String> commandDeniedList;
private boolean useProtocolLib;
public ConfigCache(FileConfiguration config) { public ConfigCache(FileConfiguration config) {
this.config = config; this.config = config;
prefix = config.getString("messages.prefix"); prefix = config.getString("messages.prefix", "");
commandDenied = config.getString("messages.command-denied", null); commandDenied = config.getString("messages.command-denied", null);
commandDeniedList = config.getStringList("messages.command-denied"); commandDeniedList = config.getStringList("messages.command-denied");
noPermission = config.getString("messages.no-permission"); subCommandDenied = config.getString("messages.subcommand-denied", "You cannot use this subcommand");
noSubCommand = config.getString("messages.no-such-subcommand"); noPermission = config.getString("messages.no-permission", "&cYou don't have permission to do this.");
configReloaded = config.getString("messages.config-reloaded"); noSubCommand = config.getString("messages.no-such-subcommand", "&cNo subcommand by that name.");
configReloaded = config.getString("messages.config-reloaded", "&eConfiguration reloaded.");
whitelistedCommand = config.getString("messages.added-to-whitelist", "&eWhitelisted command &6%s &efor permission &6%s"); whitelistedCommand = config.getString("messages.added-to-whitelist", "&eWhitelisted command &6%s &efor permission &6%s");
removedWhitelistedCommand = config.getString("messages.removed-from-whitelist", "&eRemoved command &6%s &efrom permission &6%s"); removedWhitelistedCommand = config.getString("messages.removed-from-whitelist", "&eRemoved command &6%s &efrom permission &6%s");
noSuchGroup = config.getString("messages.group-doesnt-exist", "&cGroup %s doesn't exist"); noSuchGroup = config.getString("messages.group-doesnt-exist", "&cGroup %s doesn't exist");
useProtocolLib = config.getBoolean("use-protocollib-to-detect-commands", false);
Set<String> perms = config.getConfigurationSection("commands").getKeys(false); Set<String> perms = config.getConfigurationSection("commands").getKeys(false);
for (String s : perms) { for (String s : perms) {
this.permList.put(s, config.getStringList("commands."+s)); this.permList.put(s, config.getStringList("commands."+s));
} }
Set<String> subperms = config.getConfigurationSection("tabcompletions").getKeys(false);
for (String s : subperms) {
this.permSubList.put(s, config.getStringList("tabcompletions."+s));
}
} }
public HashMap<String, List<String>> getPermList() { public HashMap<String, List<String>> getPermList() {
return permList; return permList;
} }
public HashMap<String, List<String>> getPermSubList() {
return permSubList;
}
public boolean addCommand(String command, String group) { public boolean addCommand(String command, String group) {
try { try {
if (this.permList.get(group).contains(command)) {
return true;
}
this.permList.get(group).add(command); this.permList.get(group).add(command);
this.config.set("commands."+group, permList.get(group)); this.config.set("commands."+group, permList.get(group));
config.save(CommandWhitelist.getPlugin().getDataFolder()+"/config.yml"); config.save(CommandWhitelist.getPlugin().getDataFolder()+"/config.yml");
for (Player player : Bukkit.getOnlinePlayers()) {
player.updateCommands();
}
return true; return true;
} catch (Exception e) { } catch (Exception e) {
return false; return false;
@@ -53,6 +74,9 @@ public class ConfigCache {
this.permList.get(group).remove(command); this.permList.get(group).remove(command);
this.config.set("commands."+group, permList.get(group)); this.config.set("commands."+group, permList.get(group));
config.save(CommandWhitelist.getPlugin().getDataFolder()+"/config.yml"); config.save(CommandWhitelist.getPlugin().getDataFolder()+"/config.yml");
for (Player player : Bukkit.getOnlinePlayers()) {
player.updateCommands();
}
return true; return true;
} catch (Exception e) { } catch (Exception e) {
return false; return false;
@@ -75,4 +99,10 @@ public class ConfigCache {
public String getNoSuchGroup() { public String getNoSuchGroup() {
return noSuchGroup; return noSuchGroup;
} }
public String getSubCommandDenied() {
return subCommandDenied;
}
public boolean isUseProtocolLib() {
return useProtocolLib;
}
} }
@@ -19,18 +19,17 @@ public class LegacyPlayerTabChatCompleteListener {
public static void protocol(CommandWhitelist plugin) { public static void protocol(CommandWhitelist plugin) {
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager(); ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
tabCompleteServerBound(protocolManager, plugin); tabCompleteServerBound(protocolManager, plugin);
tabCompleteClientBound(protocolManager, plugin);
} }
public static void tabCompleteServerBound(ProtocolManager protocolManager, Plugin plugin) { public static void tabCompleteServerBound(ProtocolManager protocolManager, Plugin plugin) {
protocolManager.addPacketListener(new PacketAdapter(plugin, ListenerPriority.HIGHEST, PacketType.Play.Server.TAB_COMPLETE) { protocolManager.addPacketListener(new PacketAdapter(plugin, ListenerPriority.HIGHEST, PacketType.Play.Server.TAB_COMPLETE) {
@Override @Override
public void onPacketSending(PacketEvent event) { public void onPacketSending(PacketEvent event) {
try { try {
Player player = event.getPlayer(); Player player = event.getPlayer();
if (player.hasPermission("commandwhitelist.bypass")) { if (player.hasPermission("commandwhitelist.bypass"))
return; return;
}
PacketContainer packet = event.getPacket(); PacketContainer packet = event.getPacket();
String[] message = packet.getSpecificModifier(String[].class).read(0); String[] message = packet.getSpecificModifier(String[].class).read(0);
List<String> commandList = CommandsList.getCommands(player); List<String> commandList = CommandsList.getCommands(player);
@@ -44,18 +43,43 @@ public class LegacyPlayerTabChatCompleteListener {
} }
} }
} }
String[] toWrite = new String[components]; String[] toWrite = new String[components];
int counter = 0; int counter = 0;
for (String cmd : finalList) { for (String cmd : finalList) {
toWrite[counter++] = cmd; toWrite[counter++] = cmd;
} }
packet.getSpecificModifier(String[].class).write(0, toWrite); packet.getSpecificModifier(String[].class).write(0, toWrite);
} catch (Exception ignored) {} } catch (Exception ignored) {}
} }
}); });
} }
public static void tabCompleteClientBound(ProtocolManager protocolManager, Plugin plugin) {
protocolManager.addPacketListener(new PacketAdapter(plugin, ListenerPriority.HIGHEST, PacketType.Play.Client.TAB_COMPLETE) {
@Override
public void onPacketReceiving(PacketEvent event) {
try {
Player player = event.getPlayer();
if (player.hasPermission("commandwhitelist.bypass")) {
return;
}
PacketContainer packet = event.getPacket();
String command = packet.getSpecificModifier(String.class).read(0);
String label = CommandsList.getCommandLabel(command);
List<String> commandList = CommandsList.getCommands(player);
if (command.equals("/"))
return;
for (String cmd : commandList) {
if (cmd.startsWith("/"+label+" "))
return;
}
event.setCancelled(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
} }
@@ -0,0 +1,68 @@
package eu.endermite.commandwhitelist.spigot.listeners;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import eu.endermite.commandwhitelist.api.CommandsList;
import eu.endermite.commandwhitelist.api.RandomStuff;
import eu.endermite.commandwhitelist.spigot.CommandWhitelist;
import eu.endermite.commandwhitelist.spigot.config.ConfigCache;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.util.List;
import java.util.Map;
public class PacketCommandSendListener {
public static void protocol(CommandWhitelist plugin) {
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
commandExecListener(protocolManager, plugin);
}
public static void commandExecListener(ProtocolManager protocolManager, Plugin plugin) {
protocolManager.addPacketListener(new PacketAdapter(plugin, ListenerPriority.HIGHEST, PacketType.Play.Client.CHAT) {
@Override
public void onPacketReceiving(PacketEvent event) {
PacketContainer packet = event.getPacket();
String string = packet.getStrings().read(0);
if (!string.startsWith("/"))
return;
Player player = event.getPlayer();
if (player.hasPermission("commandwhitelist.bypass"))
return;
String cmd = string.replace("/", "");
String[] split = cmd.split("\\s+");
String command = split[0].toLowerCase();
for (Map.Entry<String, List<String>> s : CommandWhitelist.getConfigCache().getPermList().entrySet()) {
if (!player.hasPermission("commandwhitelist.commands." + s.getKey()))
continue;
for (String comm : s.getValue()) {
comm = comm.toLowerCase();
if (command.equalsIgnoreCase(comm) || command.startsWith(comm + " ")) {
List<String> bannedSubCommands = CommandsList.getSuggestions(player);
for (String bannedSubCommand : bannedSubCommands) {
if (cmd.startsWith(bannedSubCommand)) {
event.setCancelled(true);
ConfigCache config = CommandWhitelist.getConfigCache();
player.sendMessage(ChatColor.translateAlternateColorCodes('&', config.getPrefix() + RandomStuff.getMessage(config.getCommandDeniedList(), config.getSubCommandDenied())));
return;
}
}
return;
}
}
}
event.setCancelled(true);
ConfigCache config = CommandWhitelist.getConfigCache();
player.sendMessage(ChatColor.translateAlternateColorCodes('&', config.getPrefix() + RandomStuff.getMessage(config.getCommandDeniedList(), config.getCommandDenied())));
}
});
}
}
@@ -1,5 +1,6 @@
package eu.endermite.commandwhitelist.spigot.listeners; package eu.endermite.commandwhitelist.spigot.listeners;
import eu.endermite.commandwhitelist.api.CommandsList;
import eu.endermite.commandwhitelist.api.RandomStuff; import eu.endermite.commandwhitelist.api.RandomStuff;
import eu.endermite.commandwhitelist.spigot.CommandWhitelist; import eu.endermite.commandwhitelist.spigot.CommandWhitelist;
import eu.endermite.commandwhitelist.spigot.config.ConfigCache; import eu.endermite.commandwhitelist.spigot.config.ConfigCache;
@@ -8,37 +9,39 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class PlayerCommandPreProcessListener implements Listener { public class PlayerCommandPreProcessListener implements Listener {
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@EventHandler(priority = EventPriority.HIGHEST)
public void PlayerCommandSendEvent(org.bukkit.event.player.PlayerCommandPreprocessEvent event) { public void PlayerCommandSendEvent(org.bukkit.event.player.PlayerCommandPreprocessEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
if (player.hasPermission("commandwhitelist.bypass")) if (player.hasPermission("commandwhitelist.bypass"))
return; return;
String command = event.getMessage().toLowerCase(); String command = event.getMessage().toLowerCase();
if (command.startsWith("/"))
command = command.substring(1);
for (Map.Entry<String, List<String>> s : CommandWhitelist.getConfigCache().getPermList().entrySet()) { for (Map.Entry<String, List<String>> s : CommandWhitelist.getConfigCache().getPermList().entrySet()) {
if (!player.hasPermission("commandwhitelist.commands." + s.getKey())) if (!player.hasPermission("commandwhitelist.commands." + s.getKey()))
continue; continue;
for (String comm : s.getValue()) { for (String comm : s.getValue()) {
comm = comm.toLowerCase(); comm = comm.toLowerCase();
if (command.equalsIgnoreCase("/" + comm)) if (command.equalsIgnoreCase(comm) || command.startsWith(comm + " ")) {
List<String> bannedSubCommands = CommandsList.getSuggestions(player);
for (String bannedSubCommand : bannedSubCommands) {
if (command.startsWith(bannedSubCommand)) {
event.setCancelled(true);
ConfigCache config = CommandWhitelist.getConfigCache();
player.sendMessage(ChatColor.translateAlternateColorCodes('&', config.getPrefix() + RandomStuff.getMessage(config.getCommandDeniedList(), config.getSubCommandDenied())));
return; return;
else if (command.startsWith("/" + comm + " ")) { }
}
return; return;
} }
} }
} }
event.setCancelled(true); event.setCancelled(true);
ConfigCache config = CommandWhitelist.getConfigCache(); ConfigCache config = CommandWhitelist.getConfigCache();
player.sendMessage(ChatColor.translateAlternateColorCodes('&', CommandWhitelist.getConfigCache().getPrefix() + RandomStuff.getMessage(config.getCommandDeniedList(), config.getCommandDenied()))); player.sendMessage(ChatColor.translateAlternateColorCodes('&', config.getPrefix() + RandomStuff.getMessage(config.getCommandDeniedList(), config.getCommandDenied())));
} }
} }
@@ -8,21 +8,12 @@ import org.bukkit.event.Listener;
import java.util.*; import java.util.*;
public class PlayerCommandSendListener implements Listener { public class PlayerCommandSendListener implements Listener {
@EventHandler(priority = EventPriority.NORMAL)
@EventHandler(priority = EventPriority.HIGHEST)
public void PlayerCommandSendEvent(org.bukkit.event.player.PlayerCommandSendEvent event) { public void PlayerCommandSendEvent(org.bukkit.event.player.PlayerCommandSendEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
if (player.hasPermission("commandwhitelist.bypass"))
if (player.hasPermission("commandwhitelist.bypass")) {
return; return;
}
List<String> commandList = CommandsList.getCommands(player); List<String> commandList = CommandsList.getCommands(player);
event.getCommands().removeIf((cmd) -> !commandList.contains(cmd)); event.getCommands().removeIf((cmd) -> !commandList.contains(cmd));
} }
} }
@@ -0,0 +1,41 @@
package eu.endermite.commandwhitelist.spigot.listeners;
import eu.endermite.commandwhitelist.api.CommandsList;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import java.util.List;
public class TabCompleteBlockerListener implements Listener {
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onCommandTabComplete(org.bukkit.event.server.TabCompleteEvent event) {
if (!(event.getSender() instanceof Player))
return;
Player player = (Player) event.getSender();
String buffer = event.getBuffer();
List<String> blockedCommands = CommandsList.getSuggestions(player);
List<String> suggestions = event.getCompletions();
for (String s : blockedCommands) {
String slast = CommandsList.getLastArgument(s);
String scommand = s.replace(slast, "");
String[] cmdSplit = buffer.split(" ");
StringBuilder cmdBuilder = new StringBuilder();
for (int i = 0; i <= cmdSplit.length-1; i++)
cmdBuilder.append(cmdSplit[i]).append(" ");
String cmd = cmdBuilder.toString();
if (cmd.startsWith("/"+scommand)) {
// This sometimes throws exceptions. No clue why, it just does. try/catch is the only fix.
// Probably happening when plugin adds suggestions in this event on the same priority - not confirmed.
try {
while (suggestions.contains(slast))
suggestions.remove(slast);
} catch (Exception ignored) {}
}
}
event.setCompletions(suggestions);
}
}
@@ -0,0 +1,112 @@
package eu.endermite.commandwhitelist.velocity;
import com.moandjiezana.toml.Toml;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.api.event.command.PlayerAvailableCommandsEvent;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import eu.endermite.commandwhitelist.api.CommandsList;
import eu.endermite.commandwhitelist.velocity.command.VelocityMainCommand;
import eu.endermite.commandwhitelist.velocity.config.VelocityConfigCache;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import org.slf4j.Logger;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class CommandWhitelistVelocity {
private static CommandWhitelistVelocity plugin;
private static ProxyServer server;
private static VelocityConfigCache configCache;
private static Path folder;
@Inject
public CommandWhitelistVelocity(ProxyServer server, Logger logger, @DataDirectory final Path folder) {
CommandWhitelistVelocity.server = server;
CommandWhitelistVelocity.folder = folder;
CommandWhitelistVelocity.plugin = this;
}
private static Toml loadConfig(Path path) {
File folder = path.toFile();
File file = new File(folder, "config.toml");
if (!file.getParentFile().exists())
file.getParentFile().mkdirs();
if (!file.exists()) {
try (InputStream input = CommandWhitelistVelocity.class.getResourceAsStream("/" + file.getName())) {
if (input != null) {
Files.copy(input, file.toPath());
} else {
file.createNewFile();
}
} catch (IOException exception) {
exception.printStackTrace();
return null;
}
}
return new Toml().read(file);
}
private static void reloadConfig() {
configCache = new VelocityConfigCache(loadConfig(folder));
}
public static void reloadConfig(CommandSource source) {
server.getScheduler().buildTask(plugin, () -> {
reloadConfig();
source.sendMessage(Identity.nil(), Component.text(getConfigCache().getConfigReloaded()));
}).schedule();
}
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent event) {
reloadConfig();
CommandMeta commandMeta = server.getCommandManager().metaBuilder("vcw").build();
server.getCommandManager().register(commandMeta, new VelocityMainCommand());
}
@Subscribe
public void onUserCommandSendEvent(PlayerAvailableCommandsEvent event) {
if (event.getPlayer().hasPermission("commandwhitelist.bypass"))
return;
List<String> allowedCommands = CommandsList.getCommands(event.getPlayer());
event.getRootNode().getChildren().removeIf((commandNode) ->
server.getCommandManager().hasCommand(commandNode.getName())
&& !allowedCommands.contains(commandNode.getName())
);
}
@Subscribe
public void onUserCommandExecuteEvent(com.velocitypowered.api.event.command.CommandExecuteEvent event) {
if (!(event.getCommandSource() instanceof Player))
return;
Player player = (Player) event.getCommandSource();
if (player.hasPermission("commandwhitelist.bypass"))
return;
List<String> allowedCommands = CommandsList.getCommands(player);
String command = event.getCommand().split(" ")[0];
if (server.getCommandManager().hasCommand(command)
&& !allowedCommands.contains(command))
event.setResult(CommandExecuteEvent.CommandResult.forwardToServer());
}
public static VelocityConfigCache getConfigCache() {
return configCache;
}
}
@@ -0,0 +1,48 @@
package eu.endermite.commandwhitelist.velocity.command;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand;
import eu.endermite.commandwhitelist.velocity.CommandWhitelistVelocity;
import net.kyori.adventure.text.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class VelocityMainCommand implements SimpleCommand {
@Override
public void execute(final Invocation invocation) {
CommandSource source = invocation.source();
String[] args = invocation.arguments();
if (args.length > 0) {
if (args.length == 1 && args[0].equalsIgnoreCase("reload")) {
if (source.hasPermission("commandwhitelist.reload")) {
CommandWhitelistVelocity.reloadConfig(source);
} else {
source.sendMessage(Component.text(CommandWhitelistVelocity.getConfigCache().getNoPermission()));
}
}
} else {
source.sendMessage(Component.text("&bCommand Whitelist by YouHaveTrouble".replaceAll("&", "§")));
if (source.hasPermission("commandwhitelist.reload")) {
source.sendMessage(Component.text("&9/vcw reload &b- Reload velocity plugin configuration".replaceAll("&", "§")));
}
}
}
@Override
public CompletableFuture<List<String>> suggestAsync(Invocation invocation) {
CommandSource source = invocation.source();
String[] args = invocation.arguments();
return CompletableFuture.supplyAsync(() -> {
List<String> suggestions = new ArrayList<>();
if (args.length == 1) {
if (source.hasPermission("commandwhitelist.reload") && "reload".startsWith(args[0]))
suggestions.add("reload");
}
return suggestions;
});
}
}
@@ -0,0 +1,38 @@
package eu.endermite.commandwhitelist.velocity.config;
import com.moandjiezana.toml.Toml;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class VelocityConfigCache {
private HashMap<String, List<String>> permList = new HashMap<>();
private final String noPermission, noSubCommand, configReloaded;
public VelocityConfigCache(Toml config) {
Toml messages = config.getTable("messages");
noPermission = messages.getString("no-permission", "&cYou don't have permission to do this.");
noSubCommand = messages.getString("no-such-subcommand", "&cNo subcommand by that name.");
configReloaded = messages.getString("config-reloaded", "&eConfiguration reloaded.");
Toml groups = config.getTable("commands");
for (Map.Entry<String, Object> set : groups.entrySet()) {
this.permList.put(set.getKey(), (List<String>) set.getValue());
}
}
public HashMap<String, List<String>> getPermList() {
return permList;
}
public String getNoPermission() {
return noPermission.replaceAll("&", "§");
}
public String getNoSubCommand() {return noSubCommand.replaceAll("&", "§");}
public String getConfigReloaded() {return configReloaded.replaceAll("&", "§");}
}
+1 -3
View File
@@ -13,9 +13,7 @@ commands:
# Permissions that control what commands players can use # Permissions that control what commands players can use
# you can add unlimited amount of whitelists # you can add unlimited amount of whitelists
# commandwhitelist.commands.default # this will be automatically given to players by default
# this will NOT be automatically given to players by default!
# you have to give this permission to default droup by yourself as there is no automatic way to do it for a plugin
default: default:
- glist - glist
- server - server
+24
View File
@@ -0,0 +1,24 @@
# This is velocity version of the config.
[messages]
prefix="CommandWhitelist > "
command-denied="No such command."
subcommand-denied="You cannot use this subcommand"
no-permission="&cYou don't have permission to do this."
no-such-subcommand="&cNo subcommand by that name."
config-reloaded="&eConfiguration reloaded."
added-to-whitelist="&eWhitelisted command &6%s &efor permission &6%s"
removed-from-whitelist="&eRemoved command &6%s &efrom permission &6%s"
group-doesnt-exist="&cGroup doesn't exist or error occured"
# Permissions that control what commands players can use
# you can add unlimited amount of whitelists
[commands]
# this will be automatically given to players by default
default= [
"velocity",
"server",
]
# commandwhitelist.commands.example
example= [
"example"
]
+13 -1
View File
@@ -1,6 +1,7 @@
messages: messages:
prefix: "CommandWhitelist > " prefix: "CommandWhitelist > "
command-denied: "No such command." command-denied: "No such command."
subcommand-denied: "You cannot use this subcommand"
no-permission: "&cYou don't have permission to do this." no-permission: "&cYou don't have permission to do this."
no-such-subcommand: "&cNo subcommand by that name." no-such-subcommand: "&cNo subcommand by that name."
config-reloaded: "&eConfiguration reloaded." config-reloaded: "&eConfiguration reloaded."
@@ -8,11 +9,15 @@ messages:
removed-from-whitelist: "&eRemoved command &6%s &efrom permission &6%s" removed-from-whitelist: "&eRemoved command &6%s &efrom permission &6%s"
group-doesnt-exist: "&cGroup doesn't exist or error occured" group-doesnt-exist: "&cGroup doesn't exist or error occured"
# To cover the 1% of plugins that don't register their commands and/or aliases properly.
# Do not enable if you don't have issues with aliased commands.
# This requires server restart to take effect.
use-protocollib-to-detect-commands: false
commands: commands:
# Permissions that control what commands players can use # Permissions that control what commands players can use
# you can add unlimited amount of whitelists # you can add unlimited amount of whitelists
# commandwhitelist.commands.default
# this will be automatically given to players by default # this will be automatically given to players by default
default: default:
- help - help
@@ -31,3 +36,10 @@ commands:
# commandwhitelist.commands.example # commandwhitelist.commands.example
example: example:
- example - example
tabcompletions:
# This one is working as a blacklist. Player will not be able
# to see/use listed subcommands unless they have specified permission
# commandwhitelist.subcommands.example
example:
- help about
+9
View File
@@ -0,0 +1,9 @@
{
"id":"commandwhitelist",
"name":"CommandWhitelist",
"version":"${project.version}",
"description":"Control what commands players can use",
"authors":["YouHaveTrouble"],
"dependencies":[],
"main":"eu.endermite.commandwhitelist.velocity.CommandWhitelistVelocity"
}