Repository: PBH-BTN/PeerBanHelper
Pull request #1065: v7.4.6
Author: login: Ghost-chu
Created: 2025-04-04T19:55:10Z | Merged: 2025-04-05T14:44:53Z

## 新增功能

重新添加 Transmission 兼容支持，最低版本要求为 4.1.0-beta2（注：不支持任何低于此版本的过往版本 Transmission，没有例外） @Ghost-chu 

注意：由于 API 缺失，部分功能可能完全不工作或者无法可靠工作，以下是不稳定或不可用的功能：

* PeerID 过滤器（几乎不可用） - Transmission 的客户端名称接口行为不稳定 https://github.com/transmission/transmission/pull/7514
* ClientName 过滤器（有时不好用） - Transmission 的客户端名称接口行为不稳定 https://github.com/transmission/transmission/pull/7514
* 进度作弊检查（部分不可用） - Transmission 无法提供 Peer 的累计传输量信息 https://github.com/transmission/transmission/pull/7172
  * 进度差异检测可用
  * 进度回退检测可用
  * 禁止过量下载不可用，需要累计传输量信息
  * 快速 PCB 测试可用
* 脚本引擎 - 因 API 缺失，部分脚本可能不能工作
* 主动检测（流量告警不可用） - Transmission 没有全局流量统计

一旦这些更改被正式合并到 Transmission，我们会再次更新 PeerBanHelper 以适应这些更改并将这些功能带回。  
我们也会持续跟进 Transmission 的版本更新。

这些功能已经测试并且稳定可用：

* IP 过滤器（包括手动 IP 屏蔽、ASN/GeoIP 屏蔽、IP 订阅规则、BTN IP 规则）
* 连锁封禁
* 多拨封禁
* DNS 反向解析封禁

## 错误修复

1. 修复在某些情况下，对于国家/地区的文本处理不正确的情况 @Ghost-chu @Gaojianli 
2. 修复 DEB 包无法开箱即用 @Anuskuss @Ghost-chu 

## 已知问题

这些问题我们已经知道，但尚未在当前版本中得到修复。我们会在未来的更新中继续关注它们：

1. 当 PeerBanHelper 重启时，浏览器中如仍有未关闭的 WebUI 页面，则可能导致触发暴力破解防护导致 IP 被暂时封禁
2. 部分日志的行为未完全跟随参数改变

## Docker

DockerHub: `ghostchu/peerbanhelper:v7.4.6`  
阿里云国内镜像加速: `registry.cn-hangzhou.aliyuncs.com/ghostchu/peerbanhelper:v7.4.6`

---

[部署教程](https://docs.pbh-btn.com/docs/category/%E5%AE%89%E8%A3%85%E9%83%A8%E7%BD%B2) | [常见问题](https://docs.pbh-btn.com/docs/faq) | [如何设置下载器](https://docs.pbh-btn.com/docs/category/%E4%B8%8B%E8%BD%BD%E5%99%A8%E9%85%8D%E7%BD%AE)


Diff:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index 1b41b922e6..0000000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-# To get started with Dependabot version updates, you'll need to specify which
-# package ecosystems to update and where the package manifests are located.
-# Please see the documentation for all configuration options:
-# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
-
-version: 2
-updates:
-  - package-ecosystem: "maven" # See documentation for possible values
-    open-pull-requests-limit: 100
-    directory: "/" # Location of package manifests
-    schedule:
-      interval: "weekly"
diff --git a/README.EN.md b/README.EN.md
index 5f1136352a..f6755c0fdb 100644
--- a/README.EN.md
+++ b/README.EN.md
@@ -33,12 +33,11 @@ Additionally, PeerBanHelper downloads the GeoIP database on startup. Once loaded
 > [!CAUTION]
 > All Docker-based downloaders must use host network driver. PBH requires real peer IPs - bridge mode is unsupported!
 
-- qBittorrent/qBittorrent **v4.5.0+** (XDown not supported)
+- qBittorrent/qBittorrent **v4.5.0 or newer** (XDown not supported)
 - BiglyBT (requires [plugin](https://github.com/PBH-BTN/PBH-Adapter-BiglyBT))
 - Deluge (requires [plugin](https://github.com/PBH-BTN/PBH-Adapter-Deluge))
-- Azureus(Vuze) (requires [plugin](https://github.com/PBH-BTN/PBH-Adapter-Azureus))
-- Transmission **(Not recommended; v3.00-20+ required)**
-- BitComet **v2.10 Beta6 [20240928]+** (LTSeed anti-leech unsupported due to BitComet's limitation)
+- Transmission **(v4.1.0-beta2 or newer required)**
+- BitComet **v2.10 Beta6 [20240928] or newer** (LTSeed anti-leech unsupported due to BitComet's limitation)
 
 PeerBanHelper only supports traditional IPv4/IPv6 anti-leech. I2P/Tor connections are ignored.
 
diff --git a/README.md b/README.md
index 62115405fe..7bfa453546 100644
--- a/README.md
+++ b/README.md
@@ -34,11 +34,10 @@ PeerBanHelper 主要由以下几个功能模块组成：
 > [!CAUTION]
 > 所有安装在 Docker 中的下载器，必须使用 host 网络驱动程序。PBH 需要下载器能获得 Peer 的真实 IP，不能使用 bridge 桥接模式！
 
-- qBittorrent/qBitorrent，**4.5.0 或更高版本**，不支持 XDown，
+- qBittorrent/qBitorrent，**4.5.0 或更高版本**，不支持 XDown
 - BiglyBT（需要安装[插件](https://github.com/PBH-BTN/PBH-Adapter-BiglyBT)）
 - Deluge（需要安装[插件](https://github.com/PBH-BTN/PBH-Adapter-Deluge)）
-- Azureus(Vuze)（需要安装[插件](https://github.com/PBH-BTN/PBH-Adapter-Azureus)）
-- Transmission **(不建议使用；3.00-20 或更高版本)**
+- Transmission **(4.1.0-beta2 或更高版本)**
 - BitComet **v2.10 Beta6 [20240928] 或更高版本** (不支持 P2SP LTSeed 长效种子反吸血，因为 BitComet 暂时无法封禁长效连接)
 
 PeerBanHelper 仅支持对传统 IPv4 或 IPv6 地址的反吸血，如遇 I2P 或者 Tor 连接将主动忽略。
diff --git a/pkg/deb/usr/lib/systemd/system/peerbanhelper.service b/pkg/deb/usr/lib/systemd/system/peerbanhelper.service
index 460300217b..e027c6367e 100644
--- a/pkg/deb/usr/lib/systemd/system/peerbanhelper.service
+++ b/pkg/deb/usr/lib/systemd/system/peerbanhelper.service
@@ -5,7 +5,7 @@ After=network.target
 [Service]
 User=peerbanhelper
 WorkingDirectory=/usr/lib/peerbanhelper
-ExecStart=/usr/bin/java -Dpbh.release=debian -Dpbh.datadir=/var/lib/peerbanhelper -Dpbh.configdir=/etc/peerbanhelper -Dpbh.logsdir=/var/log/peerbanhelper -Dpbh.log.level=WARN -Xmx512M -Xms16M -Xss512k -XX:+UseG1GC --enable-native-access=ALL-UNNAMED -XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders -XX:+UseStringDeduplication -XX:+ShrinkHeapInSteps -jar PeerBanHelper.jar
+ExecStart=/usr/bin/java -Dpbh.release=debian -Dpbh.datadir=/var/lib/peerbanhelper -Dpbh.configdir=/etc/peerbanhelper -Dpbh.logsdir=/var/log/peerbanhelper -Dpbh.log.level=WARN -Xmx512M -Xms16M -Xss512k -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+ShrinkHeapInSteps -jar PeerBanHelper.jar
 SuccessExitStatus=143
 AmbientCapabilities=CAP_NET_ADMIN
 Restart=on-failure
diff --git a/pom.xml b/pom.xml
index 0f32f95915..75b9df0566 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
 
     <groupId>com.ghostchu.peerbanhelper</groupId>
     <artifactId>peerbanhelper</artifactId>
-    <version>7.4.5</version>
+    <version>7.4.6</version>
     <packaging>jar</packaging>
 
     <name>PeerBanHelper</name>
@@ -262,7 +262,6 @@
             <groupId>org.bspfsystems</groupId>
             <artifactId>yamlconfiguration</artifactId>
             <version>3.0.2</version>
-            <scope>compile</scope>
         </dependency>
         <dependency>
             <groupId>org.projectlombok</groupId>
diff --git a/src/main/java/com/ghostchu/peerbanhelper/PeerBanHelperServer.java b/src/main/java/com/ghostchu/peerbanhelper/PeerBanHelperServer.java
index f6680ac4c9..d5029463df 100644
--- a/src/main/java/com/ghostchu/peerbanhelper/PeerBanHelperServer.java
+++ b/src/main/java/com/ghostchu/peerbanhelper/PeerBanHelperServer.java
@@ -73,8 +73,8 @@
 import java.lang.management.ThreadInfo;
 import java.lang.management.ThreadMXBean;
 import java.sql.SQLException;
-import java.util.List;
 import java.util.*;
+import java.util.List;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.Lock;
@@ -618,8 +618,8 @@ public void banWave() {
                 }
             }
             if (!hideFinishLogs && !getDownloaders().isEmpty()) {
-                long downloadersCount = peers.keySet().size();
-                long torrentsCount = peers.values().stream().mapToLong(e -> e.keySet().size()).sum();
+                long downloadersCount = peers.size();
+                long torrentsCount = peers.values().stream().mapToLong(e -> e.size()).sum();
                 long peersCount = peers.values().stream().flatMap(e -> e.values().stream()).mapToLong(List::size).sum();
                 log.info(tlUI(Lang.BAN_WAVE_CHECK_COMPLETED, downloadersCount, torrentsCount, peersCount, bannedPeers.size(), unbannedPeers.size(), System.currentTimeMillis() - startTimer));
             }
diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/AbstractDownloader.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/AbstractDownloader.java
index 7470ba04cb..e2026a7b01 100644
--- a/src/main/java/com/ghostchu/peerbanhelper/downloader/AbstractDownloader.java
+++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/AbstractDownloader.java
@@ -13,7 +13,7 @@
 import java.util.List;
 
 public abstract class AbstractDownloader implements Downloader {
-    private final AlertManager alertManager;
+    public final AlertManager alertManager;
     protected String name;
     private DownloaderLastStatus lastStatus = DownloaderLastStatus.UNKNOWN;
     private TranslationComponent statusMessage;
diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/Transmission.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/Transmission.java
index faecffe7d1..33b60c5c7c 100644
--- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/Transmission.java
+++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/Transmission.java
@@ -1,6 +1,6 @@
 package com.ghostchu.peerbanhelper.downloader.impl.transmission;
 
-import com.ghostchu.peerbanhelper.ExternalSwitch;
+import com.ghostchu.peerbanhelper.alert.AlertLevel;
 import com.ghostchu.peerbanhelper.alert.AlertManager;
 import com.ghostchu.peerbanhelper.downloader.AbstractDownloader;
 import com.ghostchu.peerbanhelper.downloader.DownloaderLoginResult;
@@ -14,33 +14,25 @@
 import com.ghostchu.peerbanhelper.wrapper.BanMetadata;
 import com.ghostchu.peerbanhelper.wrapper.PeerAddress;
 import com.ghostchu.peerbanhelper.wrapper.TorrentWrapper;
-import com.google.common.primitives.Ints;
 import com.google.gson.JsonObject;
+import com.vdurmont.semver4j.Semver;
 import cordelia.client.TrClient;
 import cordelia.client.TypedResponse;
 import cordelia.rpc.*;
 import cordelia.rpc.types.Fields;
 import cordelia.rpc.types.Status;
-import cordelia.rpc.types.TorrentAction;
-import cordelia.rpc.types.Torrents;
 import lombok.Data;
-import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.SneakyThrows;
 import org.bspfsystems.yamlconfiguration.configuration.ConfigurationSection;
 import org.bspfsystems.yamlconfiguration.file.YamlConfiguration;
-import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 
 import java.net.http.HttpClient;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.StringJoiner;
-import java.util.concurrent.DelayQueue;
-import java.util.concurrent.Delayed;
-import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import static com.ghostchu.peerbanhelper.text.TextManager.tlUI;
@@ -50,19 +42,13 @@ public final class Transmission extends AbstractDownloader {
     private final TrClient client;
     private final String blocklistUrl;
     private final Config config;
-    private final DelayQueue<DelayedTorrents> pausedTorrents = new DelayQueue<>();
 
-    /*
-            API 受限，实际实现起来意义不大
-
-            */
     public Transmission(String name, String blocklistUrl, Config config, AlertManager alertManager) {
         super(name, alertManager);
         this.config = config;
         this.client = new TrClient(config.getEndpoint() + config.getRpcUrl(), config.getUsername(), config.getPassword(), config.isVerifySsl(), HttpClient.Version.valueOf(config.getHttpVersion()));
         this.blocklistUrl = blocklistUrl;
         log.warn(tlUI(Lang.DOWNLOADER_TR_MOTD_WARNING));
-        log.warn(tlUI(Lang.DOWNLOADER_TRANSMISSION_DISCOURAGE));
     }
 
     private static String generateBlocklistUrl(String pbhServerAddress) {
@@ -110,31 +96,52 @@ public void setPaused(boolean paused) {
         config.setPaused(paused);
     }
 
-    @SneakyThrows(InterruptedException.class)
     @Override
     public DownloaderLoginResult login0() {
         RqSessionGet get = new RqSessionGet();
         TypedResponse<RsSessionGet> resp = client.execute(get); // 执行任意 RPC 操作以刷新 session
         String version = resp.getArgs().getVersion();
-        if (version.startsWith("0.") || version.startsWith("1.") || version.startsWith("2.")) {
-            return new DownloaderLoginResult(DownloaderLoginResult.Status.EXCEPTION, new TranslationComponent(Lang.DOWNLOADER_TR_KNOWN_INCOMPATIBILITY, "API Version"));
+        if (version.length() > 5) {
+            version = version.substring(0, 5);
+        }
+        Semver semver = new Semver(version, Semver.SemverType.LOOSE);
+        // must 4.1.0 or higher
+        if (semver.getMajor() < 4) {
+            return new DownloaderLoginResult(DownloaderLoginResult.Status.EXCEPTION, new TranslationComponent(Lang.DOWNLOADER_TR_KNOWN_INCOMPATIBILITY, "Transmission version must be 4.1.0 or higher"));
+        }
+        if (semver.getMajor() == 4) {
+            if (semver.getMinor() < 1) {
+                return new DownloaderLoginResult(DownloaderLoginResult.Status.EXCEPTION, new TranslationComponent(Lang.DOWNLOADER_TR_KNOWN_INCOMPATIBILITY, "Transmission version must higher then (or equals to) 4.1.0"));
+            }
         }
         if (!resp.getArgs().getBlocklistEnabled() || !resp.getArgs().getBlocklistUrl().startsWith(blocklistUrl)) {
-            RqSessionSet set = RqSessionSet.builder()
-                    .blocklistUrl(blocklistUrl + "?t=" + System.currentTimeMillis()) // 更改 URL 来确保更改生效
-                    .blocklistEnabled(true)
-                    .build();
-            TypedResponse<RsSessionGet> sessionSetResp = client.execute(set);
-            if (!sessionSetResp.isSuccess()) {
-                log.error(tlUI(Lang.DOWNLOADER_TR_INCORRECT_BANLIST_API_RESP), sessionSetResp.getResult());
+            if (!setBlockListUrl(blocklistUrl)) {
+                return new DownloaderLoginResult(DownloaderLoginResult.Status.EXCEPTION, new TranslationComponent(Lang.DOWNLOADER_TR_INCORRECT_BANLIST_API_RESP, "Unable to set Transmission blocklist"));
+            }
+            if (!updateBlockList()) {
+                return new DownloaderLoginResult(DownloaderLoginResult.Status.REQUIRE_TAKE_ACTIONS, new TranslationComponent(Lang.DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED, getName()));
             }
-            Thread.sleep(3000);
         }
-        if (ExternalSwitch.parse("pbh.downloader.transmission.i-know-transmission-is-discourage-and-could-be-removed-in-future-please-dont-show-require-take-actions-status-text-and-i-know-what-i-am-doing") != null) {
-            return new DownloaderLoginResult(DownloaderLoginResult.Status.SUCCESS, new TranslationComponent(Lang.STATUS_TEXT_OK));
-        } else {
-            return new DownloaderLoginResult(DownloaderLoginResult.Status.REQUIRE_TAKE_ACTIONS, new TranslationComponent(Lang.DOWNLOADER_TRANSMISSION_DISCOURAGE));
+        return new DownloaderLoginResult(DownloaderLoginResult.Status.SUCCESS, new TranslationComponent(Lang.STATUS_TEXT_OK));
+    }
+
+    private boolean setBlockListUrl(String blocklistUrl) {
+        RqSessionSet set = RqSessionSet.builder()
+                .blocklistUrl(blocklistUrl + "?t=" + System.currentTimeMillis()) // 更改 URL 来确保更改生效
+                .blocklistEnabled(true)
+                .build();
+        TypedResponse<RsSessionGet> sessionSetResp = client.execute(set);
+        return sessionSetResp.isSuccess();
+    }
+
+    private boolean updateBlockList() {
+        RqBlockList updateBlockList = new RqBlockList();
+        TypedResponse<RsBlockList> updateBlockListResp = client.execute(updateBlockList);
+        if (!updateBlockListResp.isSuccess()) {
+            setBlockListUrl("http://peerbanhelper-blocklist-update-failed.com/check-peerbanhelper-webui-prefix-settings");
+            return false;
         }
+        return true;
     }
 
     @Override
@@ -148,7 +155,7 @@ public List<Torrent> getAllTorrents() {
     }
 
     public List<Torrent> fetchTorrents(boolean onlyActiveTorrent, boolean includePrivate) {
-        RqTorrentGet torrent = new RqTorrentGet(Fields.ID, Fields.HASH_STRING, Fields.NAME, Fields.PEERS_CONNECTED, Fields.STATUS, Fields.TOTAL_SIZE, Fields.PEERS, Fields.RATE_DOWNLOAD, Fields.RATE_UPLOAD, Fields.PEER_LIMIT, Fields.PERCENT_DONE, Fields.SIZE_WHEN_DONE, Fields.TRACKER_LIST, Fields.TRACKER_STATS);
+        RqTorrentGet torrent = new RqTorrentGet(Fields.ID, Fields.HASH_STRING, Fields.NAME, Fields.PEERS_CONNECTED, Fields.STATUS, Fields.TOTAL_SIZE, Fields.PEERS, Fields.RATE_DOWNLOAD, Fields.RATE_UPLOAD, Fields.PEER_LIMIT, Fields.PERCENT_DONE, Fields.SIZE_WHEN_DONE, Fields.TRACKER_LIST, Fields.TRACKER_STATS, Fields.IS_PRIVATE);
         TypedResponse<RsTorrentGet> rsp = client.execute(torrent);
         return rsp.getArgs().getTorrents().stream()
                 .filter(t -> {
@@ -184,7 +191,6 @@ public void setTrackers(Torrent torrent, List<Tracker> trackers) {
         client.execute(set);
     }
 
-
     @SneakyThrows
     @Override
     public void setBanList(Collection<PeerAddress> fullList, @Nullable Collection<BanMetadata> added, @Nullable Collection<BanMetadata> removed, boolean applyFullList) {
@@ -192,6 +198,11 @@ public void setBanList(Collection<PeerAddress> fullList, @Nullable Collection<Ba
         TypedResponse<RsBlockList> updateBlockListResp = client.execute(updateBlockList);
         if (!updateBlockListResp.isSuccess()) {
             log.error(tlUI(Lang.DOWNLOADER_TR_INCORRECT_SET_BANLIST_API_RESP));
+            alertManager.publishAlert(true, AlertLevel.WARN,
+                    "downloader-" + getEndpoint() + "-transmission-blocklist-update-error",
+                    new TranslationComponent(Lang.ALERT_DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED_TITLE, getName()),
+                    new TranslationComponent(Lang.ALERT_DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED_DESCRIPTION, getName(), blocklistUrl)
+            );
         } else {
             log.info(tlUI(Lang.DOWNLOADER_TR_UPDATED_BLOCKLIST), updateBlockListResp.getArgs().getBlockListSize());
         }
@@ -207,112 +218,26 @@ public DownloaderStatistics getStatistics() {
 
     @Override
     public void relaunchTorrentIfNeeded(Collection<Torrent> torrents) {
-        relaunchTorrents(torrents.stream()
-                .filter(t -> {
-                    try {
-                        Long.parseLong(t.getId());
-                        return true;
-                    } catch (Exception e) {
-                        return false;
-                    }
-                }).map(t -> Long.parseLong(t.getId())).toList());
-    }
 
-    private void relaunchTorrents(Collection<Long> ids) {
-        if (ids.isEmpty()) return;
-        RqTorrentGet torrentList = new RqTorrentGet(Fields.ID, Fields.HASH_STRING, Fields.NAME,
-                Fields.PEERS_CONNECTED,
-                Fields.STATUS, Fields.TOTAL_SIZE, Fields.PEERS, Fields.RATE_DOWNLOAD,
-                Fields.RATE_UPLOAD, Fields.PEER_LIMIT, Fields.PERCENT_DONE, Fields.IS_PRIVATE, Fields.SIZE_WHEN_DONE);
-        TypedResponse<RsTorrentGet> rsp = client.execute(torrentList);
-        List<Long> torrents = rsp.getArgs().getTorrents().stream()
-                .filter(t -> t.getStatus() != Status.STOPPED)
-                .map(Torrents::getId)
-                .filter(ids::contains).collect(Collectors.toList());
-        log.info(tlUI(Lang.DOWNLOADER_TR_DISCONNECT_PEERS, torrents.size()));
-        RqTorrent stop = new RqTorrent(TorrentAction.STOP, new ArrayList<>());
-        for (long torrent : torrents) {
-            stop.add(torrent);
-        }
-        pausedTorrents.add(new DelayedTorrents(torrents, 5000));
-        client.execute(stop);
-        unpauseTorrents(false);
     }
 
+
     @Override
     public void relaunchTorrentIfNeededByTorrentWrapper(Collection<TorrentWrapper> torrents) {
-        if (ExternalSwitch.parse("pbh.transmission.disable-torrent-relaunch") != null) {
-            return;
-        }
-        relaunchTorrents(torrents.stream().filter(t -> {
-            try {
-                Long.parseLong(t.getId());
-                return true;
-            } catch (Exception e) {
-                return false;
-            }
-        }).map(t -> Long.parseLong(t.getId())).toList());
-    }
 
-    private synchronized void unpauseTorrents(boolean includeUnexpired) {
-        List<DelayedTorrents> ids;
-        if (includeUnexpired) {
-            ids = new ArrayList<>(pausedTorrents);
-            pausedTorrents.clear();
-        } else {
-            ids = new ArrayList<>();
-            pausedTorrents.drainTo(ids);
-        }
-        if (ids.isEmpty()) {
-            return;
-        }
-        RqTorrent resume = new RqTorrent(TorrentAction.START, new ArrayList<>());
-        ids.stream().flatMap(d -> d.getTorrentIds().stream()).forEach(resume::add);
-        try {
-            var run = client.execute(resume);
-            if (!run.isSuccess()) {
-                pausedTorrents.addAll(ids);
-            }
-        } catch (Exception e) {
-            log.warn("Unable handle Transmission task restore, scheduled re-run in next schedule task window.");
-            pausedTorrents.addAll(ids);
-        }
     }
 
     @Override
     public void runScheduleTasks() {
-        unpauseTorrents(false);
+        // unpauseTorrents(false);
     }
 
     @Override
     public void close() {
-        unpauseTorrents(true);
+        //  unpauseTorrents(true);
         client.shutdown();
     }
 
-
-    public static class DelayedTorrents implements Delayed {
-        private final long start;
-        @Getter
-        private final List<Long> torrentIds;
-
-        public DelayedTorrents(List<Long> torrentIds, long delay) {
-            this.torrentIds = new ArrayList<>(torrentIds);
-            this.start = System.currentTimeMillis() + delay;
-        }
-
-        @Override
-        public long getDelay(@NotNull TimeUnit unit) {
-            long diff = start - System.currentTimeMillis();
-            return unit.convert(diff, TimeUnit.MILLISECONDS);
-        }
-
-        @Override
-        public int compareTo(@NotNull Delayed o) {
-            return Ints.saturatedCast(this.start - ((DelayedTorrents) o).start);
-        }
-    }
-
     @NoArgsConstructor
     @Data
     public static class Config {
diff --git a/src/main/java/com/ghostchu/peerbanhelper/ipdb/IPDB.java b/src/main/java/com/ghostchu/peerbanhelper/ipdb/IPDB.java
index 37ec5a6faa..bf4a99751c 100644
--- a/src/main/java/com/ghostchu/peerbanhelper/ipdb/IPDB.java
+++ b/src/main/java/com/ghostchu/peerbanhelper/ipdb/IPDB.java
@@ -41,6 +41,7 @@
 import java.time.temporal.ChronoUnit;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executors;
@@ -221,7 +222,11 @@ private IPGeoData.CountryData queryCountry(InetAddress address) {
             countryData.setIso(country.getIsoCode());
             String countryRegionName = country.getName();
             // 对 TW,HK,MO 后处理，偷个懒
-            if (languageTag.getFirst().equals("zh-CN") && (country.getIsoCode().equals("TW") || country.getIsoCode().equals("HK") || country.getIsoCode().equalsIgnoreCase("MO"))) {
+            var code = languageTag.getFirst();
+            code = code.toLowerCase(Locale.ROOT).replace("-", "_");
+            // 台湾、香港、澳门地区有一个独立 ISO 代码，需要手动处理一下保证符合所在地法律法规
+            // 这坨代码已经改成一坨了，有时间得写个好点的 :(
+            if ((code.equals("zh_cn") || code.equals("zh_hk") || code.equals("zh_mo")) && (country.getIsoCode().equals("TW") || country.getIsoCode().equals("HK") || country.getIsoCode().equalsIgnoreCase("MO"))) {
                 countryRegionName = "中国" + countryRegionName;
             }
             countryData.setName(countryRegionName);
diff --git a/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java b/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java
index 905ec05395..7672aadd7b 100644
--- a/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java
+++ b/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java
@@ -500,7 +500,7 @@ public enum Lang {
     LAB_EXPERIMENT_FILL_MISSING_DATA_IN_TRAFFIC_SUMMARY_DESCRIPTION,
     GUI_TABBED_WEBUI,
     JCEF_BROWSER_UNSUPPORTED_PLATFORM,
-    JCEF_BROWSER_UNSUPPORTED_EXCEPTION, JCEF_DOWNLOAD_TITLE, JCEF_DOWNLOAD_DESCRIPTION, GUI_COMMON_CANCEL, JCEF_DOWNLOAD_UNZIP_DESCRIPTION, IPDB_DOWNLOAD_TITLE, IPDB_DOWNLOAD_DESCRIPTION, PBH_PLUS_THANKS_FOR_DONATION_GUI_TITLE, LAB_EXPERIMENT_MAC_FLATLAF_THEME_TITLE, LAB_EXPERIMENT_MAC_FLATLAF_THEME_DESCRIPTION, GUI_TITLE_DEBUG, DOWNLOADER_DOCKER_INCORRECT_NETWORK_DETECTED_TITLE, DOWNLOADER_DOCKER_INCORRECT_NETWORK_DETECTED_DESCRIPTION, ABOUT_VIEW_CREDIT, INCOMPATIBLE_BITNESS_TITLE, INCOMPATIBLE_BITNESS_DESCRIPTION, TITLE_INCOMPATIBLE_PLATFORM, INCOMPATIBLE_BITNESS_LOG, LAB_EXPERIMENT_TRANSACTION_BATCH_BAN_HISTORY_WRITE_TITLE, LAB_EXPERIMENT_TRANSACTION_BATCH_BAN_HISTORY_WRITE_DESCRIPTION, TITLE_APP_V_CONTAINER;
+    JCEF_BROWSER_UNSUPPORTED_EXCEPTION, JCEF_DOWNLOAD_TITLE, JCEF_DOWNLOAD_DESCRIPTION, GUI_COMMON_CANCEL, JCEF_DOWNLOAD_UNZIP_DESCRIPTION, IPDB_DOWNLOAD_TITLE, IPDB_DOWNLOAD_DESCRIPTION, PBH_PLUS_THANKS_FOR_DONATION_GUI_TITLE, LAB_EXPERIMENT_MAC_FLATLAF_THEME_TITLE, LAB_EXPERIMENT_MAC_FLATLAF_THEME_DESCRIPTION, GUI_TITLE_DEBUG, DOWNLOADER_DOCKER_INCORRECT_NETWORK_DETECTED_TITLE, DOWNLOADER_DOCKER_INCORRECT_NETWORK_DETECTED_DESCRIPTION, ABOUT_VIEW_CREDIT, INCOMPATIBLE_BITNESS_TITLE, INCOMPATIBLE_BITNESS_DESCRIPTION, TITLE_INCOMPATIBLE_PLATFORM, INCOMPATIBLE_BITNESS_LOG, LAB_EXPERIMENT_TRANSACTION_BATCH_BAN_HISTORY_WRITE_TITLE, LAB_EXPERIMENT_TRANSACTION_BATCH_BAN_HISTORY_WRITE_DESCRIPTION, TITLE_APP_V_CONTAINER, ALERT_DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED_TITLE, ALERT_DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED_DESCRIPTION, DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED;
 
     public String getKey() {
         return name();
diff --git a/src/main/java/cordelia/client/TrClient.java b/src/main/java/cordelia/client/TrClient.java
index be8bd36eb2..e8bee02759 100644
--- a/src/main/java/cordelia/client/TrClient.java
+++ b/src/main/java/cordelia/client/TrClient.java
@@ -107,7 +107,7 @@ public <E extends RqArguments, S extends RsArguments> TypedResponse<S> execute(E
             log.error(tlUI(Lang.DOWNLOADER_TR_INVALID_RESPONSE, jsonBuffer, jsonSyntaxException));
             throw new IllegalStateException(jsonSyntaxException);
         } catch (IOException | InterruptedException e) {
-            log.error("Request Transmission JsonRPC failure", e);
+            log.debug("Request Transmission JsonRPC failure", e);
             throw new IllegalStateException(e);
         }
     }
diff --git a/src/main/resources/lang/en_us/messages.yml b/src/main/resources/lang/en_us/messages.yml
index b6d652b64d..76be81d1fe 100644
--- a/src/main/resources/lang/en_us/messages.yml
+++ b/src/main/resources/lang/en_us/messages.yml
@@ -583,4 +583,18 @@ DOWNLOADER_DOCKER_INCORRECT_NETWORK_DETECTED_DESCRIPTION: |
 LAB_EXPERIMENT_TRANSACTION_BATCH_BAN_HISTORY_WRITE_TITLE: "Enable transaction batch write ban history"
 LAB_EXPERIMENT_TRANSACTION_BATCH_BAN_HISTORY_WRITE_DESCRIPTION: "Use transactions to batch write to the database when recording ban history to improve performance and reduce random disk I/O operations."
 ABOUT_VIEW_CREDIT: "Developers and community contributors..."
-TITLE_APP_V_CONTAINER: "📦App-V"
\ No newline at end of file
+TITLE_APP_V_CONTAINER: "📦App-V"
+
+ALERT_DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED_TITLE: "Failed to update IP blocklist for {}"
+ALERT_DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED_DESCRIPTION: |
+  PeerBanHelper was unable to update the IP blocklist for {} because Transmission notified us that the blocklist update failed.
+  Please check and ensure that the "Settings -> Basic Settings -> WebUI -> Prefix" configuration item is correctly set to point to the PeerBanHelper WebUI address and that it is accessible and reachable by Transmission (merely confirming access via a browser is insufficient).
+  If this configuration is incorrect, Transmission will not be able to properly block or unblock any IP addresses.
+  
+  If you need any assistance with the configuration, please refer to the support documentation or request help in the community.
+DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED: |
+  PeerBanHelper was unable to update the IP blocklist for {} because Transmission notified us that the blocklist update failed.
+  Please check and ensure that the "Settings -> Basic Settings -> WebUI -> Prefix" configuration item is correctly set to point to the PeerBanHelper WebUI address and that it is accessible and reachable by Transmission (merely confirming access via a browser is insufficient).
+  If this configuration is incorrect, Transmission will not be able to properly block or unblock any IP addresses.
+  
+  If you need any assistance with the configuration, please refer to the support documentation or request help in the community.
\ No newline at end of file
diff --git a/src/main/resources/lang/messages_fallback.yml b/src/main/resources/lang/messages_fallback.yml
index 56cd967b67..6f7937365d 100644
--- a/src/main/resources/lang/messages_fallback.yml
+++ b/src/main/resources/lang/messages_fallback.yml
@@ -587,4 +587,12 @@ TITLE_INCOMPATIBLE_PLATFORM: "⚠不受支持的平台"
 LAB_EXPERIMENT_TRANSACTION_BATCH_BAN_HISTORY_WRITE_TITLE: "启用事务批量写入封禁历史记录"
 LAB_EXPERIMENT_TRANSACTION_BATCH_BAN_HISTORY_WRITE_DESCRIPTION: "在记录封禁历史记录时使用事务以批量写入数据库以提高性能和减少随机磁盘 I/O 操作。"
 ABOUT_VIEW_CREDIT: "开发人员与社区贡献者..."
-TITLE_APP_V_CONTAINER: "📦App-V"
\ No newline at end of file
+TITLE_APP_V_CONTAINER: "📦App-V"
+
+ALERT_DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED_TITLE: "下载器 {} IP 封禁列表更新失败"
+ALERT_DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED_DESCRIPTION: |
+  PeerBanHelper 无法更新 {} 的 IP 封禁列表，因为 Transmission 通知我们封禁列表更新失败。
+  请检查并确保已正确配置 "设置 -> 基础设置 -> WebUI -> 前缀" 配置项，确保指向 PeerBanHelper WebUI 的地址，并可以被 Transmission 访问和连接（仅通过浏览器访问确认是不够的）。
+  如果此配置不正确，则 Transmission 将无法正确封禁和解除封禁任何 IP 地址。
+  
+  如果需要任何配置帮助，请参阅支持文档或在社区中请求帮助。
\ No newline at end of file
diff --git a/src/main/resources/lang/zh-tw/messages.yml b/src/main/resources/lang/zh-tw/messages.yml
deleted file mode 100644
index cf21b5ad58..0000000000
--- a/src/main/resources/lang/zh-tw/messages.yml
+++ /dev/null
@@ -1,594 +0,0 @@
-ERR_BUILD_NO_INFO_FILE: "錯誤：構建資訊文件不存在"
-ERR_CANNOT_LOAD_BUILD_INFO: "錯誤：無法載入構建資訊文件"
-MOTD: "PeerBanHelper v{} - by PBH-BTN Community, Made with Sugar & Love"
-LOADING_CONFIG: "正在載入設定檔……"
-CONFIG_PEERBANHELPER: "已初始化目錄結構，相關文件已放置在執行目錄的 data 資料夾下，請配置相關文件後，再重新啟動 PeerBanHelper"
-ERR_SETUP_CONFIGURATION: "錯誤：無法初始化設定檔結構"
-DISCOVER_NEW_CLIENT: " + {} -> {} ({})"
-ERR_INITIALIZE_BAN_PROVIDER_ENDPOINT_FAILURE: "錯誤：無法初始化 API 提供端點，Transmission 模組的封禁功能將不起作用"
-WAIT_FOR_MODULES_STARTUP: "請等待功能模組初始化……"
-MODULE_REGISTER: "[註冊] {}"
-MODULE_UNREGISTER: "[解註冊] {}"
-ERR_CLIENT_LOGIN_FAILURE_SKIP: "登入到 {} ({}) 失敗：{}"
-ERR_UNEXPECTED_API_ERROR: "在處理 {} ({}) 的 API 操作時出現了一個非預期的錯誤"
-PEER_UNBAN_WAVE: "[解封] 解除了 {} 個過期的對等體封禁"
-ERR_UPDATE_BAN_LIST: "在更新 {} ({}) 的封禁列表時出現了一個非預期的錯誤"
-BAN_PEER: "[封禁] {}, PeerId={}, ClientName={}, Progress={}, Uploaded={}, Downloaded={}, Torrent={}, Reason={}"
-CHECK_COMPLETED: "[完成] 已檢查 {} 的 {} 個活躍 Torrent 和 {} 個對等體"
-ERR_INVALID_RULE_SYNTAX: "規則 {} 的表達式無效，請檢查是否存在拼寫錯誤"
-MODULE_CNB_MATCH_CLIENT_NAME: "匹配 ClientName (UserAgent): {}"
-MODULE_IBL_MATCH_IP: "匹配 IP 規則: {}"
-MODULE_IBL_MATCH_IP_RULE: "匹配 IP黑名單訂閱 規則: {}, IP 位址: {}, 備註: {}"
-MODULE_IBL_MATCH_ASN: "匹配 ASN 規則: {}"
-MODULE_IBL_MATCH_REGION: "匹配國家或地區 ISO 程式碼規則: {}"
-MODULE_IBL_EXCEPTION_GEOIP: "匹配 GeoIP 資訊時出現異常，請回饋錯誤給開發者"
-MODULE_IBL_MATCH_PORT: "匹配 Port 規則: {}"
-MODULE_PID_MATCH_PEER_ID: "匹配 PeerId 規則: {}"
-MODULE_IBL_MATCH_CITY: "匹配城市名稱規則: {}"
-MODULE_PCB_EXCESSIVE_DOWNLOAD: "客戶端下載過量：種子大小：{}，上傳給此對等體的總量：{}，最大允許的過量下載總量：{}"
-MODULE_PCB_EXCESSIVE_DOWNLOAD_INCOMPLETE: "客戶端下載過量：種子大小：{} ({} 已下載)，上傳給此對等體的總量：{}，最大允許的過量下載總量：{}"
-MODULE_PCB_PEER_MORE_THAN_LOCAL_SKIP: "客戶端進度：{}，實際進度：{}，客戶端的進度多於本機進度，跳過檢測"
-MODULE_PCB_PEER_BAN_INCORRECT_PROGRESS: "客戶端進度：{}，實際進度：{}，差值：{}"
-MODULE_PCB_PEER_BAN_REWIND: "客戶端進度：{}，實際進度：{}，上次記錄進度：{}，本次回退進度：{}，差值：{}"
-MODULE_PCB_SKIP_UNKNOWN_SIZE_TORRENT: "種子大小未知"
-GUI_BUTTON_RESIZE_TABLE: "按此調整列寬"
-MODULE_AP_SSL_CONTEXT_FAILURE: "初始化 SSLContext 時出錯"
-MODULE_MDB_MULTI_DIALING_NOT_DETECTED: "未發現多撥下載"
-MODULE_MDB_MULTI_DIALING_DETECTED: "發現多撥下載，請持續關注，子網：{}，觸發IP：{}"
-MODULE_MDB_MULTI_DIALING_HUNTING_TRIGGERED: "觸發多撥追獵名單，子網：{}，觸發IP：{}"
-DOWNLOADER_QB_LOGIN_FAILED: "登入到 {} 失敗：{} - {}: {}"
-DOWNLOADER_QB_FAILED_REQUEST_TORRENT_LIST: "請求 Torrents 列表失敗 - {} - {}"
-DOWNLOADER_QB_FAILED_REQUEST_PEERS_LIST_IN_TORRENT: "請求 Torrent 的 Peers 列表失敗 - {} - {}"
-DOWNLOADER_QB_API_PREFERENCES_ERR: "qBittorrent 的偏好設定 API 返回了非 200 預期響應 - {} - {}"
-DOWNLOADER_QB_FAILED_SAVE_BANLIST: "無法儲存 {} ({}) 的 Banlist！{} - {}\n{}"
-DOWNLOADER_TR_MOTD_WARNING: "[受限] 由於 Transmission 的 RPC-API 限制，PeerId 黑名單功能和 ProgressCheatBlocker 功能的過量下載模組不可用"
-DOWNLOADER_TR_DISCONNECT_PEERS: "[重設] 正在斷開 Transmission 上的 {} 個種子連接的對等體，以便應用 IP 封鎖列表的更改"
-DOWNLOADER_TR_INCORRECT_BANLIST_API_RESP: "設定 Transmission 的 BanList 地址時，返回非成功響應：{}。"
-DOWNLOADER_TR_INCORRECT_SET_BANLIST_API_RESP: |
-  到 Transmission，PBH 沒有生效
-  更新 BanList 時，返回非成功響應
-  erBanHelper 的外部互動埠，以便 Transmission 從 PBH 拉取 IP 黑名單
-  的 設定 -> 隱私 -> 封鎖列表 中自動填寫的 URL 是否正確，如果不正確，請在 PeerBanHelper 的 config.yml 中正確配置 server 部分的設定檔，確保 Transmission 能夠正確連接到 IP 黑名單提供端
-DOWNLOADER_TR_INVALID_RESPONSE: "[錯誤] Transmission 返回無效 JSON 響應: {}"
-DOWNLOADER_TR_UPDATED_BLOCKLIST: "[響應] Transmission 封鎖列表已更新成功，現在包含 {} 條規則"
-DOWNLOADER_TR_KNOWN_INCOMPATIBILITY: "[錯誤] 您正在使用的 Transmission 版本與 PeerBanHelper 不相容: {}"
-DOWNLOADER_TR_INCOMPATIBILITY_BANAPI: "目前版本存在封禁 API 的已知問題，請升級至 3.0-20 或更高版本"
-ERR_CONFIG_DIRECTORY_INCORRECT: "初始化失敗：config 不是一個目錄。如果您正在使用 Docker，請確保其正確掛載。"
-GUI_MENU_OPEN_DATA_DIRECTORY: "打開資料文件儲存位置..."
-PBH_SHUTTING_DOWN: "[退出] 正在退出，請等待我們完成剩餘的工作……"
-ARB_BANNED: "IP 位址 {} 與另一個已封禁的 IP 位址 {} 處於同一封禁區間 {} 內，執行連鎖封禁操作。"
-DATABASE_SETUP_FAILED: "[錯誤] 資料庫初始化失敗"
-DATABASE_SAVE_BUFFER_FAILED: "[錯誤] 寫入記憶體快取的封禁日誌時出現了 SQL 錯誤，未儲存的資料已被丟棄"
-WEB_BANLOGS_INTERNAL_ERROR: "[錯誤] 讀取封禁日誌時遇到非預期錯誤"
-BOOTSTRAP_FAILED: "[錯誤] PeerBanHelper 啟動失敗，遇到致命錯誤，請檢查控制台日誌"
-DATABASE_FAILURE: "[錯誤] 無法連接到持久化資料儲存資料庫，請檢查是否同時啟動了多個 PBH 範例？（如果 SQLite 資料庫損壞，請刪除它，PBH 將會重新生成新的資料庫文件）"
-CONFIGURATION_OUTDATED_MODULE_DISABLED: "[警告] 無法確認功能模組 {} 的配置狀態。設定檔似乎已過期，因此無法讀取此模組的模組設定檔"
-BTN_DOWNLOADER_GENERAL_FAILURE: "[BTN 網路] 從下載器 {} 獲取目前 Torrent 任務資訊失敗，跳過……"
-BTN_UPDATE_RULES_SUCCESSES: "[BTN 網路] 規則資料更新成功，目前資料版本：{}"
-BTN_REQUEST_FAILS: "[BTN 網路] 請求時出現錯誤，操作已取消 {}"
-BTN_CONFIG_FAILS: "[BTN 網路] 所連接的 BTN 網路實例未返回有效配置響應，BTN 網路功能可能不會正常工作 {}，將在 {} 秒後重試"
-MODULE_BTN_BAN: "[BTN 封禁] 匹配 {} 規則集（{}）：{}"
-BTN_NETWORK_CONNECTING: "[BTN 網路] 請等待我們連接到 BTN 網路……"
-BTN_NETWORK_NOT_ENABLED: "[BTN 網路] 未啟用 BTN 功能：此 PeerBanHelper 客戶端未加入 BTN 網路"
-BTN_NETWORK_ENABLED: "[BTN 網路] 功能已啟用"
-BANLIST_INVOKER_REGISTERED: "[BanListInvoker] 已註冊：{}"
-BANLIST_INVOKER_IPFILTER_FAIL: "[BanListInvoker] 清空 ipfilter.dat 文件失敗，出現 I/O 錯誤"
-BANLIST_INVOKER_COMMAND_EXEC_TIMEOUT: "[BanListInvoker] 執行指令 {} 時超時，PBH 不再繼續等待行程"
-BANLIST_INVOKER_COMMAND_EXEC_FAILED: "[BanListInvoker] 執行指令 {} 時，行程返回非零狀態碼（{}），這可能意味著指令未被成功執行，請查看"
-BTN_INCOMPATIBLE_SERVER: "[BTN 網路] 您所連接的 BTN 實例與目前 BTN 客戶端不相容"
-BTN_SUBMITTING_PEERS: "[BTN 網路] 計劃任務正在向 BTN 網路提交目前下載的 Peers 列表，請稍等……"
-BTN_SUBMITTED_PEERS: "[BTN 網路] 已向 BTN 網路提交 {} 個 Peers，感謝您對 BTN 網路的支持！"
-BTN_SUBMITTING_BANS: "[BTN 網路] 計劃任務正在向 BTN 網路提交自上次匯報以來新增的封禁條目，請稍等……"
-BTN_SUBMITTED_BANS: "[BTN 網路] 已向 BTN 網路提交 {} 個封禁記錄，感謝您對 BTN 網路的支持！"
-BTN_SUBMITTING_HITRATE: "[BTN 網路] 計劃任務正在向 BTN 網路回報規則命中率資料，請稍等"
-BTN_SUBMITTED_HITRATE: "[BTN 網路] 已向 BTN 網路回報 {} 個規則的命中率資料，感謝您對 BTN 網路的支持！"
-BTN_SUBMITTING_HISTORIES: "[BTN 網路] 計劃任務正在向 BTN 網路提交 Peer 活動歷史記錄，請稍等……"
-BTN_SUBMITTED_HISTORIES: "[BTN 網路] 已向 BTN 網路提交 {} 個 Peer 活動歷史記錄，感謝您對 BTN 網路的支持！"
-CONFIG_CHECKING: "[配置升級實用工具] 請等待檢查設定檔更新……"
-CONFIG_MIGRATING: "[配置升級實用工具] 遷移設定檔：從 {} 至 {} ……"
-CONFIG_EXECUTE_MIGRATE: "[配置升級實用工具] 執行設定檔升級腳本：{}"
-CONFIG_MIGRATE_FAILED: "[配置升級實用工具] 執行設定檔升級腳本 {}（升級到版本 {}）時出現了錯誤，PeerBanHelper 可能無法正常執行：{}"
-CONFIG_UPGRADED: "[配置升級實用工具] 成功升級設定檔到版本 {}"
-CONFIG_SAVE_CHANGES: "[配置升級實用工具] 正在儲存更改……"
-CONFIG_SAVE_ERROR: "[配置升級實用工具] 更改儲存到磁碟失敗"
-BTN_RECONFIGURE_CHECK_FAILED: "[BTN 網路] 檢查重配置狀態失敗：{}"
-BTN_SHUTTING_DOWN: "[BTN 網路] 正在關閉 BTN 模組……"
-BTN_RECONFIGURING: "[BTN 網路] 發現伺服器基本配置更新，正在重新配置 BTN 網路模組……"
-RULE_MATCHER_STRING_CONTAINS: "子串匹配"
-RULE_MATCHER_STRING_ENDS_WITH: "匹配結尾"
-MATCH_STRING_STARTS_WITH: "字串開頭: {}"
-RULE_MATCHER_STRING_STARTS_WITH: "匹配開頭"
-RULE_MATCHER_STRING_LENGTH: "匹配長度"
-RULE_MATCHER_STRING_REGEX: "匹配正則"
-RULE_MATCHER_SUB_RULE: "訂閱規則: {}"
-RESET_DOWNLOADER_FAILED: "[警告] 重設下載器封禁列表到初始狀態時出現錯誤"
-DOWNLOADER_QB_INCREAMENT_BAN_FAILED: "[錯誤] 向下載器請求增量封禁對等體時出現錯誤，請在設定檔中關閉增量封禁(increment-ban)配置項"
-SHUTDOWN_CLOSE_METRICS: "[退出] 正在儲存封禁日誌和統計資料……"
-SHUTDOWN_UNREGISTER_MODULES: "[退出] 正在註銷功能模組……"
-SHUTDOWN_CLOSE_DATABASE: "[退出] 正在安全關閉並儲存持久化資料庫……"
-SHUTDOWN_CLEANUP_RESOURCES: "[退出] 清理資源……"
-SHUTDOWN_DONE: "[退出] 全部完成！"
-SAVED_BANLIST: "[儲存] 已成功儲存 {} 條封禁資料到資料庫"
-SAVE_BANLIST_FAILED: "[儲存] 儲存封禁列表到文件失敗"
-LOAD_BANLIST_FROM_FILE: "[封禁] 已從儲存的封禁列表快取文件中復原了 {} 個封禁項"
-LOAD_BANLIST_FAIL: "[封禁] 載入封禁列表過程出現錯誤"
-GUI_MENU_PROGRAM: "程式"
-GUI_MENU_WEBUI: "WebUI"
-GUI_MENU_WEBUI_OPEN: "打開並自動登入 WebUI..."
-GUI_MENU_ABOUT: "關於"
-GUI_MENU_QUIT: "退出"
-GUI_COPY_WEBUI_TOKEN: "複製 WebUI Token..."
-GUI_TRAY_MESSAGE_CAPTION: "PeerBanHelper 正在後台執行"
-GUI_TRAY_MESSAGE_DESCRIPTION: "按下系統匣圖示重新打開視窗；右鍵系統匣圖示可完全退出"
-GUI_TABBED_LOGS: "執行日誌"
-GUI_TABBED_PEERS: "已連接的Peers"
-ABOUT_VIEW_GITHUB: "查看 Github 頁面..."
-IPDB_UPDATING: "{} 資料庫已過期且需要更新，請等待 PBH 連線到伺服器更新資料……"
-IPDB_UPDATE_FAILED: "下載資料庫 {} 時出現錯誤：{}"
-IPDB_UPDATE_SUCCESS: "更新資料庫 {} 成功！"
-IPDB_INVALID: "由於在初始化過程中出現錯誤，IPDB 功能已被自動停用。請檢查日誌檔案以修復問題"
-IPDB_NEED_CONFIG: "IPDB 功能需要配置才能使用，請在 config.yml 的 ip-database 中填寫相關配置資訊"
-DOWNLOAD_PROGRESS_DETERMINED: "下載進度：已下載 {}/{} 位元組，進度：{}%"
-DOWNLOAD_PROGRESS: "下載進度：已下載 {} 位元組"
-DOWNLOAD_COMPLETED: "下載進度：已完成！共傳輸 {} 位元組的資料"
-BAN_WAVE_CHECK_COMPLETED: "已檢查 {} 個下載器的 {} 個活躍 Torrent 與 {} 個 Peers。共封禁 {} 個 Peers，並解除 {} 個過期的封禁 ({}ms)"
-WATCH_DOG_HUNGRY: "[警告] WatchDog Service {} 未在指定時間 {} 內得到重設，最後狀態 {}，正在轉儲行程執行緒資訊，請發送給 PeerBanHelper 開發者以協助修復此問題"
-WATCH_DOG_CALLBACK_BLOCKED: "[錯誤] WatchDog Service 回調執行緒無響應，已強制離開回調"
-PBH_BAN_WAVE_STARTED: "PeerBanHelper BanWave Daemon 已啟動"
-BAN_WAVE_WATCH_DOG_TITLE: "PeerBanHelper 正嘗試從異常中復原"
-BAN_WAVE_WATCH_DOG_DESCRIPTION: "我們檢測到封禁執行緒因未知原因停止響應，因此 PeerBanHelper 已嘗試重啟問題執行緒。請查看程式日誌並將有關資訊發送給開發者以協助修復此錯誤。"
-INTERNAL_ERROR: "出現了一個內部錯誤，請檢查控制台日誌"
-PART_TASKS_TIMED_OUT: "[警告] 等待部分任務執行時超過最大時間限制，忽略未完成的任務…… 目前執行：{}"
-TOO_WEAK_TOKEN: "Web Auth Token 未初始化或不滿足最低強度要求（長度 > 8），PeerBanHelper 已重新生成了一個滿足複雜度的新 Token"
-TIMING_RECOVER_PERSISTENT_BAN_LIST: "[超時] 在復原持久化封禁列表到下載器時出現操作超時，任務已被強制終止"
-TIMING_CHECK_BANS: "[超時] 在執行 Peers 檢查時出現操作超時，任務已被強制終止"
-TIMING_ADD_BANS: "[超時] 在處理新增 Peers 封禁時出現操作超時，任務已被強制終止"
-TIMING_APPLY_BAN_LIST: "[超時] 在應用封禁列表到下載器時出現操作超時，任務已被強制終止"
-TIMING_COLLECT_PEERS: "[超時] 在請求下載器 WebAPI 以獲取已連接的 Peers 時操作超時，任務已被強制終止，建議檢查下載器狀態和網路連接。"
-TIMING_UNFINISHED_TASK: "[超時] 未完成的任務已被強制終止 -> {}"
-CONFIGURATION_INVALID: "[錯誤] 設定檔載入失敗，可能由於人為修改錯誤或裝置異常斷電導致損壞，請刪除文件 {} 來重設設定檔"
-CONFIGURATION_INVALID_TITLE: "設定檔載入失敗"
-CONFIGURATION_INVALID_DESCRIPTION: "PeerBanHelper 無法正確載入必要的設定檔，這可能由於人為修改錯誤或裝置異常斷電導致損壞，請刪除文件 {} 來重設設定檔。\nPeerBanHelper 即將退出……"
-TRCLIENT_API_ERROR: "[錯誤] TrClient 請求下載器時出現錯誤 {} - {}"
-IP_BAN_RULE_MATCH_ERROR: "[錯誤] IP黑名單訂閱規則匹配異常"
-IP_BAN_RULE_MATCH_TIME: "匹配IP黑名單訂閱規則花費時間：{}"
-IP_BAN_RULE_UPDATE_TYPE_AUTO: "自動更新"
-IP_BAN_RULE_UPDATE_TYPE_MANUAL: "手動更新"
-IP_BAN_RULE_UPDATE_FINISH: "IP黑名單規則訂閱完畢"
-IP_BAN_RULE_NO_UPDATE: "IP黑名單訂閱規則 {} 未發生更新"
-IP_BAN_RULE_UPDATE_SUCCESS: "IP黑名單訂閱規則 {} 更新成功"
-IP_BAN_RULE_UPDATE_FAILED: "IP黑名單訂閱規則 {} 更新失敗"
-IP_BAN_RULE_LOAD_SUCCESS: "IP黑名單訂閱規則 {} 載入成功"
-IP_BAN_RULE_UPDATE_LOG_ERROR: "[錯誤] IP黑名單訂閱規則 {} 更新日誌失敗"
-IP_BAN_RULE_USE_CACHE: "[警告] IP黑名單訂閱規則 {} 訂閱失敗，使用本機快取載入成功"
-IP_BAN_RULE_LOAD_FAILED: "[錯誤] IP黑名單訂閱規則 {} 載入失敗"
-IP_BAN_RULE_LOAD_CIDR: "IP黑名單訂閱規則 {} 載入CIDR : {}"
-IP_BAN_RULE_LOAD_IP: "IP黑名單訂閱規則 {} 載入精確IP : {}"
-RULE_SUB_API_INTERNAL_ERROR: "[錯誤] 訂閱規則API遇到非預期錯誤"
-IP_BAN_RULE_NO_ID: "[錯誤] IP黑名單訂閱規則ID為空"
-IP_BAN_RULE_ID_CONFLICT: "[錯誤] IP黑名單訂閱規則ID衝突: {}"
-IP_BAN_RULE_CANT_FIND: "[錯誤] 未找到IP黑名單訂閱規則: {}"
-IP_BAN_RULE_PARAM_WRONG: "[錯誤] IP黑名單訂閱規則參數錯誤"
-IP_BAN_RULE_URL_WRONG: "[錯誤] IP黑名單訂閱規則 {} URL錯誤"
-IP_BAN_RULE_ENABLED: "IP黑名單訂閱規則 {} 已啟用"
-IP_BAN_RULE_DISABLED: "IP黑名單訂閱規則 {} 已停用"
-IP_BAN_RULE_UPDATED: "IP黑名單訂閱規則 {} 已更新"
-IP_BAN_RULE_ALL_UPDATED: "IP黑名單訂閱規則已全部更新"
-IP_BAN_RULE_SAVED: "IP黑名單訂閱規則已儲存"
-IP_BAN_RULE_DELETED: "IP黑名單訂閱規則 {} 已刪除"
-IP_BAN_RULE_INFO_QUERY_SUCCESS: "IP黑名單訂閱規則查詢成功"
-IP_BAN_RULE_LOG_QUERY_SUCCESS: "IP黑名單訂閱規則更新日誌查詢成功"
-IP_BAN_RULE_LOG_QUERY_ERROR: "IP黑名單訂閱規則更新日誌查詢出錯"
-IP_BAN_RULE_LOG_QUERY_WRONG_PARAM: "IP黑名單訂閱規則更新日誌查詢參數錯誤"
-IP_BAN_RULE_CHECK_INTERVAL_QUERY_SUCCESS: "IP黑名單訂閱規則更新間隔查詢成功"
-IP_BAN_RULE_CHECK_INTERVAL_WRONG_PARAM: "IP黑名單訂閱規則更新間隔參數錯誤"
-IP_BAN_RULE_CHECK_INTERVAL_UPDATED: "IP黑名單訂閱規則更新間隔設定成功"
-IP_BAN_RULE_ENABLED_WRONG_PARAM: "IP黑名單訂閱規則啟用停用參數錯誤"
-WEBAPI_AUTH_INVALID_TOKEN: "登入失敗，Token 無效"
-WEBAPI_AUTH_OK: "登入成功"
-WEBAPI_AUTH_BANNED_TOO_FREQ: "登入錯誤次數過多，此 IP 位址已被暫時封禁"
-WEBAPI_NOT_LOGGED: "操作失敗，您還未登入"
-WEBAPI_INTERNAL_ERROR: "處理 WebAPI 請求時出現了一個內部伺服器錯誤，請查看控制台日誌"
-GITHUB_PAGE: "https://github.com/PBH-BTN/PeerBanHelper"
-GUI_COPY_TO_CLIPBOARD_TITLE: "複製到剪貼簿"
-GUI_COPY_TO_CLIPBOARD_DESCRIPTION: "已成功複製到系統剪貼簿: \n{}"
-GUI_TITLE_LOADING: "PeerBanHelper - 正在載入，請稍候..."
-GUI_TITLE_LOADED: "PeerBanHelper - {} ({})"
-WEBVIEW_DISABLED_WEBKIT_NOT_INCLUDED: "未找到 JavaFx Web 模組，您正在使用精簡構建，WebUI 頁籤未啟用"
-WEBVIEW_ENABLED: "已找到 JavaFx Web，WebUI 頁籤已啟用"
-STATUS_TEXT_OK: "目前工作正常"
-STATUS_TEXT_LOGIN_FAILED: "嘗試登入到下載器失敗"
-STATUS_TEXT_EXCEPTION: "出現異常，請檢查 PeerBanHelper 控制台: {}"
-STATUS_TEXT_NEED_PRIVILEGE: "權限不足，請求權限提升（以管理員/root身份執行）"
-SUGGEST_FIREWALL_IPTABELS: "不推薦使用原生 iptables，可能引起網路性能下降。請考慮安裝 ipset 代替使用 iptabels"
-SUGGEST_FIREWALL_FIREWALLD: "不推薦使用原生 firewalld，可能引起網路性能下降。請考慮安裝 ipset 代替使用 firewalld"
-SUGGEST_FIREWALL_WINDOWS_FIREWALL_DISABLED: "Windows 防火牆目前處於停用狀態，請為「公用網路」和「私人網路」打開 Windows 防火牆，否則系統防火牆整合將不起作用"
-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 秒）……"
-LIBRARIES_LOADER_DETERMINE_TEST_RESULT: "下載測試結果："
-LIBRARIES_DOWNLOAD_DIALOG_TITLE: "下載依賴文件"
-LIBRARIES_DOWNLOAD_DIALOG_DESCRIPTION: "正在下載：{}"
-LIBRARIES_DOWNLOAD_DIALOG_BAR_TEXT: "下載進度：{}/{}"
-LIBRARIES_DOWNLOAD_DIALOG_TOOLTIP: "正在下載所需的執行階段依賴文件，請稍等……"
-LIBRARIES_DOWNLOAD_DIALOG_TEST_SERVER: "測試最佳鏡像源"
-LIBRARIES_DOWNLOAD_DIALOG_TEST_SERVER_DESCRIPTION: "正在測試共 {} 個 Maven 鏡像倉庫，最多 15 秒……"
-LIBRARIES_DOWNLOAD_DIALOG_TEST_SERVER_TOOLTIP: "使用最佳的鏡像源可在下載依賴文件時顯著提升下載速度。"
-LIBRARIES_DOWNLOAD_DIALOG_TEST_SERVER_BAR_TEXT: "很快就好！"
-WEBVIEW_RELOAD_PAGE: "重新整理網頁"
-WEBVIEW_RESET_PAGE: "回到初始頁"
-WEBVIEW_BACK: "後退"
-WEBVIEW_FORWARD: "前進"
-DOWNLOADER_API_ADD_FAILURE: "下載器建立失敗，是否傳遞的是一個不受支援的下載器類型？"
-DOWNLOADER_API_CREATED: "下載器建立成功"
-DOWNLOADER_API_UPDATED: "下載器更新成功"
-DOWNLOADER_API_CREATION_FAILED_ALREADY_EXISTS: "下載器建立失敗，已有相同的下載器配置存在"
-DOWNLOADER_API_CREATION_FAILED_IO_EXCEPTION: "下載器建立失敗，出現 I/O 錯誤，請檢查控制台日誌"
-DOWNLOADER_API_UPDATE_FAILURE: "下載器更新失敗，是否傳遞的是一個不受支援的下載器類型？"
-DOWNLOADER_API_UPDATE_FAILURE_ALREADY_EXISTS: "下載器更新失敗，已有一個相同的下載器配置存在，且移除失敗"
-DOWNLOADER_API_TEST_NAME_EXISTS: "下載器所使用的名稱與另一個已存在的下載器衝突"
-DOWNLOADER_API_TEST_OK: "驗證成功，配置有效"
-DOWNLOADER_API_REMOVE_NOT_EXISTS: "無法移除指定的下載器，指定的下載器並沒有在 PeerBanHelper 中註冊"
-DOWNLOADER_API_REMOVE_SAVED: "移除成功，配置已儲存"
-DOWNLOADER_API_DOWNLOADER_NOT_EXISTS: "請求的下載器未在 PeerBanHelper 中註冊"
-DOWNLOADER_BIGLYBT_INCORRECT_RESPONSE: "請求 Torrent 列表失敗：{} - {}"
-DOWNLOADER_BIGLYBT_FAILED_REQUEST_PEERS_LIST_IN_TORRENT: "請求 Torrent 的 Peers 列表失敗 - {} - {}"
-DOWNLOADER_BIGLYBT_INCREAMENT_BAN_FAILED: "[錯誤] 向下載器請求增量封禁對等體時出現錯誤，請嘗試在設定檔中關閉增量封禁(increment-ban)配置項"
-DOWNLOADER_BIGLYBT_FAILED_SAVE_BANLIST: "無法儲存 {} ({}) 的 Banlist！{} - {}\n{}"
-ALERT_INCORRECT_PROXY_SETTING: "警告！通過 HTTP_PROXY 環境變數無法為 Java 應用程式設定代理伺服器！您的代理設定可能並不會生效。"
-COMMAND_EXECUTOR: "[CommandExecutor] 指令執行器正在執行系統終端指令：{}"
-COMMAND_EXECUTOR_FAILED: "[CommandExecutor] 系統終端指令執行失敗：{}"
-COMMAND_EXECUTOR_FAILED_TIMEOUT: "[CommandExecutor] 系統終端指令執行超時：{}"
-DOWNLOADER_DELUGE_PLUGIN_NOT_INSTALLED: "無法登入到下載器 {}，此 Deluge 下載器必須正確載入 PeerBanHelper Deluge Adapter 擴展外掛程式：https://github.com/PBH-BTN/PBH-Adapter-Deluge"
-DOWNLOADER_DELUGE_API_ERROR: "執行 Deluge RPC 呼叫失敗，操作被忽略"
-DOWNLOADER_UNHANDLED_EXCEPTION: "發生了一個未處理的異常，請回饋給 PeerBanHelper 開發者，此錯誤已被跳過……"
-WEB_ENDPOINT_REGISTERED: "[註冊] WebAPI 端點已註冊：{}"
-SKIP_LOAD_PLUGIN_FOR_NATIVE_IMAGE: "檢測到Native Images，跳過載入外掛程式"
-ERR_CANNOT_LOAD_PLUGIN: "[註冊] 無法載入外掛程式：{}"
-ERR_CANNOT_UNLOAD_PLUGIN: "[退出] 無法移除外掛程式：{}"
-ARB_ERROR_TO_CONVERTING_IP: "IP 位址 {} 既不是 IPV4 地址也不是 IPV6 地址。"
-DATABASE_BUFFER_SAVED: "[儲存] 已儲存 {} 條記憶體快取的封禁日誌到資料庫，用時 {}ms"
-PERSIST_DISABLED: "[停用] Persist 持久化資料儲存已在此伺服器上被停用"
-BTN_PREPARE_TO_SUBMIT: "[BTN 網路] 已收集了 {} 個 Peer 資訊，將分為 {} 次提交到 BTN 網路，感謝您對 BTN 網路做出的貢獻"
-BTN_UPDATE_RULES: "[BTN 網路] 正在連線到 BTN 網路伺服器並更新規則資料，本機資料版本：{}"
-BTN_NETWORK_RECONFIGURED: "[BTN 網路] 伺服器配置資訊下發成功，已連接至 BTN 網路：{}"
-PERSIST_CLEAN_LOGS: "[清理] 已成功清理 {} 條封禁日誌"
-BAN_PEER_REVERSE_LOOKUP: "[DNS反向尋找] IP 位址 {} 反向 DNS 記錄為：{}"
-RULE_ENGINE_PARSE_FAILED: "[規則引擎] 規則 {} 解析失敗，解析過程中出現錯誤"
-RULE_ENGINE_INVALID_RULE: "規則 {} 的參數 {} 無效，僅接受以下值：{}"
-RULE_ENGINE_NOT_A_RULE: "[規則引擎] 表達式 {} 不是一個有效規則"
-RULE_MATCHER_STRING_EQUALS: "匹配相同"
-NEW_SETUP_NO_DOWNLOADERS: "PeerBanHelper 現在還未連接到任何下載器！請打開 WebUI 並新增下載器。如需登入 Token：{} （可隨時從 GUI 選單複製，或者從 config.yml 找到）"
-RULE_ENGINE_INVALID_RETURNS: |
-  腳本 {} 返回了無效值，返回的值必須是以下類型中的其一：
-  Boolean: [false=不採取任何操作, true=封禁Peer]
-  Integer: [0=不採取任何操作,1=封禁Peer,2=跳過其它規則]
-  com.ghostchu.peerbanhelper.module.PeerAction: [NO_ACTION, BAN, SKIP]
-  com.ghostchu.peerbanhelper.module.BanResult
-IP_BLACKLIST_PORT_RULE: "埠規則: {}"
-IP_BLACKLIST_CIDR_RULE: "IP/CIDR 規則: {}"
-IP_BLACKLIST_ASN_RULE: "ASN 規則: {}"
-IP_BLACKLIST_REGION_RULE: "國家/地區 ISO 程式碼規則: {}"
-
-AUTO_RANGE_BAN_IPV4_RULE: "IPv4 區間封禁規則: /{}"
-AUTO_RANGE_BAN_IPV6_RULE: "IPv6 區間封禁規則: /{}"
-
-PCB_RULE_REACHED_MAX_ALLOWED_EXCESSIVE_THRESHOLD: "已達到下載過量最大限制"
-PCB_RULE_REACHED_MAX_DIFFERENCE: "已超過允許的進度差異最大值"
-PCB_RULE_PROGRESS_REWIND: "已超過允許的進度倒退最大值"
-
-MDB_MULTI_DIALING_DETECTED: "檢測到多撥下載"
-MDB_MULTI_HUNTING: "多撥持續追蹤"
-
-BTN_BTN_RULE: "BTN-{}-{}"
-BTN_PORT_RULE: "BTN 埠規則 ({})"
-BTN_IP_RULE: "BTN IP 規則 ({})"
-GENERAL_NA: "N/A"
-
-DUPLICATE_BAN: "正在封禁的 Peer 已處於封禁列表：{}"
-
-NET_TYPE_WIDEBAND: "寬頻"
-NET_TYPE_BASE_STATION: "基地台"
-NET_TYPE_GOVERNMENT_AND_ENTERPRISE_LINE: "政企專線"
-NET_TYPE_BUSINESS_PLATFORM: "業務平台"
-NET_TYPE_BACKBONE_NETWORK: "骨幹網"
-NET_TYPE_IP_PRIVATE_NETWORK: "IP 專網"
-NET_TYPE_INTERNET_CAFE: "網咖"
-NET_TYPE_IOT: "物聯網"
-NET_TYPE_DATACENTER: "資料中心"
-
-WEBUI_VALIDATION_DOWNLOAD_LOGIN_FAILED: "配置有效，但無法連接到下載器，請檢查日誌"
-
-STATUS_TEXT_UNKNOWN: "PeerBanHelper 暫時還未獲取到此下載器的資訊"
-STATUS_TEXT_PAUSED: "PeerBanHelper 不會檢查已暫停的下載器，請取消暫停"
-DOWNLOADER_LOGIN_EXCEPTION: "無法連接到下載器，登入時出現錯誤：{}"
-DOWNLOADER_LOGIN_IO_EXCEPTION: "無法連接到下載器，登入時出現網路錯誤：{}"
-DOWNLOADER_LOGIN_INCORRECT_CRED: "鑒權失敗，錯誤的登入憑據。請檢查使用者名稱、密碼或 Token 是否正確。"
-
-USER_SCRIPT_RULE: "腳本引擎"
-USER_SCRIPT_RUN_RESULT: "腳本 {}: {}"
-SCHEDULED_OPERATIONS: "[計劃任務] 已處理 {} 個封禁列表的外部計劃更改"
-
-ARB_BANNED_REASON: "IP 位址 {} 與另一個已封禁的 IP 位址 {} 處於同一封禁區間內，執行連鎖封禁操作：{}@{}"
-
-TOO_MANY_FAILED_ATTEMPT: "連接此下載器時連續多次錯誤，已暫停登入嘗試，請檢查使用者名稱密碼和網路連接。暫停狀態將於 {} 復原"
-
-AMM_SHUTTING_DOWN: "請等待 ActiveMonitoring 將快取在記憶體中的資料寫入到資料庫，最多 5 秒……"
-AMM_CLEANING_TABLES: "[資料清理] ActiveMonitoring 正在清理過期資料，這可能需要一點時間……"
-AMM_CLEANED_UP: "[資料清理] ActiveMonitoring 已刪除了 {} 條過期資料"
-
-OOBE_DISALLOW_REINIT: "此 PeerBanHelper 已被初始化，OOBE 介面不再可用。"
-WEBAPI_NEED_INIT: "PeerBanHelper 需要 OOBE 嚮導初始化"
-
-DONATION_KEY_VERIFICATION_SUCCESSFUL: "捐贈金鑰啟用成功！感謝捐贈！授權給：{} ({})，過期時間：{}"
-IP_BLACKLIST_NETTYPE_RULE: "網路類型規則"
-
-PBH_OOBE_REQUIRED: "PeerBanHelper 需要初始化，請前往 WebUI 完成初始化步驟！WebUI 已在 {} 上啟動。"
-WEBVIEW_DEFAULT_DISABLED: "WebUI 現在預設停用，建議使用系統瀏覽器打開 WebUI"
-
-PCB_SHUTTING_DOWN: "請等待 ProgressCheatBlocker 將快取在記憶體中的資料寫入到資料庫，最多 5 秒……"
-
-CHARTS_IPDB_NEED_INIT: "請先配置 GeoIP 資料庫"
-
-IP_BLACKLIST_PUT_IP_INVALID_IP: "無效 IP 位址，請輸入一個有效的 IPV4/IPV6 地址，或者 CIDR 表達式"
-IP_BLACKLIST_PUT_IP_INVALID_ARG: "不能同時輸入 CIDR 地址或者範圍（亦或者兩個都不輸入），兩者只能使用其一"
-IP_BLACKLIST_PUT_PORT_INVALID_RANGE: "起始埠號不能小於結束埠號，且埠號需要在 1-65535 之間"
-
-APPLYING_FULL_BANLIST_TO_DOWNLOADER: "[修復] 正在使用全量封禁列表覆蓋下載器的封禁 IP 名單……"
-SCHEDULED_FULL_BANLIST_APPLY: "[修復] PeerBanHelper 已計劃在下次封禁檢查時向下載器發送全量封禁列表"
-CLEANED_BANLOGS: "[清理] 已清理 {} 條封禁日誌"
-
-OPERATION_EXECUTE_SUCCESSFULLY: "操作執行成功"
-PBH_PLUS_LICENSE_EXPIRED: "許可證已過期"
-PBH_PLUS_LICENSE_INVALID: "許可證無效"
-
-GUI_MENU_DEBUG: "除錯"
-GUI_MENU_DEBUG_RELOAD_CONFIGURATION: "重載設定檔..."
-GUI_MENU_DEBUG_HEAP_DUMP: "執行堆轉儲..."
-GUI_MENU_PRINT_THREADS: "列印執行緒堆疊資訊"
-RELOAD_COMPLETED_TITLE: "設定檔已重載"
-RELOAD_COMPLETED_DESCRIPTION: "已成功重載 {} 個模組。\n部分功能可能不支援重載，建議重啟 PBH 以完整應用設定檔更改。"
-HEAPDUMP_COMPLETED_TITLE: "堆轉儲已完成"
-HEAPDUMP_FAILED_TITLE: "堆轉儲失敗"
-HEAPDUMP_FAILED_DESCRIPTION: "無法轉儲記憶體快照，錯誤詳見控制台日誌消息。"
-HEAPDUMP_COMPLETED_DESCRIPTION: "記憶體快照已轉儲成功，請見 data/debug 目錄下的 .hprof 文件。\n如需發送給開發者，請使用壓縮工具打包壓縮。"
-RELOADING_MODULE: "[重載] {}: {}"
-
-TORRENT_NOT_FOUND: "指定的 Torrent 記錄不存在"
-PEER_NOT_FOUND: "指定的 Peer 記錄不存在"
-
-IN_ECOMODE_SHORT: "🍃效能模式"
-IN_ECOMODE_DESCRIPTION: "EcoQoS API 載入成功，Windows 效能模式已應用至 PeerBanHelper 以降低系統能耗"
-
-DOWNLOADER_TRANSMISSION_DISCOURAGE: "警告：Transmission 適配器已被廢棄，不再推薦使用。在頻繁發生封禁事件的種子上，頻繁啟停 Torrent 將導致對 Tracker 伺服器的頻繁更新，並間接引發 DoS 攻擊，這會增加 Tracker 伺服器壓力並可能導致您的 IP 位址被 Tracker 伺服器封禁。我們鼓勵您盡可能遷移到其它下載器上。https://github.com/PBH-BTN/PeerBanHelper/issues/382"
-DOWNLOADER_QBITTORRENTEE_SHADOWBANAPI_TEST_FAILURE: "您目前版本的 qBittorrentEE 不支援 ShadowBan 或未勾選 qBittorrentEE 設定中的 啟用 ShadowBan，請關閉 ShadowBan 開關。"
-
-DOWNLOADER_FAILED_REQUEST_STATISTICS: "獲取下載器 {} 的統計資料資訊失敗: {}"
-PCB_RULE_PEER_PROGRESS_CHEAT_TESTING: "Peer 作弊快速測試"
-PCB_DESCRIPTION_PEER_PROGRESS_CHEAT_TESTING: "PeerBanHelper 已短暫封禁此 Peer 以斷開連接以便進行快速測試，它將很快在稍後解除封禁"
-IPDB_EXISTS_UPDATE_FAILED: "IPDB 資料庫 {} 更新失敗，使用本機快取資料。資料可能已過期。"
-IPDB_RETRY_WITH_BACKUP_SOURCE: "IPDB 下載任務失敗或超時，正在使用備用源重試……"
-IPDB_UNGZIP_FAILED: "IPDB 資料庫 {} 解壓失敗"
-DOWNLOADER_BC_LOGIN_FAILED: "登入到 {} 失敗：{} - {}: {}"
-DOWNLOADER_BC_FAILED_REQUEST_TORRENT_LIST: "請求 Torrents 列表失敗 - {} - {}"
-DOWNLOADER_BC_FAILED_REQUEST_PEERS_LIST_IN_TORRENT: "請求 Torrent 的 Peers 列表失敗 - {} - {}"
-DOWNLOADER_BC_API_PREFERENCES_ERR: "BitComet API 返回了非預期響應 - {} - {}"
-DOWNLOADER_BC_FAILED_SAVE_BANLIST: "無法儲存 {} ({}) 的 Banlist！{} - {}\n{}"
-DOWNLOADER_BC_CONFIG_IP_FILTER: "檢測到 BitComet IpFilter 配置不正確，正在自動修正……"
-DOWNLOADER_BC_CONFIG_IP_FILTER_SUCCESS: "BitComet IpFilter 配置自動修正成功"
-DOWNLOADER_BC_CONFIG_IP_FILTER_FAILED: "BitComet IpFilter 配置自動修正失敗"
-DOWNLOADER_BC_VERSION_UNACCEPTABLE: "目前使用的 BitComet 版本不受支援，請升級到 2.10 或者更高版本"
-DOWNLOADER_BC_DOWNLOAD_DEPENDENCIES_FAILED: "下載連接到 BitComet 所需的 BouncyCastle 加解密套件時出錯，操作無法繼續，請檢查網路連接。有關更多資訊，請檢查控制台日誌。"
-
-JAVALIN_PORT_IN_USE: "埠監聽失敗，埠 {} 已被其它行程占用。這可能是因為：(1)有兩個 PeerBanHelper 正在執行（特別是您可能選擇了安裝為系統服務，這種情況下將會自動啟動）(2)其它程式正在使用此埠（如 Uplay/Ubisoft Connect）；請跟隨故障排除指南解決此問題。https://docs.pbh-btn.com/docs/faq"
-JAVALIN_PORT_REQUIRE_PRIVILEGES: "埠監聽失敗，監聽 {} 埠需要權限提升。請更換一個 WebUI 埠或以管理員身份執行 PeerBanHelper"
-SPRING_CONTEXT_LOADING: "Spring Context 正在進行上下文裝配以啟動 PeerBanHelper，在低端裝置上可能需要一段時間"
-PBH_STARTUP_FATAL_ERROR: "PeerBanHelper 啟動過程中遇到致命錯誤，無法繼續啟動"
-UNABLE_TO_PUSH_ALERT_VIA_PROVIDERS: "透過通知推送渠道推送消息失敗"
-UNABLE_TO_PUSH_ALERT_VIA: "通過 {} 通知推送渠道推送消息失敗"
-UNABLE_TO_PUSH_ALERT: "推送消息失敗"
-UNKNOWN_PUSH_PROVIDER: "未知類型的推送渠道: {} - {}"
-UNABLE_EXECUTE_MODULE: "無法執行模組 {}，請回饋此錯誤給 PeerBanHelper 開發者"
-UNABLE_RETRIEVE_PEERS: "獲取 Peers 失敗"
-UNABLE_COMPLETE_SCHEDULE_TASKS: "執行計劃任務時出現未知錯誤"
-UNABLE_COMPLETE_PEER_BAN_TASK: "執行 Peer 封禁任務時出現錯誤，請回饋此錯誤給 PeerBanHelper 開發者"
-UNFINISHED_RUNNABLE: "未完成的任務: {}"
-UNABLE_CLOSE_DOWNLOADER: "關閉下載器 {} 時出現錯誤"
-UNABLE_LOAD_BTN_ABILITY: "載入 BTN 能力 {} 時出現錯誤"
-MISSING_VERSION_PROTOCOL_FIELD: "來自 BTN 伺服器的響應中缺失必須的 min_protocol_version 欄位，請與 BTN 伺服器管理員聯繫"
-BTN_NO_CONTENT_REPORTED_YET: "暫未向遠端伺服器上報任何內容"
-BTN_LAST_REPORT_EMPTY: "上一次向遠端伺服器上報時的上報的內容為空，上報已被跳過"
-BTN_HTTP_ERROR: "HTTP 錯誤: {} - {}"
-BTN_UNKNOWN_ERROR: "未知錯誤: {}"
-BTN_REPORTED_DATA: "已上報 {} 條資料"
-BTN_SUBMIT_PEERS_FAILED: "上報 Peers 資料時出現錯誤"
-BTN_SUBMIT_BANS_FAILED: "上報封禁列表資料時出現錯誤"
-BTN_RULES_LOADED_FROM_CACHE: "已從本機磁碟快取載入"
-BTN_RULES_LOADED_FROM_REMOTE: "已從遠端伺服器載入，版本: {}"
-BTN_RULES_LOAD_FROM_CACHE_FAILED: "從本機磁碟快取載入時出現錯誤"
-BTN_STAND_BY: "就緒"
-BTN_RECONFIGURE_DISABLED_BY_SERVER: "BTN 伺服器已停用重新配置"
-BTN_RECONFIGURE_PREPARE_RECONFIGURE: "檢測到新的版本，正在重新配置"
-UNABLE_SET_SQLITE_OPTIMIZED_PRAGMA: "設定 SQLite 最佳化的 PRAGMA 參數時出現錯誤"
-THREAD_INTERRUPTED: "執行緒強制終止，操作已被取消"
-DOWNLOADER_BITCOMET_UNABLE_FETCH_TASK_SUMMARY: "無法獲取 BitComet 任務詳情資訊"
-ALERT_SNAPSHOT: "您目前正在使用的是 PeerBanHelper 的非穩定版本"
-ALERT_SNAPSHOT_DESCRIPTION: "非穩定版本的構建僅用於測試，且可能存在嚴重缺陷。在繼續使用非穩定版本前，請做好資料備份。如遇到錯誤，請前往 [Issue Tracker](https://github.com/PBH-BTN/PeerBanHelper/issues) 回饋。"
-BTN_ABILITY_RECONFIGURE: "重新配置"
-BTN_ABILITY_RECONFIGURE_DESCRIPTION: "「重新配置」能力允許 BTN 伺服器通知 PeerBanHelper 在間隔一定時間後與 BTN 伺服器聯繫，重新整理使用的設定檔，以獲取遠端伺服器的配置更改"
-BTN_ABILITY_RULES: "雲端規則"
-BTN_ABILITY_RULES_DESCRIPTION: |
-  「雲端規則」能力允許 BTN 伺服器向 PeerBanHelper 定期提供由遠端伺服器生成的反吸血規則，作為本機反吸血規則的補充。  
-  
-  規則版本：**{}**, 目前已載入 **{}** 條規則：
-  
-  * IP 規則: {} 條
-  * PeerID 規則: {} 條
-  * ClientName 規則: {} 條
-  * 埠規則: {} 條
-  * 雲定義腳本: {} 個
-BTN_ABILITY_SUBMIT_BANS: "提交封禁列表"
-BTN_ABILITY_SUBMIT_BANS_DESCRIPTION: "「提交封禁列表」能力允許 BTN 伺服器定期接收 PeerBanHelper 的封禁列表和封禁時的資料快照。這些資料有助於 BTN 伺服器分析網路上的惡意行為，並動態生成反吸血規則阻斷這些惡意行為。"
-BTN_ABILITY_SUBMIT_HISTORY: "提交歷史記錄"
-BTN_ABILITY_SUBMIT_HISTORY_DESCRIPTION: "「提交歷史記錄」能力允許 BTN 伺服器定期接收 PeerBanHelper 所跟蹤的下載器上的 Peers 活動和最後一次記錄的活動資料快照。這些資料有助於 BTN 伺服器分析網路上的惡意行為，並動態生成反吸血規則阻斷這些惡意行為。同時，這些資料還有助於分析是否存在利用 PeerBanHelper 反吸血功能的漏洞，繞過反吸血機制的在野利用。"
-BTN_ABILITY_SUBMIT_SNAPSHOT: "提交快照"
-BTN_ABILITY_SUBMIT_SNAPSHOT_DESCRIPTION: "「提交快照」能力允許 BTN 伺服器定期接收 PeerBanHelper 所跟蹤的下載器上的瞬間狀態快照。這些資料有助於 BTN 伺服器分析網路上的惡意行為，並動態生成反吸血規則阻斷這些惡意行為。同時，這些資料還有助於 BTN 分析目前網路上的活動，以了解不同時刻網路上的惡意活動的概況。"
-BTN_ABILITY_EXCEPTION: "例外"
-BTN_ABILITY_EXCEPTION_DESCRIPTION: "「例外」能力允許 BTN 伺服器通知 PeerBanHelper 將某些地址排除在檢查列表之外。如果指定的地址已被封禁，則將解除封禁。我們使用此功能來處理潛在的破壞行為。如果有人惡意利用 BTN 網路，我們將使用此能力將受到影響的紀錄從 PeerBanHelper 中刪除。"
-BTN_ABILITY_EXCEPTION_LOADED_FROM_CACHE: "已從本機磁碟快取載入"
-BTN_ABILITY_EXCEPTION_LOADED_FROM_CACHE_FAILED: "從本機磁碟快取載入例外列表失敗"
-BTN_ABILITY_EXCEPTION_LOADED_FROM_REMOTE: "已從遠端伺服器載入，版本: {}"
-BTN_ABILITY_EXCEPTION_UPDATE_RULES_SUCCESSES: "[BTN 網路] (例外)規則資料更新成功，目前資料版本：{}"
-BTN_ABILITY_EXCEPTION_UNBANNED_PEERS: "[BTN 網路] 已解除了 {} 個 Peer 的封禁，因為它們被 BTN 網路列入例外列表"
-UNABLE_READ_ALERT: "無法更新警報的已讀狀態"
-BTN_NOT_ENABLE_AND_REQUIRE_RESTART: "目前 BTN 模組未被載入，請啟用 BTN 模組並重新啟動 PeerBanHelper"
-RELOAD_RESULT_SUCCESS: "設定檔已儲存並成功重載"
-RELOAD_RESULT_SCHEDULED: "設定檔已儲存並成功重載，但部分修改可能需要一段時間生效"
-RELOAD_RESULT_REQUIRE_RESTART: "設定檔已儲存並成功重載，但部分修改需要重啟以生效"
-RELOAD_RESULT_FAILED: "設定檔已儲存，但重載過程中出現了錯誤。請檢查控制台日誌"
-WS_LOGS_STREAM_ACCESS_DENIED: "操作被拒絕，鑒權失敗"
-ALERT_MANAGER_CLEAN_UP: "已清理 {} 條歷史警報記錄"
-MODULE_AMM_TRAFFIC_MONITORING_TRAFFIC_ALERT_TITLE: "下載器上行流量超限告警 ({})"
-MODULE_AMM_TRAFFIC_MONITORING_TRAFFIC_ALERT_DESCRIPTION: |
-  PeerBanHelper 監測到託管的下載器的總上傳流量於 {} 達到當日告警閾值。
-
-  **已上傳流量：{} (告警閾值: {})**
-
-  請檢查下載器目前執行狀態。  
-  若需要修改告警設定，請前往 [主動監控](/config?tab=profile) 頁面修改。
-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: "PeerBanHelper 可用記憶體嚴重不足"
-PROGRAM_OUT_OF_MEMORY_DESCRIPTION: |
-  PeerBanHelper 的可用執行記憶體嚴重不足，並可能隨時停止執行。
-
-  可用記憶體: {}MB  
-  最大記憶體: {}MB
-
-  請立即增加 PeerBanHelper 的記憶體分配。
-PUSH_PROVIDER_TEST_TITLE: "PeerBanHelper 推送服務測試消息"
-PUSH_PROVIDER_TEST_DESCRIPTION: "如果你看到了這條訊息，代表推送服務 {} ({}) 配置正確。"
-PUSH_PROVIDER_TEST_SUCCESS: "{} ({}) 發送成功，請檢查是否收到消息"
-PUSH_PROVIDER_TEST_FAILED: "測試推送服務 {} ({}) 失敗"
-PUSH_PROVIDER_TEST_ERROR: "測試推送服務 {} ({}) 失敗: {}"
-BAN_PEER_EXCEPTION: "封禁 Peer 時出現意外錯誤，指定 Peer 可能封禁失敗，請將下面的資訊報告給 PeerBanHelper 開發者"
-EXPRESS_RULE_ENGINE_DISALLOW_UNSAFE_SOURCE_ACCESS: "因安全原因，不允許透過網路或者反向代理建立 AviatorScript 腳本，請閱讀：https://docs.pbh-btn.com/docs/faq"
-EXPRESS_RULE_ENGINE_SAVED: "腳本已儲存"
-BTN_RULES_SCRIPT_COMPILING: "正在編譯來自 BTN 伺服器的可編程腳本，請稍等，這可能需要一點時間……"
-BTN_RULES_SCRIPT_COMPILED: "已成功編譯 {} 個腳本，用時 {}ms"
-BTN_SERVICES_NEED_RESTART: "BTN 服務不可用：需要重啟 PeerBanHelper 以載入 BTN 模組並應用更改"
-GUI_MENU_QUICK_OPERATIONS: "快速操作"
-GUI_MENU_SHOW_WINDOW: "打開主視窗"
-GUI_MENU_STATS: "統計資料"
-GUI_MENU_STATS_BANNED: "已封禁 {} 個 Peer ({} 個 IP 位址)"
-GUI_MENU_STATS_DOWNLOADER: "已連接 {}/{} 個下載器"
-
-PUSH_PROVIDER_API_ADD_FAILURE: "推送渠道建立失敗，是否傳遞的是一個不受支援的推送類型？"
-PUSH_PROVIDER_API_CREATED: "推送渠道建立成功"
-PUSH_PROVIDER_API_UPDATED: "推送渠道更新成功"
-PUSH_PROVIDER_API_CREATION_FAILED_ALREADY_EXISTS: "推送渠道建立失敗，已有相同的推送渠道配置存在"
-PUSH_PROVIDER_API_CREATION_FAILED_IO_EXCEPTION: "推送渠道建立失敗，出現 I/O 錯誤，請檢查控制台日誌"
-PUSH_PROVIDER_API_UPDATE_FAILURE: "推送渠道更新失敗，是否傳遞的是一個不受支援的推送類型？"
-PUSH_PROVIDER_API_UPDATE_FAILURE_ALREADY_EXISTS: "推送渠道更新失敗，已有一個相同的推送渠道配置存在，且移除失敗"
-PUSH_PROVIDER_API_TEST_NAME_EXISTS: "推送渠道所使用的名稱與另一個已存在的推送渠道衝突"
-PUSH_PROVIDER_API_TEST_OK: "驗證成功，配置有效"
-PUSH_PROVIDER_API_REMOVE_NOT_EXISTS: "無法移除指定的推送渠道，指定的推送渠道並沒有在 PeerBanHelper 中註冊"
-PUSH_PROVIDER_API_REMOVE_SAVED: "移除成功，配置已儲存"
-PUSH_PROVIDER_API_PROVIDER_NOT_EXISTS: "請求的推送渠道未在 PeerBanHelper 中註冊"
-PORT_MAPPER_PORT_MAPPING: "[PortMapper] 正在嘗試為埠 {}/{} 建立自動埠映射……"
-PORT_MAPPER_PORT_MAPPED: "[PortMapper] 已在網卡 {} 的閘道器上上為本機埠 {}/{} 建立了一個埠映射，外部埠為：{} (外部 IP: {})；TTL：{} 秒"
-PORT_MAPPER_PORT_MAPPED_BUT_INTERNAL_ADDRESS: "[PortMapper] 需要注意！儘管 PortMapper 已成功建立了一個或者多個埠映射，但它們的外部地址似乎都是專用 IP 位址，這通常意味著您處於雙層 NAT 之後（例如：您的裝置連接者一台路由器，但這台路由器處於同為路由模式的光纖數據機之下），埠映射可能不起作用。"
-PORT_MAPPER_PORT_MAPPING_FAILED: "[PortMapper] 在網卡 {} 上建立/更新埠映射 {}/{} 失敗，正在重試；如果頻繁出現此提示，請檢查是否裝置接入了多個網路（如：Tailscale）"
-LAB_EXPERIMENT_IPFS_TITLE: "IPFS 去中心化網路"
-LAB_EXPERIMENT_IPFS_DESCRIPTION: |
-  [IPFS (星際文件系統)](https://ipfs.tech/) 是一個無中心伺服器的分布式網路。每一台電腦都是 IPFS 網路中的一個節點。  
-  IPFS 靈活且富有彈性，您可以透過 IPFS 儲存和尋找文件和傳遞資料或資源，並且隨著資源變得逐漸熱門，速度和可存取性都會自動改善。當一個資料不再被任何節點需要（且無人刻意保留它）時，它也會隨著時間推移而從網路中自動淘汰移除。  
-  不需要伺服器，您也可以分享自己的封禁列表或者 IP 規則給其它人。  
-  
-  要使用 IPFS 功能，請啟用此實驗，並在設定檔中配置 Kubo IPFS 客戶端的 RPC 地址。  
-  啟用後您將能夠訂閱其它人分享給您的位於 IPFS/IPNS 之上的 IP 規則。
-IPFS_INIT_FAILED: "[IPFS] 初始化 Kubo IPFS RPC 失敗，IPFS 功能將不可用，請確認 Kubo IPFS 是否正在執行，若您不使用 IPFS 相關功能，可忽略此警告資訊: {}"
-IPFS_INIT_WELCOME: "[IPFS] 已成功連接到 Kubo IPFS RPC，版本 {}。PeerBanHelper 已成功透過 Kubo 連接 IPFS 網路，並提供擴充功能"
-IPFS_BANLIST_PUBLISHED: "[IPFS] 已更新封禁列表到 IPFS，並一同更新了 IPNS 記錄"
-MODULE_IBL_UPDATE_IPFS_NOT_AVAILABLE: "無法從 IPFS/IPNS 更新規則訂閱，因為 IPFS 功能未啟用"
-
-FREE_LICENSE_RENEW_SUCCESS: "新的許可證已生成並啟用"
-FREE_LICENSE_RENEW_STILL_ACTIVE: "目前許可證仍在有效期內，無需重新生成"
-FREE_LICENSE_SOURCE: "本機生成的免費許可證"
-FREE_LICENSE_DESCRIPTION: "本機生成的循環免費許可證，僅用於個人用途"
-DOWNLOADER_PAUSED: "下載器已暫停"
-LAB_EXPERIMENT_DNSJAVA_TITLE: "DNSJava DNS 解析"
-LAB_EXPERIMENT_DNSJAVA_DESCRIPTION: "在部分功能模組中使用 [dnsjava/dnsjava](https://github.com/dnsjava/dnsjava) 替代 JDK 預設的 DNS 解析器，以提供更可靠的主機名稱和反向 DNS 查詢能力，並支援自訂 DNS 伺服器和負載均衡的能力。DNSJava 僅在部分功能上工作（如 DNS 反查），其它功能將繼續使用 JDK DNS。  \n啟用此實驗後，您可以在設定檔中配置 DNSJava 的 DNS 伺服器地址。"
-LAB_EXPERIMENT_SQLITE_VACUUM_TITLE: "SQLite VACUUM 資料庫最佳化"
-LAB_EXPERIMENT_SQLITE_VACUUM_DESCRIPTION: "PeerBanHelper 將在啟動時檢查 SQLite 資料文件是否需要真空（VACUUM），預設每 60 天進行一次真空操作（可修改 `persist.vacuum-interval-days` 來改變真空間隔時間）。真空操作將最佳化資料庫文件並減少檔案大小，並同時提高查詢性能。"
-SQLITE_VACUUM_BACKUP: "正在備份 SQLite 資料庫文件以便對資料庫進行真空操作……"
-SQLITE_VACUUM_BACKUP_COMPLETED: "SQLite 資料庫文件備份完成，準備進行真空……"
-SQLITE_VACUUM_BACKUP_FAILED: "SQLite 資料庫文件備份失敗，無法進行真空操作，這可能是由於磁碟空間不足，IO 錯誤或者權限不足導致的，請檢查錯誤資訊。"
-SQLITE_VACUUM_IN_PROGRESS: "正在對 SQLite 資料庫進行真空操作，請勿關閉 PeerBanHelper 否則將可能導致資料庫損壞！ 【進行中】...."
-SQLITE_VACUUM_SUCCESS: "SQLite 資料庫已成功真空，原檔案大小: {} 真空後檔案大小: {}"
-MODULE_PTR_MATCH_PTR_RULE: "匹配 PTR 規則: {}"
-
-MATCH_CONDITION_PORT_MATCH: "埠匹配"
-MATCH_CONDITION_BOOLEAN: "布林值"
-MATCH_CONDITION_BOOLEAN_BY_INTEGER: "布林值 (整數轉換)"
-MATCH_CONDITION_BOOLEAN_BY_STRING: "布林值 (字串轉換)"
-MATCH_STRING_CONTAINS: "字串包含: {}"
-MATCH_STRING_ENDS_WITH: "字串結尾: {}"
-MATCH_STRING_EQUALS: "字串匹配: {}"
-MATCH_STRING_LENGTH: "字串長度: {}"
-MATCH_STRING_REGEX: "字串正規表示式: {}"
-MODULE_IBL_COMMENT_UNKNOWN: "未提供"
-JSON_MATCHER_NOT_MET: "表達式未滿足: {} 於 {} 上"
-DOWNLOADER_API_TEST_BYPASS_PAUSED: "下載器配置已暫存，請在稍後啟用"
-STATUS_BAR_GLOBAL_PAUSED: "⏸全域暫停"
-BTN_CONFIG_STATUS_UNSUCCESSFUL_HTTP_REQUEST: "向 URL {} 發送 HTTP 請求失敗，HTTP 狀態碼: {}，遠端響應：{}"
-BTN_CONFIG_STATUS_UNSUCCESSFUL_INCOMPATIBLE_BTN_PROTOCOL_VERSION_CLIENT: "目前客戶端 BTN 協議實現版本過舊，本機實現版本號: {}，伺服器要求最低: {}"
-BTN_CONFIG_STATUS_UNSUCCESSFUL_INCOMPATIBLE_BTN_PROTOCOL_VERSION_SERVER: "目前伺服器 BTN 協議實現版本過舊，伺服器實現版本號: {}，本機要求最低: {}"
-BTN_CONFIG_STATUS_SUCCESSFUL: "已成功連接到 BTN 伺服器"
-BTN_CONFIG_STATUS_EXCEPTION: "配置 BTN 功能時發生意外錯誤：{}: {}"
-DOWNLOADER_QB_FAILED_REQUEST_TRACKER_LIST_ON_TORRENT: "請求種子 {} 的 Tracker 列表失敗：{} - {}"
-TRACKER_FAILED_TO_UPDATE_TRACKER: "請求更新種子 {} 的 Tracker 列表失敗：{} - {}"
-UTILITIES_TRACKER_REPLACED: "已成功取代 {} 個種子的 Tracker"
-DOWNLOADER_BIGLYBT_INCORRECT_ADAPTER_VERSION: "目前使用的 BiglyBT 適配器版本不受支援，請升級到 {} 或更高版本"
-LAB_EXPERIMENT_FILL_MISSING_DATA_IN_TRAFFIC_SUMMARY_TITLE: "使用已知資料填充流量統計缺失的資料"
-LAB_EXPERIMENT_FILL_MISSING_DATA_IN_TRAFFIC_SUMMARY_DESCRIPTION: "使用上一條的已知的資料嘗試填充流量統計中缺失的資料以嘗試修正錯誤的流量統計資料（僅在非開頭區域中缺失記錄時有效）。這是一種實驗性演算法，且提供的資料可能仍舊不準確。"
-GUI_TABBED_WEBUI: "WebUI 控制台"
-JCEF_BROWSER_UNSUPPORTED_PLATFORM: "JCEF 初始化失敗：平台不受支援"
-JCEF_BROWSER_UNSUPPORTED_EXCEPTION: "JCEF 初始化失敗：發生 CEF 初始化錯誤"
-JCEF_DOWNLOAD_TITLE: "下載 JCEF 封包"
-JCEF_DOWNLOAD_DESCRIPTION: |
-  PeerBanHelper 正在下載 JCEF 封包以在 GUI 視窗中啟用 WebUI 控制台標籤卡，這可能需要一點時間...<br/>
-  如果不希望啟用 JCEF 功能，請新增啟動附加參數 nojcef。
-JCEF_DOWNLOAD_UNZIP_DESCRIPTION: 正在解壓縮 JCEF 封包，請稍等...
-IPDB_DOWNLOAD_TITLE: "下載 GeoIP 資料庫: {}"
-IPDB_DOWNLOAD_DESCRIPTION: "PeerBanHelper 需要下載 GeoIP 資料庫以為反吸血提供 IP 位址的地理位置資訊支援，這可能需要一點時間..."
-GUI_COMMON_CANCEL: "取消"
-PBH_PLUS_THANKS_FOR_DONATION_GUI_TITLE: "❤感謝捐贈"
-GUI_TITLE_DEBUG: "🔧除錯模式"
-DOWNLOADER_DOCKER_INCORRECT_NETWORK_DETECTED_TITLE: "嚴重錯誤：下載器處於不正確的網路模式下，PeerBanHelper 可能不會工作"
-DOWNLOADER_DOCKER_INCORRECT_NETWORK_DETECTED_DESCRIPTION: |
-  檢測到嚴重錯誤：下載器 {} 的 Peer 入站連接 IP 位址配置不正確 ({})。  
-  PeerBanHelper 需要 Peer 的真實 IP 位址才能正常工作，否則將會錯誤的封禁所有入站連接，因此 PeerBanHelper 正在忽略這些異常的地址。封禁不會生效。  
-  這通常是由於下載器執行在 Docker 環境內且正在使用 `bridge` 網路模式導致的，必須切換到 `host` 網路模式以避免使用者空間轉發改寫 IP 位址資訊。  
-  
-  請注意：你應該正確修復 IP 問題，僅將內部網路 IP 位址從忽略列表中移除會導致 PeerBanHelper 錯誤地封禁全部入站連接。  
-  如需幫助，請加入 PeerBanHelper 社群尋求幫助。**此提示在每個下載器上僅會提示一次。**
-ABOUT_VIEW_CREDIT: "開發人員與社群貢獻者..."
-LAB_EXPERIMENT_TRANSACTION_BATCH_BAN_HISTORY_WRITE_TITLE: "啟用事務批次寫入封禁歷史記錄"
-LAB_EXPERIMENT_TRANSACTION_BATCH_BAN_HISTORY_WRITE_DESCRIPTION: "在記錄封禁歷史記錄時使用事務以批次寫入資料庫以提高性能和減少隨機磁碟 I/O 操作。"
-TITLE_APP_V_CONTAINER: "📦App-V"
\ No newline at end of file
diff --git a/src/main/resources/lang/zh_cn/messages.yml b/src/main/resources/lang/zh_cn/messages.yml
index f5d2175303..b8d2d71b8d 100644
--- a/src/main/resources/lang/zh_cn/messages.yml
+++ b/src/main/resources/lang/zh_cn/messages.yml
@@ -44,11 +44,6 @@ DOWNLOADER_QB_FAILED_SAVE_BANLIST: "无法保存 {} ({}) 的 Banlist！{} - {}\n
 DOWNLOADER_TR_MOTD_WARNING: "[受限] 由于 Transmission 的 RPC-API 限制，PeerId 黑名单功能和 ProgressCheatBlocker 功能的过量下载模块不可用"
 DOWNLOADER_TR_DISCONNECT_PEERS: "[重置] 正在断开 Transmission 上的 {} 个种子连接的对等体，以便应用 IP 屏蔽列表的更改"
 DOWNLOADER_TR_INCORRECT_BANLIST_API_RESP: "设置 Transmission 的 BanList 地址时，返回非成功响应：{}。"
-DOWNLOADER_TR_INCORRECT_SET_BANLIST_API_RESP: |
-  到 Transmission，PBH 没有生效
-  更新 BanList 时，返回非成功响应
-  erBanHelper 的外部交互端口，以便 Transmission 从 PBH 拉取 IP 黑名单
-  的 设置 -> 隐私 -> 屏蔽列表 中自动填写的 URL 是否正确，如果不正确，请在 PeerBanHelper 的 config.yml 中正确配置 server 部分的配置文件，确保 Transmission 能够正确连接到 IP 黑名单提供端
 DOWNLOADER_TR_INVALID_RESPONSE: "[错误] Transmission 返回无效 JSON 响应: {}"
 DOWNLOADER_TR_UPDATED_BLOCKLIST: "[响应] Transmission 屏蔽列表已更新成功，现在包含 {} 条规则"
 DOWNLOADER_TR_KNOWN_INCOMPATIBILITY: "[错误] 您正在使用的 Transmission 版本与 PeerBanHelper 不兼容: {}"
@@ -579,4 +574,19 @@ DOWNLOADER_DOCKER_INCORRECT_NETWORK_DETECTED_DESCRIPTION: |
 ABOUT_VIEW_CREDIT: "开发人员与社区贡献者..."
 LAB_EXPERIMENT_TRANSACTION_BATCH_BAN_HISTORY_WRITE_TITLE: "启用事务批量写入封禁历史记录"
 LAB_EXPERIMENT_TRANSACTION_BATCH_BAN_HISTORY_WRITE_DESCRIPTION: "在记录封禁历史记录时使用事务以批量写入数据库以提高性能和减少随机磁盘 I/O 操作。"
-TITLE_APP_V_CONTAINER: "📦App-V"
\ No newline at end of file
+TITLE_APP_V_CONTAINER: "📦App-V"
+
+ALERT_DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED_TITLE: "下载器 {} IP 封禁列表更新失败"
+ALERT_DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED_DESCRIPTION: |
+  PeerBanHelper 无法更新 {} 的 IP 封禁列表，因为 Transmission 通知我们封禁列表更新失败。
+  请检查并确保已正确配置 "设置 -> 基础设置 -> WebUI -> 前缀" 配置项，确保指向 PeerBanHelper WebUI 的地址，并可以被 Transmission 访问和连接（仅通过浏览器访问确认是不够的）。
+  如果此配置不正确，则 Transmission 将无法正确封禁和解除封禁任何 IP 地址。
+  
+  如果需要任何配置帮助，请参阅支持文档或在社区中请求帮助。
+
+DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED: |
+  PeerBanHelper 无法更新 {} 的 IP 封禁列表，因为 Transmission 通知我们封禁列表更新失败。
+  请检查并确保已正确配置 "设置 -> 基础设置 -> WebUI -> 前缀" 配置项，确保指向 PeerBanHelper WebUI 的地址，并可以被 Transmission 访问和连接（仅通过浏览器访问确认是不够的）。
+  如果此配置不正确，则 Transmission 将无法正确封禁和解除封禁任何 IP 地址。
+  
+  如果需要任何配置帮助，请参阅支持文档或在社区中请求帮助。
\ No newline at end of file
diff --git a/src/main/resources/lang/zh_tw/messages.yml b/src/main/resources/lang/zh_tw/messages.yml
index 2c7e201366..fd940d11dc 100644
--- a/src/main/resources/lang/zh_tw/messages.yml
+++ b/src/main/resources/lang/zh_tw/messages.yml
@@ -591,4 +591,19 @@ DOWNLOADER_DOCKER_INCORRECT_NETWORK_DETECTED_DESCRIPTION: |
 ABOUT_VIEW_CREDIT: "開發人員與社群貢獻者..."
 LAB_EXPERIMENT_TRANSACTION_BATCH_BAN_HISTORY_WRITE_TITLE: "啟用事務批次寫入封禁歷史記錄"
 LAB_EXPERIMENT_TRANSACTION_BATCH_BAN_HISTORY_WRITE_DESCRIPTION: "在記錄封禁歷史記錄時使用事務以批次寫入資料庫以提高性能和減少隨機磁碟 I/O 操作。"
-TITLE_APP_V_CONTAINER: "📦App-V"
\ No newline at end of file
+TITLE_APP_V_CONTAINER: "📦App-V"
+
+ALERT_DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED_TITLE: "下载器 {} IP 封禁列表更新失败"
+ALERT_DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED_DESCRIPTION: |
+  PeerBanHelper 无法更新 {} 的 IP 封禁列表，因为 Transmission 通知我们封禁列表更新失败。
+  请检查并确保已正确配置 "设置 -> 基础设置 -> WebUI -> 前缀" 配置项，确保指向 PeerBanHelper WebUI 的地址，并可以被 Transmission 访问和连接（仅通过浏览器访问确认是不够的）。
+  如果此配置不正确，则 Transmission 将无法正确封禁和解除封禁任何 IP 地址。
+  
+  如果需要任何配置帮助，请参阅支持文档或在社区中请求帮助。
+
+DOWNLOADER_TRANSMISSION_BLOCKLIST_UPDATE_FAILED: |
+  PeerBanHelper 无法更新 {} 的 IP 封禁列表，因为 Transmission 通知我们封禁列表更新失败。
+  请检查并确保已正确配置 "设置 -> 基础设置 -> WebUI -> 前缀" 配置项，确保指向 PeerBanHelper WebUI 的地址，并可以被 Transmission 访问和连接（仅通过浏览器访问确认是不够的）。
+  如果此配置不正确，则 Transmission 将无法正确封禁和解除封禁任何 IP 地址。
+  
+  如果需要任何配置帮助，请参阅支持文档或在社区中请求帮助。
\ No newline at end of file
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
index 90c171fd78..fc1b3bc0aa 100644
--- a/src/main/resources/logback.xml
+++ b/src/main/resources/logback.xml
@@ -1,4 +1,7 @@
-<configuration debug="true">
+<configuration debug="false">
+
+    <statusListener class="ch.qos.logback.core.status.NopStatusListener"/>
+
     <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
         <encoder>
             <pattern>[%d{HH:mm:ss}] [%t/%level]: %msg%n</pattern>
diff --git a/webui/src/views/dashboard/components/editDownloaderModal.vue b/webui/src/views/dashboard/components/editDownloaderModal.vue
index 8389227382..244bdf907c 100644
--- a/webui/src/views/dashboard/components/editDownloaderModal.vue
+++ b/webui/src/views/dashboard/components/editDownloaderModal.vue
@@ -21,9 +21,9 @@
           <a-option :value="ClientTypeEnum.BiglyBT">BiglyBT</a-option>
           <a-option :value="ClientTypeEnum.Deluge">Deluge</a-option>
           <a-option :value="ClientTypeEnum.BitComet">BitComet</a-option>
-          <a-tooltip :content="t('page.dashboard.editModal.transmission.discourage')">
-            <a-option :value="ClientTypeEnum.Transmission" disabled>Transmission</a-option>
-          </a-tooltip>
+          <!--          <a-tooltip :content="t('page.dashboard.editModal.transmission.discourage')">-->
+          <a-option :value="ClientTypeEnum.Transmission">Transmission</a-option>
+          <!--          </a-tooltip>-->
         </a-select>
         <template v-if="form.config.type === ClientTypeEnum.BiglyBT" #extra>
           <i18n-t keypath="page.dashboard.editModal.biglybt">
diff --git a/webui/src/views/oobe/components/addDownloader.vue b/webui/src/views/oobe/components/addDownloader.vue
index fe0cd64cfc..e32092e980 100644
--- a/webui/src/views/oobe/components/addDownloader.vue
+++ b/webui/src/views/oobe/components/addDownloader.vue
@@ -20,9 +20,9 @@
           <a-option :value="ClientTypeEnum.BiglyBT">BiglyBT</a-option>
           <a-option :value="ClientTypeEnum.Deluge">Deluge</a-option>
           <a-option :value="ClientTypeEnum.BitComet">BitComet</a-option>
-          <a-tooltip :content="t('page.dashboard.editModal.transmission.discourage')">
-            <a-option :value="ClientTypeEnum.Transmission" disabled>Transmission</a-option>
-          </a-tooltip>
+          <!--          <a-tooltip :content="t('page.dashboard.editModal.transmission.discourage')">-->
+          <a-option :value="ClientTypeEnum.Transmission">Transmission</a-option>
+          <!--          </a-tooltip>-->
         </a-select>
         <template v-if="config.downloaderConfig.config.type === ClientTypeEnum.BiglyBT" #extra>
           <i18n-t keypath="page.dashboard.editModal.biglybt">


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 15 out of 17 changed files in this pull request and generated 4 comments.

<details>
<summary>Files not reviewed (2)</summary>

* **.github/dependabot.yml**: Language not supported
* **pom.xml**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**src/main/java/cordelia/client/TrClient.java:110**
* Switching from error-level to debug-level logging for Transmission RPC failures might obscure critical error details in production. Consider retaining error-level logging for failure events.
```
log.debug("Request Transmission JsonRPC failure", e);
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T20:05:27Z
user: login: coderabbitai[bot]
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T20:08:38Z
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 15 out of 19 changed files in this pull request and generated 2 comments.

<details>
<summary>Files not reviewed (4)</summary>

* **.github/dependabot.yml**: Language not supported
* **pkg/deb/usr/lib/systemd/system/peerbanhelper.service**: Language not supported
* **pom.xml**: Language not supported
* **src/main/resources/logback.xml**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**src/main/java/cordelia/client/TrClient.java:110**
* Changing the log level from error to debug could hide critical RPC errors; please confirm that this change is intended for production diagnostics.
```
log.debug("Request Transmission JsonRPC failure", e);
```
**src/main/java/com/ghostchu/peerbanhelper/ipdb/IPDB.java:228**
* [nitpick] Please consider refining this informal comment for clarity and professionalism in the production code.
```
// 这坨代码已经改成一坨了，有时间得写个好点的 :(
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-05T09:46:48Z

Review comments:
diff_hunk: @@ -110,31 +96,49 @@ public void setPaused(boolean paused) {
         config.setPaused(paused);
     }
 
-    @SneakyThrows(InterruptedException.class)
     @Override
     public DownloaderLoginResult login0() {
         RqSessionGet get = new RqSessionGet();
         TypedResponse<RsSessionGet> resp = client.execute(get); // 执行任意 RPC 操作以刷新 session
         String version = resp.getArgs().getVersion();
-        if (version.startsWith("0.") || version.startsWith("1.") || version.startsWith("2.")) {
-            return new DownloaderLoginResult(DownloaderLoginResult.Status.EXCEPTION, new TranslationComponent(Lang.DOWNLOADER_TR_KNOWN_INCOMPATIBILITY, "API Version"));
+        Semver semver = new Semver(version, Semver.SemverType.LOOSE);
+        // must 4.1.0 or higher
+        if (semver.getMajor() < 4) {
+            return new DownloaderLoginResult(DownloaderLoginResult.Status.EXCEPTION, new TranslationComponent(Lang.DOWNLOADER_TR_KNOWN_INCOMPATIBILITY, "Transmission version must higher then (or equals to) 4.1.0"));
path: src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/Transmission.java
user: login: Copilot
body: The error message text uses incorrect grammar. Consider revising it to 'Transmission version must be higher than or equal to 4.1.0'.
```suggestion
            return new DownloaderLoginResult(DownloaderLoginResult.Status.EXCEPTION, new TranslationComponent(Lang.DOWNLOADER_TR_KNOWN_INCOMPATIBILITY, "Transmission version must be higher than or equal to 4.1.0"));
```
created_at: 2025-04-04T20:05:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -221,7 +222,11 @@ private IPGeoData.CountryData queryCountry(InetAddress address) {
             countryData.setIso(country.getIsoCode());
             String countryRegionName = country.getName();
             // 对 TW,HK,MO 后处理，偷个懒
-            if (languageTag.getFirst().equals("zh-CN") && (country.getIsoCode().equals("TW") || country.getIsoCode().equals("HK") || country.getIsoCode().equalsIgnoreCase("MO"))) {
+            var code = languageTag.getFirst();
+            code = code.toLowerCase(Locale.ROOT).replace("-", "_");
+            // 台湾、香港、澳门地区有一个独立 ISO 代码，需要手动处理一下保证符合所在地法律法规
+            // 这坨代码已经改成一坨了，有时间得写个好点的 :(
path: src/main/java/com/ghostchu/peerbanhelper/ipdb/IPDB.java
user: login: Copilot
body: [nitpick] The inline comment is informal and may not be appropriate for production code. Consider refining or removing it for improved clarity and professionalism.
created_at: 2025-04-04T20:05:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -13,7 +13,7 @@
 import java.util.List;
 
 public abstract class AbstractDownloader implements Downloader {
-    private final AlertManager alertManager;
+    public final AlertManager alertManager;
path: src/main/java/com/ghostchu/peerbanhelper/downloader/AbstractDownloader.java
user: login: Copilot
body: Making alertManager public reduces encapsulation and can lead to unintended dependencies. Consider reverting to a private access modifier and providing a getter if access is needed.
created_at: 2025-04-04T20:05:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -207,112 +215,26 @@ public DownloaderStatistics getStatistics() {
 
     @Override
     public void relaunchTorrentIfNeeded(Collection<Torrent> torrents) {
-        relaunchTorrents(torrents.stream()
-                .filter(t -> {
-                    try {
-                        Long.parseLong(t.getId());
-                        return true;
-                    } catch (Exception e) {
-                        return false;
-                    }
-                }).map(t -> Long.parseLong(t.getId())).toList());
-    }
 
-    private void relaunchTorrents(Collection<Long> ids) {
-        if (ids.isEmpty()) return;
-        RqTorrentGet torrentList = new RqTorrentGet(Fields.ID, Fields.HASH_STRING, Fields.NAME,
-                Fields.PEERS_CONNECTED,
-                Fields.STATUS, Fields.TOTAL_SIZE, Fields.PEERS, Fields.RATE_DOWNLOAD,
-                Fields.RATE_UPLOAD, Fields.PEER_LIMIT, Fields.PERCENT_DONE, Fields.IS_PRIVATE, Fields.SIZE_WHEN_DONE);
-        TypedResponse<RsTorrentGet> rsp = client.execute(torrentList);
-        List<Long> torrents = rsp.getArgs().getTorrents().stream()
-                .filter(t -> t.getStatus() != Status.STOPPED)
-                .map(Torrents::getId)
-                .filter(ids::contains).collect(Collectors.toList());
-        log.info(tlUI(Lang.DOWNLOADER_TR_DISCONNECT_PEERS, torrents.size()));
-        RqTorrent stop = new RqTorrent(TorrentAction.STOP, new ArrayList<>());
-        for (long torrent : torrents) {
-            stop.add(torrent);
-        }
-        pausedTorrents.add(new DelayedTorrents(torrents, 5000));
-        client.execute(stop);
-        unpauseTorrents(false);
     }
 
+
     @Override
     public void relaunchTorrentIfNeededByTorrentWrapper(Collection<TorrentWrapper> torrents) {
-        if (ExternalSwitch.parse("pbh.transmission.disable-torrent-relaunch") != null) {
-            return;
-        }
-        relaunchTorrents(torrents.stream().filter(t -> {
-            try {
-                Long.parseLong(t.getId());
-                return true;
-            } catch (Exception e) {
-                return false;
-            }
-        }).map(t -> Long.parseLong(t.getId())).toList());
-    }
 
-    private synchronized void unpauseTorrents(boolean includeUnexpired) {
-        List<DelayedTorrents> ids;
-        if (includeUnexpired) {
-            ids = new ArrayList<>(pausedTorrents);
-            pausedTorrents.clear();
-        } else {
-            ids = new ArrayList<>();
-            pausedTorrents.drainTo(ids);
-        }
-        if (ids.isEmpty()) {
-            return;
-        }
-        RqTorrent resume = new RqTorrent(TorrentAction.START, new ArrayList<>());
-        ids.stream().flatMap(d -> d.getTorrentIds().stream()).forEach(resume::add);
-        try {
-            var run = client.execute(resume);
-            if (!run.isSuccess()) {
-                pausedTorrents.addAll(ids);
-            }
-        } catch (Exception e) {
-            log.warn("Unable handle Transmission task restore, scheduled re-run in next schedule task window.");
-            pausedTorrents.addAll(ids);
-        }
     }
 
     @Override
     public void runScheduleTasks() {
-        unpauseTorrents(false);
+        // unpauseTorrents(false);
path: src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/Transmission.java
user: login: Copilot
body: [nitpick] There is commented-out code for unpausing torrents in runScheduleTasks. If it is no longer needed, consider removing it to keep the codebase clean.
```suggestion

```
created_at: 2025-04-04T20:05:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -110,31 +96,52 @@ public void setPaused(boolean paused) {
         config.setPaused(paused);
     }
 
-    @SneakyThrows(InterruptedException.class)
     @Override
     public DownloaderLoginResult login0() {
         RqSessionGet get = new RqSessionGet();
         TypedResponse<RsSessionGet> resp = client.execute(get); // 执行任意 RPC 操作以刷新 session
         String version = resp.getArgs().getVersion();
-        if (version.startsWith("0.") || version.startsWith("1.") || version.startsWith("2.")) {
-            return new DownloaderLoginResult(DownloaderLoginResult.Status.EXCEPTION, new TranslationComponent(Lang.DOWNLOADER_TR_KNOWN_INCOMPATIBILITY, "API Version"));
+        if (version.length() > 5) {
+            version = version.substring(0, 5);
+        }
+        Semver semver = new Semver(version, Semver.SemverType.LOOSE);
+        // must 4.1.0 or higher
+        if (semver.getMajor() < 4) {
+            return new DownloaderLoginResult(DownloaderLoginResult.Status.EXCEPTION, new TranslationComponent(Lang.DOWNLOADER_TR_KNOWN_INCOMPATIBILITY, "Transmission version must higher then (or equals to) 4.1.0"));
path: src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/Transmission.java
user: login: Copilot
body: [nitpick] Consider updating the error message to use correct grammar, for example: 'Transmission version must be 4.1.0 or higher.'
```suggestion
            return new DownloaderLoginResult(DownloaderLoginResult.Status.EXCEPTION, new TranslationComponent(Lang.DOWNLOADER_TR_KNOWN_INCOMPATIBILITY, "Transmission version must be 4.1.0 or higher"));
```
created_at: 2025-04-05T09:46:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -13,7 +13,7 @@
 import java.util.List;
 
 public abstract class AbstractDownloader implements Downloader {
-    private final AlertManager alertManager;
+    public final AlertManager alertManager;
path: src/main/java/com/ghostchu/peerbanhelper/downloader/AbstractDownloader.java
user: login: Copilot
body: Exposing alertManager as a public field may lead to unintended modifications; consider using protected access to preserve encapsulation while allowing subclass access.
```suggestion
    protected final AlertManager alertManager;
```
created_at: 2025-04-05T09:46:48Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-04T19:55:16Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Auto reviews are disabled on base/target branches other than the default branch.
> 
> 
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=PBH-BTN/PeerBanHelper&utm_content=1065):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: Ghost-chu
created_at: 2025-04-04T20:04:24Z
body: @coderabbitai review
author_association: MEMBER
user: login: coderabbitai[bot]
created_at: 2025-04-04T20:04:29Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>:white_check_mark: Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: CONTRIBUTOR
user: login: cdowen
created_at: 2025-04-05T08:59:12Z
body: 请问这里的Peer累积传输信息是指某个session里peer的总上传和下载，peer断开/重连不会重置对吗？历史若干秒之内的传输信息对PBH来说可用吗？

另外脚本引擎和主动检测具体缺少哪些api呢？我可以尝试再提pr加上
author_association: NONE
user: login: Ghost-chu
created_at: 2025-04-05T09:03:06Z
body: > 请问这里的Peer累积传输信息是指某个session里peer的总上传和下载，peer断开/重连不会重置对吗？历史若干秒之内的传输信息对PBH来说可用吗？

是否重置并不重要，因为 PeerBanHelper 可以处理所有这两种情况。

> 另外脚本引擎和主动检测具体缺少哪些api呢？我可以尝试再提pr加上

脚本引擎和下载器支持有关，但目前最重要的就是 PeerID 和 ClientName 的 API。

主动检测主要是记录 Peer 活动，并附加流量告警，需要的是全局上传量、下载量数据。

![image](https://github.com/user-attachments/assets/9fb4908d-8e30-470e-86b5-082bf8d83eaf)


author_association: MEMBER
user: login: cdowen
created_at: 2025-04-05T09:19:27Z
body: 现在transmission的api似乎有全局上传/下载量的记录，在Session statistics里

ClientName的话，用peerid是不是就可以替代了，毕竟ClientName本来也是从peerid映射的
author_association: NONE
user: login: Ghost-chu
created_at: 2025-04-05T09:22:12Z
body: > ClientName的话，用peerid是不是就可以替代了，毕竟ClientName本来也是从peerid映射的

不能。ClientName 映射是 Transmission 自己的做法。  
标准做法是从[扩展协议](https://www.bittorrent.org/beps/bep_0010.html)提供的 client name 里面获取由客户端自己提供的。

v: Client name and version (as a utf-8 string). This is a much more reliable way of identifying the client than relying on the peer id encoding.

> 现在transmission的api似乎有全局上传/下载量的记录，在Session statistics里

这个目前需要是持久化的。
author_association: MEMBER
user: login: Ghost-chu
created_at: 2025-04-05T09:45:59Z
body: @coderabbitai review
author_association: MEMBER
user: login: coderabbitai[bot]
created_at: 2025-04-05T09:46:08Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>:white_check_mark: Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: CONTRIBUTOR