diff --git a/src/main/java/com/ghostchu/peerbanhelper/PeerBanHelperServer.java b/src/main/java/com/ghostchu/peerbanhelper/PeerBanHelperServer.java index 5484a41a7a..24165079ce 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/PeerBanHelperServer.java +++ b/src/main/java/com/ghostchu/peerbanhelper/PeerBanHelperServer.java @@ -27,12 +27,15 @@ import com.ghostchu.peerbanhelper.module.impl.rule.*; import com.ghostchu.peerbanhelper.module.impl.webapi.*; import com.ghostchu.peerbanhelper.peer.Peer; +import com.ghostchu.peerbanhelper.scriptengine.ScriptEngine; import com.ghostchu.peerbanhelper.text.Lang; import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.torrent.Torrent; import com.ghostchu.peerbanhelper.util.*; +import com.ghostchu.peerbanhelper.util.json.JsonUtil; import com.ghostchu.peerbanhelper.util.rule.ModuleMatchCache; import com.ghostchu.peerbanhelper.util.time.ExceptedTime; +import com.ghostchu.peerbanhelper.util.time.InfoHashUtil; import com.ghostchu.peerbanhelper.util.time.TimeoutProtect; import com.ghostchu.peerbanhelper.web.JavalinWebContainer; import com.ghostchu.peerbanhelper.wrapper.BanMetadata; @@ -44,10 +47,15 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.gson.JsonObject; +import com.googlecode.aviator.AviatorEvaluator; +import com.googlecode.aviator.EvalMode; +import com.googlecode.aviator.Options; +import com.googlecode.aviator.runtime.JavaMethodReflectionFunctionMissing; import inet.ipaddr.IPAddress; import io.javalin.util.JavalinBindException; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.bspfsystems.yamlconfiguration.configuration.ConfigurationSection; import org.bspfsystems.yamlconfiguration.configuration.MemoryConfiguration; import org.bspfsystems.yamlconfiguration.file.YamlConfiguration; @@ -62,6 +70,7 @@ import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; +import java.math.MathContext; import java.sql.SQLException; import java.util.*; import java.util.concurrent.*; @@ -128,6 +137,8 @@ public class PeerBanHelperServer implements Reloadable { private AlertManager alertManager; @Autowired private BanListDao banListDao; + @Getter + private ScriptEngine scriptEngine; public PeerBanHelperServer() { reloadConfig(); @@ -171,6 +182,7 @@ public void start() throws SQLException { log.info(tlUI(Lang.MOTD, Main.getMeta().getVersion())); loadDownloaders(); registerBanListInvokers(); + setupScriptEngine(); registerModules(); registerHttpServer(); setupIPDB(); @@ -190,6 +202,46 @@ public void start() throws SQLException { } + private void setupScriptEngine() { + AviatorEvaluator.getInstance().setCachedExpressionByDefault(true); + // ASM 性能优先 + AviatorEvaluator.getInstance().setOption(Options.EVAL_MODE, EvalMode.ASM); + // EVAL 性能优先 + AviatorEvaluator.getInstance().setOption(Options.OPTIMIZE_LEVEL, AviatorEvaluator.EVAL); + // 降低浮点计算精度 + AviatorEvaluator.getInstance().setOption(Options.MATH_CONTEXT, MathContext.DECIMAL32); + // 启用变量语法糖 + AviatorEvaluator.getInstance().setOption(Options.ENABLE_PROPERTY_SYNTAX_SUGAR, true); +// // 表达式允许序列化和反序列化 +// AviatorEvaluator.getInstance().setOption(Options.SERIALIZABLE, true); + // 用户规则写糊保护 + AviatorEvaluator.getInstance().setOption(Options.MAX_LOOP_COUNT, 5000); + // 启用反射方法查找 + AviatorEvaluator.getInstance().setFunctionMissing(JavaMethodReflectionFunctionMissing.getInstance()); + // 注册反射调用 + registerFunctions(IPAddressUtil.class); + registerFunctions(HTTPUtil.class); + registerFunctions(JsonUtil.class); + registerFunctions(Lang.class); + registerFunctions(StrUtil.class); + registerFunctions(PeerBanHelperServer.class); + registerFunctions(InfoHashUtil.class); + registerFunctions(Main.class); + } + + private void registerFunctions(Class clazz) { + try { + AviatorEvaluator.addInstanceFunctions(StringUtils.uncapitalize(clazz.getSimpleName()), clazz); + } catch (IllegalAccessException | NoSuchMethodException e) { + log.error("Internal error: failed on register instance functions: {}", clazz.getName(), e); + } + try { + AviatorEvaluator.addStaticFunctions(StringUtils.capitalize(clazz.getSimpleName()), clazz); + } catch (IllegalAccessException | NoSuchMethodException e) { + log.error("Internal error: failed on register static functions: {}", clazz.getName(), e); + } + } + private void sendSnapshotAlert() { if (Main.getMeta().isSnapshotOrBeta()) { alertManager.publishAlert(false, AlertLevel.INFO, "unstable-alert", new TranslationComponent(Lang.ALERT_SNAPSHOT), new TranslationComponent(Lang.ALERT_SNAPSHOT_DESCRIPTION)); diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/BtnConfig.java b/src/main/java/com/ghostchu/peerbanhelper/btn/BtnConfig.java index bc91a8e611..f3a9af208f 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/BtnConfig.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/BtnConfig.java @@ -1,6 +1,7 @@ package com.ghostchu.peerbanhelper.btn; import com.ghostchu.peerbanhelper.PeerBanHelperServer; +import com.ghostchu.peerbanhelper.scriptengine.ScriptEngine; import com.ghostchu.peerbanhelper.text.Lang; import com.ghostchu.peerbanhelper.util.rule.ModuleMatchCache; import lombok.extern.slf4j.Slf4j; @@ -22,7 +23,8 @@ public class BtnConfig { private String userAgent; @Autowired private ModuleMatchCache matchCache; - + @Autowired + private ScriptEngine scriptEngine; @Bean public BtnNetwork btnNetwork() { ConfigurationSection section = server.getMainConfig().getConfigurationSection("btn"); @@ -32,7 +34,7 @@ public BtnNetwork btnNetwork() { var submit = server.getMainConfig().getBoolean("btn.submit"); var appId = server.getMainConfig().getString("btn.app-id"); var appSecret = server.getMainConfig().getString("btn.app-secret"); - BtnNetwork btnNetwork = new BtnNetwork(server, userAgent, configUrl, submit, appId, appSecret, matchCache); + BtnNetwork btnNetwork = new BtnNetwork(server, scriptEngine, userAgent, configUrl, submit, appId, appSecret, matchCache); log.info(tlUI(Lang.BTN_NETWORK_ENABLED)); return btnNetwork; } else { diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/BtnNetwork.java b/src/main/java/com/ghostchu/peerbanhelper/btn/BtnNetwork.java index f44ad7e49c..0d1ade59c3 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/BtnNetwork.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/BtnNetwork.java @@ -3,6 +3,7 @@ import com.ghostchu.peerbanhelper.PeerBanHelperServer; import com.ghostchu.peerbanhelper.btn.ability.*; import com.ghostchu.peerbanhelper.database.dao.impl.PeerRecordDao; +import com.ghostchu.peerbanhelper.scriptengine.ScriptEngine; import com.ghostchu.peerbanhelper.text.Lang; import com.ghostchu.peerbanhelper.util.HTTPUtil; import com.ghostchu.peerbanhelper.util.rule.ModuleMatchCache; @@ -35,6 +36,7 @@ public class BtnNetwork { private static final int PBH_BTN_PROTOCOL_IMPL_VERSION = 8; @Getter private final Map, BtnAbility> abilities = new HashMap<>(); + private final ScriptEngine scriptEngine; @Getter private ScheduledExecutorService executeService = null; private String configUrl; @@ -52,8 +54,9 @@ public class BtnNetwork { private PeerRecordDao peerRecordDao; private ModuleMatchCache moduleMatchCache; - public BtnNetwork(PeerBanHelperServer server, String userAgent, String configUrl, boolean submit, String appId, String appSecret, ModuleMatchCache moduleMatchCache) { + public BtnNetwork(PeerBanHelperServer server, ScriptEngine scriptEngine, String userAgent, String configUrl, boolean submit, String appId, String appSecret, ModuleMatchCache moduleMatchCache) { this.server = server; + this.scriptEngine = scriptEngine; this.userAgent = userAgent; this.configUrl = configUrl; this.submit = submit; @@ -109,7 +112,7 @@ public void configBtnNetwork() { // abilities.put(BtnAbilitySubmitRulesHitRate.class, new BtnAbilitySubmitRulesHitRate(this, ability.get("submit_hitrate").getAsJsonObject())); // } if (ability.has("rules")) { - abilities.put(BtnAbilityRules.class, new BtnAbilityRules(this, ability.get("rules").getAsJsonObject())); + abilities.put(BtnAbilityRules.class, new BtnAbilityRules(this, scriptEngine, ability.get("rules").getAsJsonObject())); } if (ability.has("reconfigure")) { abilities.put(BtnAbilityReconfigure.class, new BtnAbilityReconfigure(this, ability.get("reconfigure").getAsJsonObject())); diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/BtnRule.java b/src/main/java/com/ghostchu/peerbanhelper/btn/BtnRule.java index 9c8b37e270..77dea24ca4 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/BtnRule.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/BtnRule.java @@ -22,4 +22,6 @@ public class BtnRule { private Map> ipRules; @SerializedName("port") private Map> portRules; + @SerializedName("script") + private Map scriptRules; } diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/BtnRuleParsed.java b/src/main/java/com/ghostchu/peerbanhelper/btn/BtnRuleParsed.java index 1a7ea3d9d2..d7dc257cda 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/BtnRuleParsed.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/BtnRuleParsed.java @@ -1,5 +1,7 @@ package com.ghostchu.peerbanhelper.btn; +import com.ghostchu.peerbanhelper.scriptengine.CompiledScript; +import com.ghostchu.peerbanhelper.scriptengine.ScriptEngine; import com.ghostchu.peerbanhelper.text.Lang; import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.util.IPAddressUtil; @@ -10,6 +12,7 @@ import com.ghostchu.peerbanhelper.util.rule.matcher.IPMatcher; import inet.ipaddr.IPAddress; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -17,20 +20,45 @@ import java.util.List; import java.util.Map; +import static com.ghostchu.peerbanhelper.text.TextManager.tlUI; + @Data +@Slf4j public class BtnRuleParsed { + private final ScriptEngine scriptEngine; private String version; private Map> peerIdRules; private Map> clientNameRules; private Map> ipRules; private Map> portRules; + private Map scriptRules; - public BtnRuleParsed(BtnRule btnRule) { + public BtnRuleParsed(ScriptEngine scriptEngine, BtnRule btnRule) { + this.scriptEngine = scriptEngine; this.version = btnRule.getVersion(); this.ipRules = parseIPRule(btnRule.getIpRules()); this.portRules = parsePortRule(btnRule.getPortRules()); this.peerIdRules = parseRule(btnRule.getPeerIdRules()); this.clientNameRules = parseRule(btnRule.getClientNameRules()); + this.scriptRules = compileScripts(btnRule.getScriptRules()); + } + + private Map compileScripts(Map scriptRules) { + Map scripts = new HashMap<>(); + log.info(tlUI(Lang.BTN_RULES_SCRIPT_COMPILING, scriptRules.size())); + long startAt = System.currentTimeMillis(); + scriptRules.forEach((name, content) -> { + try { + var script = scriptEngine.compileScript(null, name, content); + if (script != null) { + scripts.put(name, script); + } + } catch (Exception e) { + log.error("Unable to load BTN script {}", name, e); + } + }); + log.info(tlUI(Lang.BTN_RULES_SCRIPT_COMPILED, scripts.size(), System.currentTimeMillis() - startAt)); + return scripts; } private Map> parsePortRule(Map> portRules) { @@ -66,6 +94,7 @@ public String matcherIdentifier() { return rules; } + public Map> parseIPRule(Map> raw) { Map> rules = new HashMap<>(); raw.forEach((k, v) -> rules.put(k, List.of(new BtnRuleIpMatcher(version, k, k, v.stream().map(IPAddressUtil::getIPAddress).toList())))); diff --git a/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityRules.java b/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityRules.java index 970fe40178..f1988ec113 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityRules.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityRules.java @@ -5,6 +5,7 @@ import com.ghostchu.peerbanhelper.btn.BtnRule; import com.ghostchu.peerbanhelper.btn.BtnRuleParsed; import com.ghostchu.peerbanhelper.event.BtnRuleUpdateEvent; +import com.ghostchu.peerbanhelper.scriptengine.ScriptEngine; import com.ghostchu.peerbanhelper.text.Lang; import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.util.HTTPUtil; @@ -34,12 +35,14 @@ public class BtnAbilityRules extends AbstractBtnAbility { private final String endpoint; private final long randomInitialDelay; private final File btnCacheFile = new File(Main.getDataDirectory(), "btn.cache"); + private final ScriptEngine scriptEngine; @Getter private BtnRuleParsed btnRule; - public BtnAbilityRules(BtnNetwork btnNetwork, JsonObject ability) { + public BtnAbilityRules(BtnNetwork btnNetwork, ScriptEngine scriptEngine, JsonObject ability) { this.btnNetwork = btnNetwork; + this.scriptEngine = scriptEngine; this.interval = ability.get("interval").getAsLong(); this.endpoint = ability.get("endpoint").getAsString(); this.randomInitialDelay = ability.get("random_initial_delay").getAsLong(); @@ -55,7 +58,7 @@ private void loadCacheFile() throws IOException { } else { try { BtnRule btnRule = JsonUtil.getGson().fromJson(Files.readString(btnCacheFile.toPath()), BtnRule.class); - this.btnRule = new BtnRuleParsed(btnRule); + this.btnRule = new BtnRuleParsed(scriptEngine, btnRule); } catch (Throwable ignored) { } } @@ -110,7 +113,7 @@ private void updateRule() { } else { try { BtnRule btr = JsonUtil.getGson().fromJson(r.body(), BtnRule.class); - this.btnRule = new BtnRuleParsed(btr); + this.btnRule = new BtnRuleParsed(scriptEngine, btr); Main.getEventBus().post(new BtnRuleUpdateEvent()); try { Files.writeString(btnCacheFile.toPath(), r.body(), StandardCharsets.UTF_8); diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/RuleFeatureModule.java b/src/main/java/com/ghostchu/peerbanhelper/module/RuleFeatureModule.java index 5720a9ea8d..91d8bb6372 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/RuleFeatureModule.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/RuleFeatureModule.java @@ -5,6 +5,7 @@ import com.ghostchu.peerbanhelper.torrent.Torrent; import org.jetbrains.annotations.NotNull; +import java.util.List; import java.util.concurrent.ExecutorService; public interface RuleFeatureModule extends FeatureModule { @@ -12,12 +13,12 @@ public interface RuleFeatureModule extends FeatureModule { * 检查一个特定的 Torrent 和 Peer 是否应该封禁 * * @param torrent Torrent - * @param peer Peer + * @param peers Peers * @param ruleExecuteExecutor 如果需要并发执行任务,请在给定的执行器中执行,以接受线程池的约束避免资源消耗失控 * @return 规则检查结果 */ @NotNull - CheckResult shouldBanPeer(@NotNull Torrent torrent, @NotNull Peer peer, @NotNull Downloader downloader, @NotNull ExecutorService ruleExecuteExecutor); + CheckResult shouldBanPeer(@NotNull Torrent torrent, @NotNull Peer peers, @NotNull Downloader downloader, @NotNull ExecutorService ruleExecuteExecutor); /** * 指示模块的内部处理逻辑是否是线程安全的,如果线程不安全,PeerBanHelper 将在同步块中执行不安全的模块 diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/BtnNetworkOnline.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/BtnNetworkOnline.java index bb284969be..1f21df2471 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/BtnNetworkOnline.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/BtnNetworkOnline.java @@ -12,6 +12,8 @@ import com.ghostchu.peerbanhelper.module.CheckResult; import com.ghostchu.peerbanhelper.module.PeerAction; import com.ghostchu.peerbanhelper.peer.Peer; +import com.ghostchu.peerbanhelper.scriptengine.CompiledScript; +import com.ghostchu.peerbanhelper.scriptengine.ScriptEngine; import com.ghostchu.peerbanhelper.text.Lang; import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.torrent.Torrent; @@ -26,6 +28,7 @@ import com.ghostchu.simplereloadlib.ReloadResult; import com.ghostchu.simplereloadlib.ReloadStatus; import com.ghostchu.simplereloadlib.Reloadable; +import com.googlecode.aviator.exception.TimeoutException; import inet.ipaddr.IPAddress; import io.javalin.http.Context; import lombok.extern.slf4j.Slf4j; @@ -38,9 +41,15 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import static com.ghostchu.peerbanhelper.text.TextManager.tl; +import static com.ghostchu.peerbanhelper.text.TextManager.tlUI; @Slf4j @Component @@ -54,6 +63,9 @@ public class BtnNetworkOnline extends AbstractRuleFeatureModule implements Reloa private JavalinWebContainer javalinWebContainer; @Autowired private BtnNetwork btnNetwork; + @Autowired + private ScriptEngine scriptEngine; + @Override public @NotNull String getName() { @@ -155,9 +167,81 @@ public boolean isThreadSafe() { if (checkExceptionResult.action() == PeerAction.SKIP) { return checkExceptionResult; } + var scriptResult = checkScript(torrent, peer, downloader, ruleExecuteExecutor); + if (scriptResult.action() != PeerAction.NO_ACTION) { + return scriptResult; + } return checkShouldBan(torrent, peer, downloader, ruleExecuteExecutor); } + private @NotNull CheckResult checkScript(Torrent torrent, Peer peer, Downloader downloader, ExecutorService ruleExecuteExecutor) { + var abilityObject = manager.getAbilities().get(BtnAbilityRules.class); + if (abilityObject == null) { + return pass(); + } + BtnAbilityRules exception = (BtnAbilityRules) abilityObject; + BtnRuleParsed rule = exception.getBtnRule(); + if (rule == null) { + return pass(); + } + if (isHandShaking(peer)) { + return handshaking(); + } + + List> futures = new ArrayList<>(); + try (ExecutorService exec = Executors.newVirtualThreadPerTaskExecutor()) { + for (var kvPair : rule.getScriptRules().entrySet()) { + futures.add(exec.submit(() -> runExpression(kvPair.getValue(), torrent, peer, downloader, ruleExecuteExecutor))); + } + } + + CheckResult finalResult = pass(); + for (Future future : futures) { + try { + CheckResult result = future.get(); + if (result.action() == PeerAction.SKIP) { + return result; // Early exit on SKIP action + } else if (result.action() == PeerAction.BAN) { + finalResult = result; + } + } catch (InterruptedException | ExecutionException e) { + log.error("Error executing script, skipping", e); + } + } + + return finalResult; + } + + public @NotNull CheckResult runExpression(CompiledScript script, @NotNull Torrent torrent, @NotNull Peer peer, @NotNull Downloader downloader, @NotNull ExecutorService ruleExecuteExecutor) { + return getCache().readCacheButWritePassOnly(this, script.hashCode() + peer.getCacheKey(), () -> { + CheckResult result; + try { + Map env = script.expression().newEnv(); + env.put("torrent", torrent); + env.put("peer", peer); + env.put("downloader", downloader); + env.put("cacheable", new AtomicBoolean(false)); + env.put("server", getServer()); + env.put("moduleInstance", this); + Object returns; + synchronized (script.expression()) { + returns = script.expression().execute(env); + } + result = scriptEngine.handleResult(script, banDuration, returns); + } catch (TimeoutException timeoutException) { + return pass(); + } catch (Exception ex) { + log.error(tlUI(Lang.RULE_ENGINE_ERROR, script.name()), ex); + return pass(); + } + if (result != null && result.action() != PeerAction.NO_ACTION) { + return result; + } else { + return pass(); + } + }, script.cacheable()); + } + private @NotNull CheckResult checkShouldSkip(Torrent torrent, Peer peer, Downloader downloader, ExecutorService ruleExecuteExecutor) { var abilityObject = manager.getAbilities().get(BtnAbilityException.class); if (abilityObject == null) { @@ -194,7 +278,6 @@ public boolean isThreadSafe() { private @NotNull CheckResult checkShouldBan(@NotNull Torrent torrent, @NotNull Peer peer, @NotNull Downloader downloader, @NotNull ExecutorService ruleExecuteExecutor) { var abilityObject = manager.getAbilities().get(BtnAbilityRules.class); - ; if (abilityObject == null) { return pass(); } diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ExpressionRule.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ExpressionRule.java index b5c4aee39e..91c8bdc51a 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ExpressionRule.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ExpressionRule.java @@ -1,37 +1,26 @@ package com.ghostchu.peerbanhelper.module.impl.rule; import com.ghostchu.peerbanhelper.Main; -import com.ghostchu.peerbanhelper.PeerBanHelperServer; import com.ghostchu.peerbanhelper.downloader.Downloader; import com.ghostchu.peerbanhelper.module.AbstractRuleFeatureModule; import com.ghostchu.peerbanhelper.module.CheckResult; import com.ghostchu.peerbanhelper.module.PeerAction; import com.ghostchu.peerbanhelper.peer.Peer; +import com.ghostchu.peerbanhelper.scriptengine.CompiledScript; +import com.ghostchu.peerbanhelper.scriptengine.ScriptEngine; import com.ghostchu.peerbanhelper.text.Lang; -import com.ghostchu.peerbanhelper.text.TranslationComponent; import com.ghostchu.peerbanhelper.torrent.Torrent; -import com.ghostchu.peerbanhelper.util.HTTPUtil; -import com.ghostchu.peerbanhelper.util.IPAddressUtil; -import com.ghostchu.peerbanhelper.util.StrUtil; import com.ghostchu.peerbanhelper.util.context.IgnoreScan; -import com.ghostchu.peerbanhelper.util.json.JsonUtil; -import com.ghostchu.peerbanhelper.util.time.InfoHashUtil; import com.ghostchu.peerbanhelper.web.JavalinWebContainer; import com.ghostchu.peerbanhelper.web.wrapper.StdResp; import com.ghostchu.simplereloadlib.ReloadResult; import com.ghostchu.simplereloadlib.Reloadable; -import com.googlecode.aviator.AviatorEvaluator; -import com.googlecode.aviator.EvalMode; -import com.googlecode.aviator.Expression; -import com.googlecode.aviator.Options; import com.googlecode.aviator.exception.ExpressionSyntaxErrorException; import com.googlecode.aviator.exception.TimeoutException; -import com.googlecode.aviator.runtime.JavaMethodReflectionFunctionMissing; import io.javalin.http.Context; import io.javalin.http.HttpStatus; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.springframework.core.io.Resource; @@ -42,16 +31,11 @@ import java.io.File; import java.io.IOException; import java.io.StringReader; -import java.math.MathContext; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; @@ -66,11 +50,13 @@ public class ExpressionRule extends AbstractRuleFeatureModule implements Reloada private final static String VERSION = "2"; private final long maxScriptExecuteTime = 1500; private final JavalinWebContainer javalinWebContainer; - private Map expressions = new HashMap<>(); + private final ScriptEngine scriptEngine; + private final List scripts = Collections.synchronizedList(new LinkedList<>()); private long banDuration; - public ExpressionRule(JavalinWebContainer javalinWebContainer) { + public ExpressionRule(JavalinWebContainer javalinWebContainer, ScriptEngine scriptEngine) { super(); + this.scriptEngine = scriptEngine; this.javalinWebContainer = javalinWebContainer; } @@ -78,31 +64,6 @@ public ExpressionRule(JavalinWebContainer javalinWebContainer) { @Override public void onEnable() { // 默认启用脚本编译缓存 - AviatorEvaluator.getInstance().setCachedExpressionByDefault(true); - // ASM 性能优先 - AviatorEvaluator.getInstance().setOption(Options.EVAL_MODE, EvalMode.ASM); - // EVAL 性能优先 - AviatorEvaluator.getInstance().setOption(Options.OPTIMIZE_LEVEL, AviatorEvaluator.EVAL); - // 降低浮点计算精度 - AviatorEvaluator.getInstance().setOption(Options.MATH_CONTEXT, MathContext.DECIMAL32); - // 启用变量语法糖 - AviatorEvaluator.getInstance().setOption(Options.ENABLE_PROPERTY_SYNTAX_SUGAR, true); -// // 表达式允许序列化和反序列化 -// AviatorEvaluator.getInstance().setOption(Options.SERIALIZABLE, true); - // 用户规则写糊保护 - AviatorEvaluator.getInstance().setOption(Options.MAX_LOOP_COUNT, 5000); - AviatorEvaluator.getInstance().setOption(Options.EVAL_TIMEOUT_MS, maxScriptExecuteTime); - // 启用反射方法查找 - AviatorEvaluator.getInstance().setFunctionMissing(JavaMethodReflectionFunctionMissing.getInstance()); - // 注册反射调用 - registerFunctions(IPAddressUtil.class); - registerFunctions(HTTPUtil.class); - registerFunctions(JsonUtil.class); - registerFunctions(Lang.class); - registerFunctions(StrUtil.class); - registerFunctions(PeerBanHelperServer.class); - registerFunctions(InfoHashUtil.class); - registerFunctions(Main.class); try { reloadConfig(); } catch (Exception e) { @@ -191,15 +152,14 @@ private boolean insideDirectory(File allowRange, File targetFile) { private void listScripts(Context context) { List list = new ArrayList<>(); - for (Map.Entry entry : expressions.entrySet()) { - var metadata = entry.getValue(); + for (var script : scripts) { list.add(new ExpressionMetadataDto( - metadata.file().getName(), - metadata.name(), - metadata.author(), - metadata.cacheable(), - metadata.threadSafe(), - metadata.version() + script.file().getName(), + script.name(), + script.author(), + script.cacheable(), + script.threadSafe(), + script.version() )); } context.json(new StdResp(true, null, list)); @@ -211,59 +171,6 @@ public boolean isConfigurable() { return true; } - @Nullable - public CheckResult handleResult(Expression expression, Object returns) { - ExpressionMetadata meta = expressions.get(expression); - if (returns instanceof Boolean status) { - if (status) { - return new CheckResult(getClass(), PeerAction.BAN, banDuration, - new TranslationComponent(Lang.USER_SCRIPT_RULE), - new TranslationComponent(Lang.USER_SCRIPT_RUN_RESULT, meta.name(), "true")); - } - return null; - } - if (returns instanceof Number number) { - int i = number.intValue(); - if (i == 0) { - return null; - } else if (i == 1) { - return new CheckResult(getClass(), PeerAction.BAN, banDuration, - new TranslationComponent(Lang.USER_SCRIPT_RULE), - new TranslationComponent(Lang.USER_SCRIPT_RUN_RESULT, meta.name(), String.valueOf(number))); - } else if (i == 2) { - return new CheckResult(getClass(), PeerAction.SKIP, banDuration, - new TranslationComponent(Lang.USER_SCRIPT_RULE), - new TranslationComponent(Lang.USER_SCRIPT_RUN_RESULT, meta.name(), String.valueOf(number))); - } else { - log.error(tlUI(Lang.MODULE_EXPRESSION_RULE_INVALID_RETURNS, meta)); - return null; - } - } - if (returns instanceof PeerAction action) { - return new CheckResult(getClass(), action, banDuration, - new TranslationComponent(Lang.USER_SCRIPT_RULE), - new TranslationComponent(Lang.USER_SCRIPT_RUN_RESULT, meta.name(), action.name())); - } - if (returns instanceof String string) { - if (string.isBlank()) { - return pass(); - } else if (string.startsWith("@")) { - return new CheckResult(getClass(), PeerAction.SKIP, banDuration, - new TranslationComponent(Lang.USER_SCRIPT_RULE), - new TranslationComponent(string.substring(1))); - } else { - return new CheckResult(getClass(), PeerAction.BAN, banDuration, - new TranslationComponent(Lang.USER_SCRIPT_RULE), - new TranslationComponent(Lang.USER_SCRIPT_RUN_RESULT, meta.name(), string)); - } - } - if (returns instanceof CheckResult checkResult) { - return checkResult; - } - log.error(tlUI(Lang.MODULE_EXPRESSION_RULE_INVALID_RETURNS, meta)); - return null; - } - @Override public boolean isThreadSafe() { return true; @@ -279,27 +186,14 @@ public boolean isThreadSafe() { return "expression-engine"; } - private void registerFunctions(Class clazz) { - try { - AviatorEvaluator.addInstanceFunctions(StringUtils.uncapitalize(clazz.getSimpleName()), clazz); - } catch (IllegalAccessException | NoSuchMethodException e) { - log.error("Internal error: failed on register instance functions: {}", clazz.getName(), e); - } - try { - AviatorEvaluator.addStaticFunctions(StringUtils.capitalize(clazz.getSimpleName()), clazz); - } catch (IllegalAccessException | NoSuchMethodException e) { - log.error("Internal error: failed on register static functions: {}", clazz.getName(), e); - } - } - @SneakyThrows @Override public @NotNull CheckResult shouldBanPeer(@NotNull Torrent torrent, @NotNull Peer peer, @NotNull Downloader downloader, @NotNull ExecutorService ruleExecuteExecutor) { AtomicReference checkResult = new AtomicReference<>(pass()); try (ExecutorService exec = Executors.newVirtualThreadPerTaskExecutor()) { - for (Expression expression : expressions.keySet()) { + for (var compiledScript : scripts) { exec.submit(() -> { - CheckResult expressionRun = runExpression(expression, torrent, peer, downloader, ruleExecuteExecutor); + CheckResult expressionRun = runExpression(compiledScript, torrent, peer, downloader, ruleExecuteExecutor); if (expressionRun.action() == PeerAction.SKIP) { checkResult.set(expressionRun); // 提前退出 return; @@ -315,12 +209,11 @@ private void registerFunctions(Class clazz) { return checkResult.get(); } - public CheckResult runExpression(Expression expression, @NotNull Torrent torrent, @NotNull Peer peer, @NotNull Downloader downloader, @NotNull ExecutorService ruleExecuteExecutor) { - ExpressionMetadata expressionMetadata = expressions.get(expression); - return getCache().readCacheButWritePassOnly(this, expression.hashCode() + peer.getCacheKey(), () -> { + public CheckResult runExpression(CompiledScript script, @NotNull Torrent torrent, @NotNull Peer peer, @NotNull Downloader downloader, @NotNull ExecutorService ruleExecuteExecutor) { + return getCache().readCacheButWritePassOnly(this, script.expression().hashCode() + peer.getCacheKey(), () -> { CheckResult result; try { - Map env = expression.newEnv(); + Map env = script.expression().newEnv(); env.put("torrent", torrent); env.put("peer", peer); env.put("downloader", downloader); @@ -328,19 +221,18 @@ public CheckResult runExpression(Expression expression, @NotNull Torrent torrent env.put("server", getServer()); env.put("moduleInstance", this); Object returns; - if (expressionMetadata.threadSafe()) { - returns = expression.execute(env); + if (script.threadSafe()) { + returns = script.expression().execute(env); } else { - synchronized (expressionMetadata) { - returns = expression.execute(env); + synchronized (script.expression()) { + returns = script.expression().execute(env); } } - result = handleResult(expression, returns); + result = scriptEngine.handleResult(script,banDuration, returns); } catch (TimeoutException timeoutException) { - log.error(tlUI(Lang.MODULE_EXPRESSION_RULE_TIMEOUT, maxScriptExecuteTime), timeoutException); return pass(); } catch (Exception ex) { - log.error(tlUI(Lang.MODULE_EXPRESSION_RULE_ERROR, expressionMetadata.name()), ex); + log.error(tlUI(Lang.RULE_ENGINE_ERROR, script.name()), ex); return pass(); } if (result != null && result.action() != PeerAction.NO_ACTION) { @@ -348,7 +240,7 @@ public CheckResult runExpression(Expression expression, @NotNull Torrent torrent } else { return pass(); } - }, expressionMetadata.cacheable()); + }, script.cacheable()); } @Override @@ -363,12 +255,11 @@ public ReloadResult reloadModule() throws Exception { } private void reloadConfig() throws IOException { - expressions.clear(); + scripts.clear(); this.banDuration = getConfig().getLong("ban-duration", 0); initScripts(); - log.info(tlUI(Lang.MODULE_EXPRESSION_RULE_COMPILING)); + log.info(tlUI(Lang.RULE_ENGINE_COMPILING)); long start = System.currentTimeMillis(); - Map userRules = new ConcurrentHashMap<>(); try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { File scriptDir = new File(Main.getDataDirectory(), "scripts"); File[] scripts = scriptDir.listFiles(); @@ -381,24 +272,21 @@ private void reloadConfig() throws IOException { } try { String scriptContent = java.nio.file.Files.readString(script.toPath(), StandardCharsets.UTF_8); - ExpressionMetadata expressionMetadata = parseScriptMetadata(script, script.getName(), scriptContent); - AviatorEvaluator.getInstance().validate(expressionMetadata.script()); - Expression expression = AviatorEvaluator.getInstance().compile(expressionMetadata.script(), false); - expression.newEnv("peerbanhelper", getServer(), "moduleConfig", getConfig(), "ipdb", getServer().getIpdb()); - userRules.put(expression, expressionMetadata); + var compiledScript = scriptEngine.compileScript(script,script.getName(),scriptContent); + if(compiledScript == null) return; + this.scripts.add(compiledScript); } catch (IOException e) { log.error("Unable to load script file", e); } } catch (ExpressionSyntaxErrorException err) { - log.error(tlUI(Lang.MODULE_EXPRESSION_RULE_BAD_EXPRESSION), err); + log.error(tlUI(Lang.RULE_ENGINE_BAD_EXPRESSION), err); } }); } } } - expressions = new HashMap<>(userRules); getCache().invalidateAll(); - log.info(tlUI(Lang.MODULE_EXPRESSION_RULE_COMPILED, expressions.size(), System.currentTimeMillis() - start)); + log.info(tlUI(Lang.RULE_ENGINE_COMPILED, scripts.size(), System.currentTimeMillis() - start)); } private ExpressionMetadata parseScriptMetadata(File file, String fallbackName, String scriptContent) { diff --git a/src/main/java/com/ghostchu/peerbanhelper/scriptengine/CompiledScript.java b/src/main/java/com/ghostchu/peerbanhelper/scriptengine/CompiledScript.java new file mode 100644 index 0000000000..919d98f73e --- /dev/null +++ b/src/main/java/com/ghostchu/peerbanhelper/scriptengine/CompiledScript.java @@ -0,0 +1,10 @@ +package com.ghostchu.peerbanhelper.scriptengine; + +import com.googlecode.aviator.Expression; + +import java.io.File; + +public record CompiledScript(File file, String name, String author, boolean cacheable, boolean threadSafe, + String version, String script, Expression expression) { + +} diff --git a/src/main/java/com/ghostchu/peerbanhelper/scriptengine/ScriptEngine.java b/src/main/java/com/ghostchu/peerbanhelper/scriptengine/ScriptEngine.java new file mode 100644 index 0000000000..86f3803e55 --- /dev/null +++ b/src/main/java/com/ghostchu/peerbanhelper/scriptengine/ScriptEngine.java @@ -0,0 +1,157 @@ +package com.ghostchu.peerbanhelper.scriptengine; + +import com.ghostchu.peerbanhelper.module.AbstractRuleFeatureModule; +import com.ghostchu.peerbanhelper.module.CheckResult; +import com.ghostchu.peerbanhelper.module.PeerAction; +import com.ghostchu.peerbanhelper.text.Lang; +import com.ghostchu.peerbanhelper.text.TranslationComponent; +import com.googlecode.aviator.AviatorEvaluator; +import com.googlecode.aviator.Expression; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.Nullable; +import org.springframework.stereotype.Component; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; + +import static com.ghostchu.peerbanhelper.text.TextManager.tlUI; + +@Component +@Slf4j +public class ScriptEngine { + public static final CheckResult OK_CHECK_RESULT = new CheckResult(AbstractRuleFeatureModule.class, PeerAction.NO_ACTION, 0, new TranslationComponent("N/A"), new TranslationComponent("Check passed")); +// +// public CheckResult runExpression(CompiledScript script, @NotNull Torrent torrent, @NotNull Peer peer, @NotNull Downloader downloader, @NotNull ExecutorService ruleExecuteExecutor) { +// CheckResult result; +// try { +// Map env = script.expression().newEnv(); +// env.put("torrent", torrent); +// env.put("peer", peer); +// env.put("downloader", downloader); +// env.put("cacheable", new AtomicBoolean(false)); +// env.put("server", peerBanHelperServer); +// env.put("moduleInstance", this); +// Object returns; +// if (script.threadSafe()) { +// returns = script.expression().execute(env); +// } else { +// synchronized (script.expression()) { +// returns = script.expression().execute(env); +// } +// } +// result = handleResult(script.expression(), returns); +// } catch (TimeoutException timeoutException) { +// log.error(tlUI(Lang.MODULE_EXPRESSION_RULE_TIMEOUT, maxScriptExecuteTime), timeoutException); +// return pass(); +// } catch (Exception ex) { +// log.error(tlUI(Lang.MODULE_EXPRESSION_RULE_ERROR, expressionMetadata.name()), ex); +// return pass(); +// } +// if (result != null && result.action() != PeerAction.NO_ACTION) { +// return result; +// } else { +// return pass(); +// } +// } + + + @Nullable + public CheckResult handleResult(CompiledScript script, long banDuration, Object returns) { + if (returns instanceof Boolean status) { + if (status) { + return new CheckResult(getClass(), PeerAction.BAN, banDuration, + new TranslationComponent(Lang.USER_SCRIPT_RULE), + new TranslationComponent(Lang.USER_SCRIPT_RUN_RESULT, script.name(), "true")); + } + return null; + } + if (returns instanceof Number number) { + int i = number.intValue(); + if (i == 0) { + return null; + } else if (i == 1) { + return new CheckResult(getClass(), PeerAction.BAN, banDuration, + new TranslationComponent(Lang.USER_SCRIPT_RULE), + new TranslationComponent(Lang.USER_SCRIPT_RUN_RESULT, script.name(), String.valueOf(number))); + } else if (i == 2) { + return new CheckResult(getClass(), PeerAction.SKIP, banDuration, + new TranslationComponent(Lang.USER_SCRIPT_RULE), + new TranslationComponent(Lang.USER_SCRIPT_RUN_RESULT, script.name(), String.valueOf(number))); + } else { + log.error(tlUI(Lang.RULE_ENGINE_INVALID_RETURNS, script)); + return null; + } + } + if (returns instanceof PeerAction action) { + return new CheckResult(getClass(), action, banDuration, + new TranslationComponent(Lang.USER_SCRIPT_RULE), + new TranslationComponent(Lang.USER_SCRIPT_RUN_RESULT, script.name(), action.name())); + } + if (returns instanceof String string) { + if (string.isBlank()) { + return OK_CHECK_RESULT; + } else if (string.startsWith("@")) { + return new CheckResult(getClass(), PeerAction.SKIP, banDuration, + new TranslationComponent(Lang.USER_SCRIPT_RULE), + new TranslationComponent(string.substring(1))); + } else { + return new CheckResult(getClass(), PeerAction.BAN, banDuration, + new TranslationComponent(Lang.USER_SCRIPT_RULE), + new TranslationComponent(Lang.USER_SCRIPT_RUN_RESULT, script.name(), string)); + } + } + if (returns instanceof CheckResult checkResult) { + return checkResult; + } + log.error(tlUI(Lang.RULE_ENGINE_INVALID_RETURNS, script.name())); + return null; + } + + @Nullable + public CompiledScript compileScript(File file, String fallbackName, String scriptContent) { + try (BufferedReader reader = new BufferedReader(new StringReader(scriptContent))) { + String name = fallbackName; + String author = "Unknown"; + String version = "null"; + boolean cacheable = true; + boolean threadSafe = true; + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + if (line.startsWith("#")) { + line = line.substring(1).trim(); + if (line.startsWith("@NAME")) { + name = line.substring(5).trim(); + } else if (line.startsWith("@AUTHOR")) { + author = line.substring(7).trim(); + } else if (line.startsWith("@CACHEABLE")) { + cacheable = Boolean.parseBoolean(line.substring(10).trim()); + } else if (line.startsWith("@VERSION")) { + version = line.substring(8).trim(); + } else if (line.startsWith("@THREADSAFE")) { + threadSafe = Boolean.parseBoolean(line.substring(11).trim()); + } + } + } + AviatorEvaluator.getInstance().validate(scriptContent); + Expression expression = AviatorEvaluator.getInstance().compile(scriptContent, false); + return new CompiledScript( + file, + name, + author, + cacheable, + threadSafe, + version, + scriptContent, + expression + ); + } catch (Exception e) { + log.warn("Script Engine unable to compile the script: {}", fallbackName); + return null; + } + } +} diff --git a/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java b/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java index 8d70a098d7..ae502895a3 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java +++ b/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java @@ -200,13 +200,13 @@ public enum Lang { SUGGEST_FIREWALL_IPTABELS, SUGGEST_FIREWALL_FIREWALLD, SUGGEST_FIREWALL_WINDOWS_FIREWALL_DISABLED, - MODULE_EXPRESSION_RULE_BAD_EXPRESSION, - MODULE_EXPRESSION_RULE_COMPILING, - MODULE_EXPRESSION_RULE_COMPILED, - MODULE_EXPRESSION_RULE_INVALID_RETURNS, - MODULE_EXPRESSION_RULE_TIMEOUT, - MODULE_EXPRESSION_RULE_ERROR, - MODULE_EXPRESSION_RULE_RELEASE_FILE_FAILED, + RULE_ENGINE_BAD_EXPRESSION, + RULE_ENGINE_COMPILING, + RULE_ENGINE_COMPILED, + RULE_ENGINE_INVALID_RETURNS, + RULE_ENGINE_TIMEOUT, + RULE_ENGINE_ERROR, + RULE_ENGINE_RELEASE_FILE_FAILED, JFX_WEBVIEW_ALERT, DATABASE_OUTDATED_LOGS_CLEANED_UP, LIBRARIES_LOADER_DETERMINE_BEST_MIRROR, @@ -420,7 +420,7 @@ public enum Lang { MODULE_AMM_TRAFFIC_MONITORING_TRAFFIC_ALERT_DESCRIPTION, DOWNLOADER_ALERT_TOO_MANY_FAILED_ATTEMPT_TITLE, DOWNLOADER_ALERT_TOO_MANY_FAILED_ATTEMPT_DESCRIPTION, DOWNLOADER_ALERT_TOO_MANY_FAILED_ATTEMPT_DESCRIPTION_FALLBACK, PROGRAM_OUT_OF_MEMORY_TITLE, PROGRAM_OUT_OF_MEMORY_DESCRIPTION, BAN_PEER_EXCEPTION, - PUSH_PROVIDER_TEST_TITLE, PUSH_PROVIDER_TEST_DESCRIPTION, PUSH_PROVIDER_TEST_SUCCESS, PUSH_PROVIDER_TEST_FAILED, PUSH_PROVIDER_TEST_ERROR; + PUSH_PROVIDER_TEST_TITLE, PUSH_PROVIDER_TEST_DESCRIPTION, PUSH_PROVIDER_TEST_SUCCESS, PUSH_PROVIDER_TEST_FAILED, PUSH_PROVIDER_TEST_ERROR, BTN_RULES_SCRIPT_COMPILING, BTN_RULES_SCRIPT_COMPILED; public String getKey() { return name(); diff --git a/src/main/resources/lang/en_us/messages.yml b/src/main/resources/lang/en_us/messages.yml index 095270756a..44194e4be0 100644 --- a/src/main/resources/lang/en_us/messages.yml +++ b/src/main/resources/lang/en_us/messages.yml @@ -201,12 +201,12 @@ STATUS_TEXT_NEED_PRIVILEGE: "Insufficient privileges, request privilege escalati SUGGEST_FIREWALL_IPTABLES: "Native iptables are not recommended, may cause network performance degradation. Consider installing ipset instead of using iptables" SUGGEST_FIREWALL_FIREWALLD: "Native firewalld is not recommended, may cause network performance degradation. Consider installing ipset instead of using firewalld" SUGGEST_FIREWALL_WINDOWS_FIREWALL_DISABLED: "Windows Firewall is currently disabled, please enable Windows Firewall for 'Public Network' and 'Private Network' to ensure system firewall integration works" -MODULE_EXPRESSION_RULE_BAD_EXPRESSION: "Error parsing expression, please check for syntax errors" -MODULE_EXPRESSION_RULE_COMPILING: "Please wait, the rule engine is compiling user scripts to enhance execution performance, this may take some time..." -MODULE_EXPRESSION_RULE_COMPILED: "Successfully compiled {} user scripts, took {}ms" -MODULE_EXPRESSION_RULE_TIMEOUT: "User script {} execution timed out, maximum allowed time is {}ms" -MODULE_EXPRESSION_RULE_ERROR: "Error executing user script {}" -MODULE_EXPRESSION_RULE_RELEASE_FILE_FAILED: "[Error] Encountered system error while releasing preset script file {}" +RULE_ENGINE_BAD_EXPRESSION: "Error parsing expression, please check for syntax errors" +RULE_ENGINE_COMPILING: "Please wait, the rule engine is compiling scripts to enhance execution performance, this may take some time..." +RULE_ENGINE_COMPILED: "Successfully compiled {} scripts, took {}ms" +RULE_ENGINE_TIMEOUT: "Script {} execution timed out, maximum allowed time is {}ms" +RULE_ENGINE_ERROR: "Error executing script {}" +RULE_ENGINE_RELEASE_FILE_FAILED: "[Error] Encountered system error while releasing preset script file {}" JFX_WEBVIEW_ALERT: "Message from webpage" DATABASE_OUTDATED_LOGS_CLEANED_UP: "Cleaned up {} expired ban log entries from the database" LIBRARIES_LOADER_DETERMINE_BEST_MIRROR: "Please wait, initializing and testing the best download source (up to 15 seconds)..." @@ -263,8 +263,8 @@ RULE_ENGINE_INVALID_RULE: "Invalid parameter {} for rule {}, only accepts the fo RULE_ENGINE_NOT_A_RULE: "[Rule Engine] Expression {} is not a valid rule" RULE_MATCHER_STRING_EQUALS: "Matches the same" NEW_SETUP_NO_DOWNLOADERS: "PeerBanHelper is currently not connected to any downloaders! Please open the WebUI and add downloaders. Token for login: {} (can be copied from the GUI menu at any time, or found in config.yml)" -MODULE_EXPRESSION_RULE_INVALID_RETURNS: | - User script {} returned an invalid value. Valid return types are: +RULE_ENGINE_INVALID_RETURNS: | + Script {} returned an invalid value. Valid return types are: Boolean: [false=do nothing, true=ban peer] Integer: [0=do nothing, 1=ban peer, 2=skip other rules] com.ghostchu.peerbanhelper.module.PeerAction: [NO_ACTION, BAN, SKIP] @@ -308,8 +308,8 @@ DOWNLOADER_LOGIN_EXCEPTION: "Unable connect to downloader: {}" DOWNLOADER_LOGIN_IO_EXCEPTION: "Unable connect to downloader, network error: {}" DOWNLOADER_LOGIN_INCORRECT_CRED: "Unable to authenticate to downloader, check the username, password or token." -USER_SCRIPT_RULE: "User Aviator Script Custom Rule" -USER_SCRIPT_RUN_RESULT: "UserScript {}: {}" +USER_SCRIPT_RULE: "Script Engine" +USER_SCRIPT_RUN_RESULT: "Script {}: {}" SCHEDULED_OPERATIONS: "[Scheduled Tasks] Processed {} ban list external changes" ARB_BANNED_REASON: "IP address {} is in the same ban range as another banned IP address {}, performing chain ban operation: {}@{}" diff --git a/src/main/resources/lang/messages_fallback.yml b/src/main/resources/lang/messages_fallback.yml index 0eb461063b..1bba14462a 100644 --- a/src/main/resources/lang/messages_fallback.yml +++ b/src/main/resources/lang/messages_fallback.yml @@ -201,12 +201,12 @@ STATUS_TEXT_NEED_PRIVILEGE: "权限不足,请求权限提升(以管理员/ro SUGGEST_FIREWALL_IPTABELS: "不推荐使用原生 iptables,可能引起网络性能下降。请考虑安装 ipset 代替使用 iptabels" SUGGEST_FIREWALL_FIREWALLD: "不推荐使用原生 firewalld,可能引起网络性能下降。请考虑安装 ipset 代替使用 firewalld" SUGGEST_FIREWALL_WINDOWS_FIREWALL_DISABLED: "Windows 防火墙目前处于禁用状态,请为 “公用网络” 和 “专用网络” 打开 Windows 防火墙,否则系统防火墙集成将不起作用" -MODULE_EXPRESSION_RULE_BAD_EXPRESSION: "解析表达式时出错,请检查是否有语法错误" -MODULE_EXPRESSION_RULE_COMPILING: "请稍等,规则引擎正在编译用户脚本以提高执行性能,这可能需要一点时间……" -MODULE_EXPRESSION_RULE_COMPILED: "已成功编译 {} 条用户脚本,耗时 {}ms" -MODULE_EXPRESSION_RULE_TIMEOUT: "用户脚本 {} 执行超时,最大允许时间是 {}ms" -MODULE_EXPRESSION_RULE_ERROR: "执行用户脚本 {} 时出错" -MODULE_EXPRESSION_RULE_RELEASE_FILE_FAILED: "[错误] 在释放预设脚本文件 {} 时遇到了系统错误" +RULE_ENGINE_BAD_EXPRESSION: "解析表达式时出错,请检查是否有语法错误" +RULE_ENGINE_COMPILING: "请稍等,规则引擎正在编译用户脚本以提高执行性能,这可能需要一点时间……" +RULE_ENGINE_COMPILED: "已成功编译 {} 条用户脚本,耗时 {}ms" +RULE_ENGINE_TIMEOUT: "用户脚本 {} 执行超时,最大允许时间是 {}ms" +RULE_ENGINE_ERROR: "执行用户脚本 {} 时出错" +RULE_ENGINE_RELEASE_FILE_FAILED: "[错误] 在释放预设脚本文件 {} 时遇到了系统错误" JFX_WEBVIEW_ALERT: "来自网页的消息" DATABASE_OUTDATED_LOGS_CLEANED_UP: "已清理数据库中 {} 条过期封禁日志数据" LIBRARIES_LOADER_DETERMINE_BEST_MIRROR: "请稍等,正在初始化并测试最佳下载源(最多 15 秒)……" @@ -263,8 +263,8 @@ RULE_ENGINE_INVALID_RULE: "规则 {} 的参数 {} 无效,仅接受以下值: RULE_ENGINE_NOT_A_RULE: "[规则引擎] 表达式 {} 不是一个有效规则" RULE_MATCHER_STRING_EQUALS: "匹配相同" NEW_SETUP_NO_DOWNLOADERS: "PeerBanHelper 现在还未连接到任何下载器!请打开 WebUI 并添加下载器。如需登录 Token:{} (可随时从 GUI 菜单复制,或者从 config.yml 找到)" -MODULE_EXPRESSION_RULE_INVALID_RETURNS: | - 用户脚本 {} 返回了无效值,返回的值必须是以下类型中的其一: +RULE_ENGINE_INVALID_RETURNS: | + 脚本 {} 返回了无效值,返回的值必须是以下类型中的其一: Boolean: [false=不采取任何操作, true=封禁Peer] Integer: [0=不采取任何操作,1=封禁Peer,2=跳过其它规则] com.ghostchu.peerbanhelper.module.PeerAction: [NO_ACTION, BAN, SKIP] @@ -308,8 +308,8 @@ DOWNLOADER_LOGIN_EXCEPTION: "无法连接到下载器,登录时出现错误: DOWNLOADER_LOGIN_IO_EXCEPTION: "无法连接到下载器,登录时出现网络错误:{}" DOWNLOADER_LOGIN_INCORRECT_CRED: "鉴权失败,错误的登录凭据。请检查用户名、密码或 Token 是否正确。" -USER_SCRIPT_RULE: "用户 Aviator Script 自定脚本规则" -USER_SCRIPT_RUN_RESULT: "用户脚本 {}: {}" +USER_SCRIPT_RULE: "脚本引擎" +USER_SCRIPT_RUN_RESULT: "脚本 {}: {}" SCHEDULED_OPERATIONS: "[计划任务] 已处理 {} 个封禁列表的外部计划更改" ARB_BANNED_REASON: "IP 地址 {} 与另一个已封禁的 IP 地址 {} 处于同一封禁区间内,执行连锁封禁操作:{}@{}" @@ -477,3 +477,6 @@ PUSH_PROVIDER_TEST_SUCCESS: "{} ({}) 发送成功,请检查是否收到消息" PUSH_PROVIDER_TEST_FAILED: "测试推送服务 {} ({}) 失败" PUSH_PROVIDER_TEST_ERROR: "测试推送服务 {} ({}) 失败: {}" BAN_PEER_EXCEPTION: "封禁 Peer 时出现意外错误,指定 Peer 可能封禁失败,请将下面的信息报告给 PeerBanHelper 开发者" + +BTN_RULES_SCRIPT_COMPILING: "正在编译来自 BTN 服务器的可编程脚本,请稍等,这可能需要一点时间……" +BTN_RULES_SCRIPT_COMPILED: "已成功编译 {} 个脚本,用时 {}ms" \ No newline at end of file