Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of CommandFilters, replacing cooldowns and costs #3886

Open
wants to merge 15 commits into
base: 2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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 @@ -17,6 +17,7 @@
/Essentials/usermap.csv
/Essentials/usermap.bin
/Essentials/uuids.bin
/Essentials/command-filters.yml

# Build files
.gradle/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,13 +266,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 @@ -291,7 +295,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 @@ -316,13 +320,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 @@ -343,7 +351,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;
}
}
125 changes: 125 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,125 @@
package com.earth2me.essentials;

import com.earth2me.essentials.config.ConfigurateUtil;
import com.earth2me.essentials.config.EssentialsConfiguration;
import net.ess3.api.IUser;
import org.spongepowered.configurate.CommentedConfigurationNode;

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.function.Predicate;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

public class CommandFilters implements IConf {

private final IEssentials ess;
private final EssentialsConfiguration config;
private Map<CommandFilter.Type, List<CommandFilter>> commandFilters;

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

reloadConfig();
}

@Override
public void reloadConfig() {
config.load();
commandFilters = parseCommandFilters(config);
}

private Map<CommandFilter.Type, List<CommandFilter>> parseCommandFilters(EssentialsConfiguration config) {
final Map<CommandFilter.Type, List<CommandFilter>> commandFilters = new EnumMap<>(CommandFilter.Type.class);
final CommentedConfigurationNode filterSection = config.getSection("filters");
for (final String filterItem : ConfigurateUtil.getKeys(filterSection)) {
final CommentedConfigurationNode section = filterSection.node(filterItem);
final String patternString = section.node("pattern").getString();
final Pattern pattern = patternString == null ? null : compileRegex(patternString);
final String command = section.node("command").getString();

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

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

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

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

final String filterItemName = filterItem.toLowerCase(Locale.ENGLISH);

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

private Pattern compileRegex(String regex) {
if (regex.startsWith("^")) {
try {
return Pattern.compile(regex.substring(1));
} catch (final PatternSyntaxException e) {
ess.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 EssentialsConfiguration 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 (ess.getSettings().isDebug()) {
ess.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 @@ -183,6 +183,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
private transient WorldInfoProvider worldInfoProvider;
private transient SignDataProvider signDataProvider;
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 @@ -233,6 +234,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 @@ -322,6 +324,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("Upgrade3");

Expand Down Expand Up @@ -911,6 +918,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 @@ -10,6 +10,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 @@ -59,6 +60,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 @@ -674,9 +676,7 @@ public void handlePlayerCommandPreprocess(final PlayerCommandPreprocessEvent eve
user.updateActivityOnInteract(broadcast);
}

if (ess.getSettings().isCommandCooldownsEnabled()
&& !user.isAuthorized("essentials.commandcooldowns.bypass")
&& (pluginCommand == null || !user.isAuthorized("essentials.commandcooldowns.bypass." + pluginCommand.getName()))) {
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 @@ -705,14 +705,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