Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
/Essentials/kits.yml
/Essentials/userdata/testplayer1.yml
/Essentials/usermap.csv
/Essentials/command-filters.yml

# Build files
.gradle/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,17 @@ private void teleport(final IUser teleportee, final ITarget target, final Trade
delay = event.getDelay();

Trade cashCharge = chargeFor;
String cooldownCommand = null;

if (chargeFor != null) {
chargeFor.isAffordableFor(teleportOwner, future);
if (future.isCompletedExceptionally()) {
return;
}

// When cashCharge is being reassigned below, ensure the charge knows the command we should apply cooldown on
cooldownCommand = chargeFor.getCommand();

//This code is to make sure that commandcosts are checked in the initial world, and not in the resulting world.
if (!chargeFor.getCommandCost(teleportOwner).equals(BigDecimal.ZERO)) {
//By converting a command cost to a regular cost, the command cost permission isn't checked when executing the charge after teleport.
Expand All @@ -288,7 +292,7 @@ private void teleport(final IUser teleportee, final ITarget target, final Trade
}
nowAsync(teleportee, target, cause, future);
if (cashCharge != null) {
cashCharge.charge(teleportOwner, future);
cashCharge.charge(teleportOwner, cooldownCommand, future);
if (future.isCompletedExceptionally()) {
return;
}
Expand All @@ -313,13 +317,17 @@ private void teleportOther(final IUser teleporter, final IUser teleportee, final
delay = event.getDelay();

Trade cashCharge = chargeFor;
String cooldownCommand = null;

if (teleporter != null && chargeFor != null) {
chargeFor.isAffordableFor(teleporter, future);
if (future.isCompletedExceptionally()) {
return;
}

// When cashCharge is being reassigned below, ensure the charge knows the command we should apply cooldown on
cooldownCommand = chargeFor.getCommand();

//This code is to make sure that commandcosts are checked in the initial world, and not in the resulting world.
if (!chargeFor.getCommandCost(teleporter).equals(BigDecimal.ZERO)) {
//By converting a command cost to a regular cost, the command cost permission isn't checked when executing the charge after teleport.
Expand All @@ -340,7 +348,7 @@ private void teleportOther(final IUser teleporter, final IUser teleportee, final

nowAsync(teleportee, target, cause, future);
if (teleporter != null && cashCharge != null) {
cashCharge.charge(teleporter, future);
cashCharge.charge(teleporter, cooldownCommand, future);
if (future.isCompletedExceptionally()) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.earth2me.essentials;

import com.google.common.base.Preconditions;
import net.ess3.api.IUser;

import java.math.BigDecimal;
import java.util.Date;
import java.util.regex.Pattern;

public abstract class CommandFilter {

public enum Type {
REGEX,
ESS
}

private final String name;
private final Pattern pattern;
private final Integer cooldown;
private final boolean persistentCooldown;
private final BigDecimal cost;

public CommandFilter(String name, Pattern pattern, Integer cooldown, boolean persistentCooldown, BigDecimal cost) {
Preconditions.checkNotNull(pattern);
this.name = name;
this.pattern = pattern;
this.cooldown = cooldown;
this.persistentCooldown = persistentCooldown;
this.cost = cost;
}

public String getName() {
return name;
}

public Pattern getPattern() {
return pattern;
}

public boolean hasCooldown() {
return cooldown != null;
}

public Integer getCooldown() {
return cooldown;
}

public boolean applyCooldownTo(IUser user) {
if (!hasCooldown()) return false;
final Date expiry = new Date(System.currentTimeMillis() + cooldown);
user.addCommandCooldown(pattern, expiry, persistentCooldown);
return true;
}

public boolean isPersistentCooldown() {
return persistentCooldown;
}

public boolean hasCost() {
return cost != null;
}

public BigDecimal getCost() {
return cost;
}
}
146 changes: 146 additions & 0 deletions Essentials/src/main/java/com/earth2me/essentials/CommandFilters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.earth2me.essentials;

import net.ess3.api.IUser;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.MemoryConfiguration;

import java.io.File;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

public class CommandFilters implements IConf {

private final IEssentials essentials;
private final EssentialsConf config;
private ConfigurationSection filters;
private Map<CommandFilter.Type, List<CommandFilter>> commandFilters;

public CommandFilters(final IEssentials essentials) {
this.essentials = essentials;
config = new EssentialsConf(new File(essentials.getDataFolder(), "command-filters.yml"));
config.setTemplateName("/command-filters.yml");

reloadConfig();
}

@Override
public void reloadConfig() {
config.load();
filters = _getCommandFilterSection();
commandFilters = _getCommandFilters();
}

private ConfigurationSection _getCommandFilterSection() {
if (config.isConfigurationSection("filters")) {
final ConfigurationSection section = config.getConfigurationSection("filters");
final ConfigurationSection newSection = new MemoryConfiguration();
for (final String filterItem : section.getKeys(false)) {
if (section.isConfigurationSection(filterItem)) {
newSection.set(filterItem.toLowerCase(Locale.ENGLISH), section.getConfigurationSection(filterItem));
}
}
return newSection;
}
return null;
}

private Map<CommandFilter.Type, List<CommandFilter>> _getCommandFilters() {
final Map<CommandFilter.Type, List<CommandFilter>> commandFilters = new EnumMap<>(CommandFilter.Type.class);
for (final String name : filters.getKeys(false)) {
if (!filters.isConfigurationSection(name)) {
EssentialsConf.LOGGER.warning("Invalid command filter '" + name + "'");
continue;
}

final ConfigurationSection section = Objects.requireNonNull(filters.getConfigurationSection(name));
final Pattern pattern = section.isString("pattern") ? compileRegex(section.getString("pattern")) : null;
final String command = section.getString("command");

if (pattern == null && command == null) {
EssentialsConf.LOGGER.warning("Invalid command filter '" + name + "', filter must either define 'pattern' or 'command'!");
continue;
}

if (pattern != null && command != null) {
EssentialsConf.LOGGER.warning("Invalid command filter '" + name + "', filter can't have both 'pattern' and 'command'!");
continue;
}

Integer cooldown = section.getInt("cooldown", -1);
if (cooldown < 0) {
cooldown = null;
} else {
cooldown *= 1000; // Convert to milliseconds
}

final boolean persistentCooldown = section.getBoolean("persistent-cooldown", true);
final BigDecimal cost = EssentialsConf.toBigDecimal(section.getString("cost"), null);

final String lowerName = name.toLowerCase(Locale.ENGLISH);

if (pattern == null) {
commandFilters.computeIfAbsent(CommandFilter.Type.ESS, k -> new ArrayList<>()).add(new EssCommandFilter(lowerName, command, compileRegex(command), cooldown, persistentCooldown, cost));
} else {
commandFilters.computeIfAbsent(CommandFilter.Type.REGEX, k -> new ArrayList<>()).add(new RegexCommandFilter(lowerName, pattern, cooldown, persistentCooldown, cost));
}
}
config.save();
return commandFilters;
}

private Pattern compileRegex(String regex) {
if (regex.startsWith("^")) {
try {
return Pattern.compile(regex.substring(1));
} catch (final PatternSyntaxException e) {
essentials.getLogger().warning("Command cooldown error: " + e.getMessage());
return null;
}
} else {
// Escape above Regex
if (regex.startsWith("\\^")) {
regex = regex.substring(1);
}
final String cmd = regex.replaceAll("\\*", ".*"); // Wildcards are accepted as asterisk * as known universally.
return Pattern.compile(cmd + "( .*)?"); // This matches arguments, if present, to "ignore" them from the feature.
}
}

public EssentialsConf getConfig() {
return config;
}

public CommandFilter getCommandCooldown(final IUser user, final String label, CommandFilter.Type type) {
if (user.isAuthorized("essentials.commandcooldowns.bypass")) return null;
return getFilter(label, type, filter -> filter.hasCooldown() && !user.isAuthorized("essentials.commandcooldowns.bypass." + filter.getName()));
}

public CommandFilter getCommandCost(final IUser user, final String label, CommandFilter.Type type) {
if (user.isAuthorized("essentials.nocommandcost.all")) return null;
return getFilter(label, type, filter -> filter.hasCost() && !user.isAuthorized("essentials.nocommandcost." + filter.getName()));
}

private CommandFilter getFilter(final String label, CommandFilter.Type type, Predicate<CommandFilter> filterPredicate) {
for (CommandFilter filter : commandFilters.get(type)) {
if (!filterPredicate.test(filter)) continue;

final boolean matches = filter.getPattern().matcher(label).matches();
if (essentials.getSettings().isDebug()) {
essentials.getLogger().info(String.format("Checking command '%s' against filter '%s': %s", label, filter.getName(), matches));
}

if (matches) {
return filter;
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.earth2me.essentials;

import java.math.BigDecimal;
import java.util.regex.Pattern;

public class EssCommandFilter extends CommandFilter {

private final String command;

public EssCommandFilter(String name, String command, Pattern pattern, Integer cooldown, boolean persistentCooldown, BigDecimal cost) {
super(name, pattern, cooldown, persistentCooldown, cost);
this.command = command;
}

public String getCommand() {
return command;
}
}
12 changes: 12 additions & 0 deletions Essentials/src/main/java/com/earth2me/essentials/Essentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
private transient SyncCommandsProvider syncCommandsProvider;
private transient PersistentDataProvider persistentDataProvider;
private transient Kits kits;
private transient CommandFilters commandFilters;
private transient RandomTeleport randomTeleport;
private transient UpdateChecker updateChecker;
private transient Map<String, IEssentialsCommand> commandMap = new HashMap<>();
Expand Down Expand Up @@ -206,6 +207,7 @@ public void setupForTesting(final Server server) throws IOException, InvalidDesc
jails = new Jails(this);
registerListeners(server.getPluginManager());
kits = new Kits(this);
commandFilters = new CommandFilters(this);
}

@Override
Expand Down Expand Up @@ -283,6 +285,11 @@ public void onEnable() {
upgrade.convertKits();
execTimer.mark("Kits");

commandFilters = new CommandFilters(this);
confList.add(commandFilters);
upgrade.convertCommandFilters();
execTimer.mark("CommandFilters");

upgrade.afterSettings();
execTimer.mark("Upgrade2");

Expand Down Expand Up @@ -835,6 +842,11 @@ public Kits getKits() {
return kits;
}

@Override
public CommandFilters getCommandFilters() {
return commandFilters;
}

@Override
public RandomTeleport getRandomTeleport() {
return randomTeleport;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.earth2me.essentials.utils.FormatUtil;
import com.earth2me.essentials.utils.LocationUtil;
import com.earth2me.essentials.utils.MaterialUtil;
import com.earth2me.essentials.utils.NumberUtil;
import com.earth2me.essentials.utils.VersionUtil;
import io.papermc.lib.PaperLib;
import net.ess3.api.IEssentials;
Expand Down Expand Up @@ -56,6 +57,7 @@

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Date;
Expand Down Expand Up @@ -614,8 +616,7 @@ public void handlePlayerCommandPreprocess(final PlayerCommandPreprocessEvent eve
user.updateActivityOnInteract(broadcast);
}

if (ess.getSettings().isCommandCooldownsEnabled()
&& !user.isAuthorized("essentials.commandcooldowns.bypass")) {
if (pluginCommand != null) {
final int argStartIndex = effectiveCommand.indexOf(" ");
final String args = argStartIndex == -1 ? "" // No arguments present
: " " + effectiveCommand.substring(argStartIndex); // arguments start at argStartIndex; substring from there.
Expand Down Expand Up @@ -644,14 +645,27 @@ public void handlePlayerCommandPreprocess(final PlayerCommandPreprocessEvent eve
}

if (!cooldownFound) {
final Entry<Pattern, Long> cooldownEntry = ess.getSettings().getCommandCooldownEntry(fullCommand);
final CommandFilter cooldownFilter = ess.getCommandFilters().getCommandCooldown(user, fullCommand, CommandFilter.Type.REGEX);
if (cooldownFilter != null) {
if (ess.getSettings().isDebug()) {
ess.getLogger().info("Applying " + cooldownFilter.getCooldown() + "ms cooldown on /" + fullCommand + " for " + user.getName() + ".");
}
cooldownFilter.applyCooldownTo(user);
}

if (cooldownEntry != null) {
final CommandFilter costFilter = ess.getCommandFilters().getCommandCost(user, fullCommand, CommandFilter.Type.REGEX);
if (costFilter != null) {
if (ess.getSettings().isDebug()) {
ess.getLogger().info("Applying " + cooldownEntry.getValue() + "ms cooldown on /" + fullCommand + " for" + user.getName() + ".");
ess.getLogger().info("Applying a cost of " + costFilter.getCost() + " on /" + fullCommand + " for " + user.getName() + ".");
}

final BigDecimal cost = costFilter.getCost();
if (!user.canAfford(cost) && cost.signum() > 0) {
player.sendMessage(tl("notEnoughMoney", NumberUtil.displayCurrency(cost, ess)));
event.setCancelled(true);
return;
}
final Date expiry = new Date(System.currentTimeMillis() + cooldownEntry.getValue());
user.addCommandCooldown(cooldownEntry.getKey(), expiry, ess.getSettings().isCommandCooldownPersistent(fullCommand));
user.takeMoney(cost);
}
}
}
Expand Down
Loading