diff --git a/README.EN.md b/README.EN.md new file mode 100644 index 0000000000..b86767be6d --- /dev/null +++ b/README.EN.md @@ -0,0 +1,96 @@ +# PeerBanHelper +[简体中文](./README.md) + +Automatically block unwanted, leeches and abnormal BT peers with support for customized and cloud rules. + +![page-views](https://raw.githubusercontent.com/PBH-BTN/views-counter/refs/heads/master/svg/754169590/badge.svg) +## Introduction + +Following function are provided by PeerBanHelper: + +- [PeerID Blacklist](https://docs.pbh-btn.com/en/docs/module/peer-id) +- [Client Name Blacklist](https://docs.pbh-btn.com/en/docs/module/client-name) +- [IP/GeoIP/IP type Blacklist](https://docs.pbh-btn.com/en/docs/module/ip-address-blocker) +- [Fake progress checker (heuristic client detection)](https://docs.pbh-btn.com/en/docs/module/progress-cheat-blocker) +- [Auto range ban](https://docs.pbh-btn.com/en/docs/module/auto-range-ban) +- [Multi-dail ban](https://docs.pbh-btn.com/en/docs/module/multi-dial) +- Peer ID/Client Name camouflage check, powered by [AviatorScript Engine](https://docs.pbh-btn.com/en/docs/module/expression-engine) +- [Active monitoring(data analysis)](https://docs.pbh-btn.com/en/docs/module/active-monitoring) +- [IP set subscribe](https://docs.pbh-btn.com/en/docs/module/ip-address-blocker-rules) +- a mordern WebUI + +In addition, PeerBanHelper downloads the GeoIP library at startup, and supports the following functions once it successful loaded: +- View IP address attribution, AS information (ASN, ISP, AS name, etc.), network type information (broadband, base station, IoT, data center, etc.) in the blocking list. +- Based on GeoIP information, block IP addresses by country/region, city, network type, ASN and so on. +- View GeoIP statistics + +> [!TIP] +> For best results, it is recommended to work with the IP rule [PBH-BTN/BTN-Collected-Rules](https://github.com/PBH-BTN/BTN-Collected-Rules) and [BTN Network](https://docs.pbh-btn.com/en/docs/btn/intro) , but this is completely optional. + + +## Supported clients + +- qBittorrent **4.5.0 or higher** +- BiglyBT([plugin](https://github.com/PBH-BTN/PBH-Adapter-BiglyBT) is required) +- Deluge([plugin](https://github.com/PBH-BTN/PBH-Adapter-Deluge) is required) +- Azureus(Vuze)([plugin](https://github.com/PBH-BTN/PBH-Adapter-Azureus) is required) +- Transmission **(deprected;3.00-20 or higher)** +- BitComet **v2.10 Beta6 [20240928] or higher** + + +# Screenshots + +| Dashboard | Banlist | Banlogs | Rule subscribe | +| :------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | +| homepage | banlist | banlogs | banMetrics | + +## Install + +Please read the [docs](https://docs.pbh-btn.com/en/docs/category/%E5%AE%89%E8%A3%85%E9%83%A8%E7%BD%B2) + + +## FAQ + +Before submit issue, please read the [FAQ](https://docs.pbh-btn.com/en/docs/faq) + +## Support +Consider join our [Telegram](https://t.me/+_t3Nt5GZ6bJmYjBl) group. + +## Declaration + +Illegal websites and black and grey industries should not initiate any kind of manual service request to our organization's development or support staff; it is strictly prohibited to use any services or products of PBH-BTN team to engage in any illegal activities such as violating the law, endangering national security, committing or helping others to commit telecommunication crimes, and other illegal activities. +Users are not allowed to carry out any activities that harm the interests of other individuals or organizations through any services or products of PBH-BTN Team. The use of any PBH-BTN Team services or products in violation of the rights and interests of any individual or organization is not permitted. + +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=PBH-BTN/PeerBanHelper&type=Date)](https://star-history.com/#PBH-BTN/PeerBanHelper&Date) + +## Credit + +### Backend + +- [Cordelia](https://github.com/bochkov/cordelia) +- [IPAddress](https://github.com/seancfoley/IPAddress) +- [YamlConfiguration](https://github.com/bspfsystems/YamlConfiguration) +- [libby](https://github.com/AlessioDP/libby) +- [AviatorScript](https://github.com/killme2008/aviatorscript) +- [javalin](https://javalin.io/) +- [deluge-java](https://github.com/RangerRick/deluge-java) +- [jSystemThemeDetector](https://github.com/Dansoftowner/jSystemThemeDetector) +- [Methanol](https://github.com/mizosoft/methanol) +- [Flatlaf](https://github.com/JFormDesigner/FlatLaf) +- [GeoIP2](https://dev.maxmind.com/geoip) +- [ormlite](https://ormlite.com/) +- [SimpleReloadLib](https://github.com/Ghost-chu/SimpleReloadLib) + +### WebUI + +- [Vue](https://vuejs.org/) +- [ArcoDesign](https://arco.design/) +- [ECharts](https://echarts.apache.org/en/index.html) + +### Install4j + +PeerBanHelper use [Install4j multi-platform installer builder](https://www.ej-technologies.com/products/install4j/overview.html) to build its multi-platform installer. Thanks the open-source license provided by ej-technolgies. Click the link or the image below to download install4j. + +[![Install4j](https://www.ej-technologies.com/images/product_banners/install4j_large.png)](https://www.ej-technologies.com/products/install4j/overview.html) diff --git a/README.md b/README.md index aa24f260d7..b05f8757e8 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,33 @@ # PeerBanHelper +[English](./README.EN.md) 自动封禁不受欢迎、吸血和异常的 BT 客户端,并支持自定义规则。 ![page-views](https://raw.githubusercontent.com/PBH-BTN/views-counter/refs/heads/master/svg/754169590/badge.svg) +## 功能介绍 -> [!NOTE] -> PeerBanHelper 没有内建的更新检查程序,记得时常回来看看是否有新的版本更新,或者 Watch 本仓库(Custom -> Releases, Issues 和 Discussions)以接收版本更新通知 -> QQ 交流群:932978658,如果在使用过程中需要帮助,您可以在这里和他人一同交流。或者在 [Issue Tracker](https://github.com/Ghost-chu/PeerBanHelper/issues) 打开新问题 +PeerBanHelper 主要由以下几个功能模块组成: -> [!TIP] -> 您只需要正确连接 PBH 到下载器就可以正常工作,大多数情况下,并不需要额外配置 +- [PeerID 黑名单](https://docs.pbh-btn.com/docs/module/peer-id) +- [Client Name 黑名单](https://docs.pbh-btn.com/docs/module/client-name) +- [IP/GeoIP/IP 类型 黑名单](https://docs.pbh-btn.com/docs/module/ip-address-blocker) +- [虚假进度检查器(提供启发式客户端检测功能)](https://docs.pbh-btn.com/docs/module/progress-cheat-blocker) +- [自动连锁封禁](https://docs.pbh-btn.com/docs/module/auto-range-ban) +- [多拨追猎](https://docs.pbh-btn.com/docs/module/multi-dial) +- Peer ID/Client Name 伪装检查;通过 [AviatorScript 引擎](https://docs.pbh-btn.com/docs/module/expression-engine) 实现 +- [主动监测(提供本地数据分析功能)](https://docs.pbh-btn.com/docs/module/active-monitoring) +- [网络 IP 集规则订阅](https://docs.pbh-btn.com/docs/module/ip-address-blocker-rules) +- WebUI (目前支持:活跃封禁名单查看,历史封禁查询,封禁最频繁的 Top 50 IP,规则订阅管理,图表查看,Peer 列表查看) -> [!TIP] -> 为获得最佳效果,建议配合我们维护的 IP 规则库 [PBH-BTN/BTN-Collected-Rules](https://github.com/PBH-BTN/BTN-Collected-Rules) 和 [BTN 网络](https://docs.pbh-btn.com/docs/btn/intro) 一起食用,不过这是完全可选的。 +此外,PeerBanHelper 会在启动时下载 GeoIP 库,成功加载后支持以下功能: -| 主界面 | 封禁列表 | 封禁日志 | 封禁统计 | 规则统计 | 规则订阅 | -| ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| homepage | banlist | banlogs | maxban | banMetrics | banMetrics | +- 在封禁列表中查看 IP 归属地,AS 信息(ASN、ISP、AS 名称等),网络类型信息(宽带、基站、物联网、数据中心等) +- 基于 GeoIP 信息按国家/地区、城市、网络类型、ASN 等封禁 IP 地址 +- 查看 GeoIP 统计数据 -## 安装 PeerBanHelper +> [!TIP] +> 为获得最佳效果,建议配合我们维护的 IP 规则库 [PBH-BTN/BTN-Collected-Rules](https://github.com/PBH-BTN/BTN-Collected-Rules) 和 [BTN 网络](https://docs.pbh-btn.com/docs/btn/intro) 一起食用,不过这是完全可选的。 -查看 [PeerBanHelper 文档](https://docs.pbh-btn.com/docs/category/%E5%AE%89%E8%A3%85%E9%83%A8%E7%BD%B2) ## 支持的客户端 @@ -31,43 +38,26 @@ - Transmission **(不建议使用;3.00-20 或更高版本)** - BitComet **v2.10 Beta6 [20240928] 或更高版本** -## 注意事项 -请不要打开下载器中的 "允许来自同一 IP 地址的多个连接" 选项,这会干扰 PBH 计算数据,并导致错误封禁。 -如果您的下载器存在 PT 站种子,在添加下载器时建议开启 “忽略私有种子”。 +# 截图 -## 功能介绍 - -PeerBanHelper 主要由以下几个功能模块组成: +| 主界面 | 封禁列表 | 封禁日志 | 封禁统计 | 规则统计 | 规则订阅 | +| ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| homepage | banlist | banlogs | maxban | banMetrics | banMetrics | -- [PeerID 黑名单](https://docs.pbh-btn.com/docs/module/peer-id) -- [Client Name 黑名单](https://docs.pbh-btn.com/docs/module/client-name) -- [IP/GeoIP/IP 类型 黑名单](https://docs.pbh-btn.com/docs/module/ip-address-blocker) -- [虚假进度检查器(提供启发式客户端检测功能)](https://docs.pbh-btn.com/docs/module/progress-cheat-blocker) -- [自动连锁封禁](https://docs.pbh-btn.com/docs/module/auto-range-ban) -- [多拨追猎](https://docs.pbh-btn.com/docs/module/multi-dial) -- Peer ID/Client Name 伪装检查;通过 [AviatorScript 引擎](https://docs.pbh-btn.com/docs/module/expression-engine) 实现 -- [主动监测(提供本地数据分析功能)](https://docs.pbh-btn.com/docs/module/active-monitoring) -- [网络 IP 集规则订阅](https://docs.pbh-btn.com/docs/module/ip-address-blocker-rules) -- WebUI (目前支持:活跃封禁名单查看,历史封禁查询,封禁最频繁的 Top 50 IP,规则订阅管理,图表查看,Peer 列表查看) +## 安装 PeerBanHelper -此外,PeerBanHelper 会在启动时下载 GeoIP 库,成功加载后支持以下功能: +查看 [PeerBanHelper 文档](https://docs.pbh-btn.com/docs/category/%E5%AE%89%E8%A3%85%E9%83%A8%E7%BD%B2) -- 在封禁列表中查看 IP 归属地,AS 信息(ASN、ISP、AS 名称等),网络类型信息(宽带、基站、物联网、数据中心等) -- 基于 GeoIP 信息按国家/地区、城市、网络类型、ASN 等封禁 IP 地址 -- 查看 GeoIP 统计数据 ## 常见问题 在报告问题前,请先检查 [常见问题列表](https://docs.pbh-btn.com/docs/faq) -## Install4j - -PeerBanHelper 使用 [Install4j multi-platform installer builder](https://www.ej-technologies.com/products/install4j/overview.html) 打包多平台安装程序。感谢 ej-technolgies 的开放源代码许可证。点击链接或者下面的图片下载 install4j。 - -[![Install4j](https://www.ej-technologies.com/images/product_banners/install4j_large.png)](https://www.ej-technologies.com/products/install4j/overview.html) +## 需要帮助? +考虑加入我们的[QQ群](https://qm.qq.com/cgi-bin/qm/qr?k=w5as_wH2G1ReUrClreCYhR69XiNCuP65&jump_from=webapi&authKey=EyjMX7Pwc77XLM51V6FEcR7oXnG8fsUbSFqYZ4PPiEpq32vBglJn/jFvpc3LFDhn)! -## 法律文本 +## 声明 违法网站和黑灰产请勿向我组织开发或支持人员发起任何形式的人工服务请求;严禁使用 PBH-BTN 团队的任何服务、产品从事任何违法违规、危害国家安全、实施或帮助他人实施电信犯罪等非法活动。 用户不得通过 PBH-BTN 团队的任何服务、产品进行任何损害其它个人或组织的利益的活动。在任何违反个人或组织权益的情况下使用 PBH-BTN 团队的任何服务、产品均不被允许。 @@ -99,3 +89,9 @@ PeerBanHelper 使用 [Install4j multi-platform installer builder](https://www.ej - [Vue](https://vuejs.org/) - [ArcoDesign](https://arco.design/) - [ECharts](https://echarts.apache.org/en/index.html) + +### Install4j + +PeerBanHelper 使用 [Install4j multi-platform installer builder](https://www.ej-technologies.com/products/install4j/overview.html) 打包多平台安装程序。感谢 ej-technolgies 的开放源代码许可证。点击链接或者下面的图片下载 install4j。 + +[![Install4j](https://www.ej-technologies.com/images/product_banners/install4j_large.png)](https://www.ej-technologies.com/products/install4j/overview.html) diff --git a/pkg/deb/DEBIAN/conffiles b/pkg/deb/DEBIAN/conffiles index 24d2d7be5a..44bd033285 100644 --- a/pkg/deb/DEBIAN/conffiles +++ b/pkg/deb/DEBIAN/conffiles @@ -1 +1,2 @@ -/etc/peerbanhelper/data/config.yml +/etc/peerbanhelper/config.yml +/etc/peerbanhelper/profile.yml diff --git a/pkg/deb/DEBIAN/control b/pkg/deb/DEBIAN/control index a7a46e3228..c437d1b6f1 100644 --- a/pkg/deb/DEBIAN/control +++ b/pkg/deb/DEBIAN/control @@ -1,6 +1,7 @@ Package: peerbanhelper Version: -Depends: java-runtime (>=21),libc6 +Pre-Depends: adduser +Depends: java-runtime (>=21), libc6 Section: universe/net Priority: optional Architecture: all diff --git a/pkg/deb/DEBIAN/dirs b/pkg/deb/DEBIAN/dirs deleted file mode 100644 index 7ffedddb9e..0000000000 --- a/pkg/deb/DEBIAN/dirs +++ /dev/null @@ -1 +0,0 @@ -/etc/peerbanhelper \ No newline at end of file diff --git a/pkg/deb/DEBIAN/postinst b/pkg/deb/DEBIAN/postinst index ec1833a9bb..a365dfba8a 100755 --- a/pkg/deb/DEBIAN/postinst +++ b/pkg/deb/DEBIAN/postinst @@ -1,5 +1,10 @@ -#!/bin/sh -USER=peerbanhelper -adduser --system $USER -mkdir -p /etc/peerbanhelper -chown $USER /etc/peerbanhelper \ No newline at end of file +#!/bin/sh -e + +getent passwd peerbanhelper > /dev/null || adduser --quiet --system --group --home /var/lib/peerbanhelper peerbanhelper +chown -R peerbanhelper: /etc/peerbanhelper +chown -R root: /usr/lib/peerbanhelper +mkdir /var/log/peerbanhelper +chown peerbanhelper: /var/log/peerbanhelper + +systemctl daemon-reload +systemctl start peerbanhelper.service diff --git a/pkg/deb/DEBIAN/postrm b/pkg/deb/DEBIAN/postrm new file mode 100644 index 0000000000..3ce8f1aef0 --- /dev/null +++ b/pkg/deb/DEBIAN/postrm @@ -0,0 +1,3 @@ +#!/bin/sh -e + +[ $1 = purge ] && deluser --quiet peerbanhelper && rm -rf /var/lib/peerbanhelper || true diff --git a/pkg/deb/DEBIAN/preinst b/pkg/deb/DEBIAN/preinst new file mode 100644 index 0000000000..a67dbf2c23 --- /dev/null +++ b/pkg/deb/DEBIAN/preinst @@ -0,0 +1,12 @@ +#!/bin/sh -e + +# migrate <= 7.1.2 +if getent passwd peerbanhelper | grep --quiet /nonexistent; then + deluser --quiet peerbanhelper + adduser --quiet --system --group --home /var/lib/peerbanhelper peerbanhelper + chown -R peerbanhelper: /etc/peerbanhelper + mv /etc/peerbanhelper/config/config.yml /etc/peerbanhelper/config/profile.yml /etc/peerbanhelper + rm -rf /etc/peerbanhelper/config /etc/peerbanhelper/data + mv /etc/peerbanhelper/logs /var/log/peerbanhelper + find /etc/peerbanhelper -mindepth 1 -maxdepth 1 -type d -exec mv {} /var/lib/peerbanhelper \; +fi diff --git a/pkg/deb/DEBIAN/prerm b/pkg/deb/DEBIAN/prerm new file mode 100644 index 0000000000..1f1ade5c72 --- /dev/null +++ b/pkg/deb/DEBIAN/prerm @@ -0,0 +1,4 @@ +#!/bin/sh -e + +systemctl daemon-reload +systemctl stop peerbanhelper.service diff --git a/pkg/deb/etc/peerbanhelper/data/config.yml b/pkg/deb/etc/peerbanhelper/config.yml similarity index 100% rename from pkg/deb/etc/peerbanhelper/data/config.yml rename to pkg/deb/etc/peerbanhelper/config.yml diff --git a/pkg/deb/etc/peerbanhelper/data/profile.yml b/pkg/deb/etc/peerbanhelper/profile.yml similarity index 99% rename from pkg/deb/etc/peerbanhelper/data/profile.yml rename to pkg/deb/etc/peerbanhelper/profile.yml index 5151f7685e..1339abf51a 100644 --- a/pkg/deb/etc/peerbanhelper/data/profile.yml +++ b/pkg/deb/etc/peerbanhelper/profile.yml @@ -414,4 +414,4 @@ module: traffic-monitoring: # 每日阈值 - 设置为 -1 以禁用,单位:bytes # Daily threshold, set to -1 to disable, Unit: bytes - daily: -1 \ No newline at end of file + daily: -1 diff --git a/pkg/deb/usr/lib/systemd/system/peerbanhelper.service b/pkg/deb/usr/lib/systemd/system/peerbanhelper.service index 1b03498d8c..15955044fb 100644 --- a/pkg/deb/usr/lib/systemd/system/peerbanhelper.service +++ b/pkg/deb/usr/lib/systemd/system/peerbanhelper.service @@ -1,11 +1,14 @@ [Unit] Description=PeerBanHelper Service After=network.target + [Service] User=peerbanhelper -WorkingDirectory=/etc/peerbanhelper -ExecStart=/usr/bin/java -Dpbh.release=debian -Dpbh.datadir=/etc/peerbanhelper -Xmx512M -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+ShrinkHeapInSteps -jar /usr/lib/peerbanhelper/PeerBanHelper.jar +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 -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+ShrinkHeapInSteps -jar PeerBanHelper.jar +SuccessExitStatus=143 +AmbientCapabilities=CAP_NET_ADMIN Restart=on-failure -StandardOutput=null + [Install] WantedBy=multi-user.target diff --git a/pom.xml b/pom.xml index 975f5f8c67..85fa030908 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.ghostchu.peerbanhelper peerbanhelper - 7.2.0-snapshot + 7.1.4 jar PeerBanHelper @@ -485,7 +485,11 @@ xz 1.10 - + + com.formdev + flatlaf-extras + 3.0 + 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 43ce0321e1..d59121261d 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityRules.java +++ b/src/main/java/com/ghostchu/peerbanhelper/btn/ability/BtnAbilityRules.java @@ -80,6 +80,9 @@ public TranslationComponent getDisplayName() { @Override public TranslationComponent getDescription() { + if(btnRule == null){ + return new TranslationComponent(Lang.BTN_ABILITY_RULES_DESCRIPTION, "N/A", 0, 0, 0, 0, 0); + } return new TranslationComponent(Lang.BTN_ABILITY_RULES_DESCRIPTION, btnRule.getVersion(), btnRule.size(), diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/AbstractQbittorrent.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/AbstractQbittorrent.java index 352b8afa6e..ac03d964d7 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/AbstractQbittorrent.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/AbstractQbittorrent.java @@ -161,7 +161,7 @@ public List getTorrents() { fillTorrentPrivateField(qbTorrent); } return qbTorrent.stream().map(t -> (Torrent) t) - .filter(t-> !config.isIgnorePrivate() || !t.isPrivate()) + .filter(t -> !config.isIgnorePrivate() || !t.isPrivate()) .collect(Collectors.toList()); } @@ -247,6 +247,9 @@ public List getPeers(Torrent torrent) { for (String s : peers.keySet()) { JsonObject singlePeerObject = peers.getAsJsonObject(s); QBittorrentPeer qbPeer = JsonUtil.getGson().fromJson(singlePeerObject.toString(), QBittorrentPeer.class); + if ("HTTP".equalsIgnoreCase(qbPeer.getConnection()) || "HTTPS".equalsIgnoreCase(qbPeer.getConnection()) || "Web".equalsIgnoreCase(qbPeer.getConnection())) { + continue; + } if (qbPeer.getPeerAddress().getIp() == null || qbPeer.getPeerAddress().getIp().isBlank()) { continue; } diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentPeer.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentPeer.java index 1e400655f6..74f6ddb319 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentPeer.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentPeer.java @@ -18,8 +18,8 @@ public final class QBittorrentPeer implements Peer { @SerializedName("client") private String client; -// @SerializedName("connection") -// private String connection; + @SerializedName("connection") + private String connection; // @SerializedName("country") // private String country; // @SerializedName("country_code") @@ -114,4 +114,7 @@ public String getRawIp() { return rawIp == null ? ip : rawIp; } + public String getConnection() { + return connection; + } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEE.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEE.java index 4c073195b8..3fe959f68b 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEE.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEE.java @@ -103,6 +103,9 @@ public List getPeers(Torrent torrent) { if(qbPeer.getPeerAddress().getIp() == null || qbPeer.getPeerAddress().getIp().isBlank()){ continue; } + if ("HTTP".equalsIgnoreCase(qbPeer.getConnection()) || "HTTPS".equalsIgnoreCase(qbPeer.getConnection()) || "Web".equalsIgnoreCase(qbPeer.getConnection())) { + continue; + } if(qbPeer.getRawIp().contains(".onion") || qbPeer.getRawIp().contains(".i2p")){ continue; } diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEEPeer.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEEPeer.java index 2021833b8c..a85d571356 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEEPeer.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/enhanced/QBittorrentEEPeer.java @@ -116,6 +116,9 @@ public String getRawIp() { return rawIp == null ? ip : rawIp; } + public String getConnection() { + return connection; + } @Override public String toString() { diff --git a/src/main/java/com/ghostchu/peerbanhelper/gui/impl/swing/MainWindow.java b/src/main/java/com/ghostchu/peerbanhelper/gui/impl/swing/MainWindow.java index 5cf7a06458..5d46c67209 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/gui/impl/swing/MainWindow.java +++ b/src/main/java/com/ghostchu/peerbanhelper/gui/impl/swing/MainWindow.java @@ -1,7 +1,9 @@ package com.ghostchu.peerbanhelper.gui.impl.swing; +import com.formdev.flatlaf.extras.FlatSVGIcon; import com.formdev.flatlaf.util.SystemInfo; import com.ghostchu.peerbanhelper.Main; +import com.ghostchu.peerbanhelper.downloader.DownloaderLastStatus; import com.ghostchu.peerbanhelper.text.Lang; import com.ghostchu.peerbanhelper.util.logger.LogEntry; import lombok.Getter; @@ -19,6 +21,8 @@ import java.awt.event.WindowEvent; import java.io.IOException; import java.net.URI; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.logging.Level; @@ -36,7 +40,7 @@ public class MainWindow extends JFrame { private JScrollPane loggerScrollPane; @Nullable @Getter - private TrayIcon trayIcon; + private SwingTray swingTrayDialog; private boolean persistFlagTrayMessageSent; public MainWindow(SwingGuiImpl swingGUI) { @@ -87,7 +91,7 @@ public static void copyText(String content) { } private void minimizeToTray() { - if (trayIcon != null) { + if (swingTrayDialog != null) { setVisible(false); if (!persistFlagTrayMessageSent) { persistFlagTrayMessageSent = true; @@ -104,25 +108,64 @@ private void setupSystemTray() { if (SystemTray.isSupported()) { TrayIcon icon = new TrayIcon(Toolkit.getDefaultToolkit().getImage(Main.class.getResource("/assets/icon.png"))); icon.setImageAutoSize(true); - //创建弹出菜单 - PopupMenu menu = new PopupMenu(); - //添加一个用于退出的按钮 - MenuItem item = new MenuItem("Exit"); - item.addActionListener(e -> System.exit(0)); - menu.add(item); - //添加弹出菜单到托盘图标 - icon.setPopupMenu(menu); - SystemTray tray = SystemTray.getSystemTray();//获取系统托盘 - icon.addActionListener(e -> setVisible(true)); + SystemTray sysTray = SystemTray.getSystemTray();//获取系统托盘 try { - tray.add(icon);//将托盘图表添加到系统托盘 - this.trayIcon = icon; + var tray = new SwingTray(icon, mouseEvent -> setVisible(true), mouseEvent -> updateTrayMenus()); + sysTray.add(icon);//将托盘图表添加到系统托盘 + updateTrayMenus(); + this.swingTrayDialog = tray; } catch (AWTException e) { throw new RuntimeException(e); } } } + private void updateTrayMenus() { + if (this.swingTrayDialog == null) return; + List items = new ArrayList<>(); + JMenuItem openMainWindow = new JMenuItem(tlUI(Lang.GUI_MENU_SHOW_WINDOW), new FlatSVGIcon(Main.class.getResource("/assets/icon/tray/open.svg"))); + JMenuItem openWebUI = new JMenuItem(tlUI(Lang.GUI_MENU_WEBUI_OPEN), new FlatSVGIcon(Main.class.getResource("/assets/icon/tray/browser.svg"))); + JMenuItem quit = new JMenuItem(tlUI(Lang.GUI_MENU_QUIT), new FlatSVGIcon(Main.class.getResource("/assets/icon/tray/close.svg"))); + openMainWindow.addActionListener(e -> setVisible(true)); + openWebUI.addActionListener(e -> openWebUI()); + quit.addActionListener(e -> System.exit(0)); + items.add(menuDisplayItem(new JMenuItem(tlUI(Lang.GUI_MENU_STATS)))); + items.add(menuBanStats()); + items.add(menuDownloaderStats()); + items.add(menuDisplayItem(new JMenuItem(tlUI(Lang.GUI_MENU_QUICK_OPERATIONS)))); + items.add(openMainWindow); + items.add(openWebUI); + items.add(null); + items.add(quit); + this.swingTrayDialog.set(items); + } + + private JMenuItem menuDownloaderStats() { + var totalDownloaders = 0L; + var healthDownloaders = 0L; + if (Main.getServer() != null) { + totalDownloaders = Main.getServer().getDownloaders().size(); + healthDownloaders = Main.getServer().getDownloaders().stream().filter(m -> m.getLastStatus() == DownloaderLastStatus.HEALTHY).count(); + } + return new JMenuItem(tlUI(Lang.GUI_MENU_STATS_DOWNLOADER, healthDownloaders, totalDownloaders), new FlatSVGIcon(Main.class.getResource("/assets/icon/tray/connection.svg"))); + } + + private JMenuItem menuBanStats() { + var bannedPeers = 0L; + var bannedIps = 0L; + var server = Main.getServer(); + if (server != null) { + bannedIps = Main.getServer().getBannedPeers().values().stream().map(m -> m.getPeer().getAddress().getIp()).distinct().count(); + bannedPeers = Main.getServer().getBannedPeers().values().size(); + } + return new JMenuItem(tlUI(Lang.GUI_MENU_STATS_BANNED, bannedPeers, bannedIps), new FlatSVGIcon(Main.class.getResource("/assets/icon/tray/banned.svg"))); + } + + private JMenuItem menuDisplayItem(JMenuItem jMenuItem) { + jMenuItem.setEnabled(false); + return jMenuItem; + } + private JMenuBar setupMenuBar() { JMenuBar menuBar = new JMenuBar(); menuBar.add(generateProgramMenu()); @@ -156,9 +199,7 @@ private JMenu generateWebUIMenu() { JMenu webUIMenu = new JMenu(tlUI(Lang.GUI_MENU_WEBUI)); JMenuItem openWebUIMenuItem = new JMenuItem(tlUI(Lang.GUI_MENU_WEBUI_OPEN)); openWebUIMenuItem.addActionListener(e -> { - if (Main.getServer() != null && Main.getServer().getWebContainer() != null) { - swingGUI.openWebpage(URI.create("http://localhost:" + Main.getServer().getWebContainer().javalin().port() + "?token=" + Main.getServer().getWebContainer().getToken())); - } + this.openWebUI(); }); webUIMenu.add(openWebUIMenuItem); JMenuItem copyWebUIToken = new JMenuItem(tlUI(Lang.GUI_COPY_WEBUI_TOKEN)); @@ -173,6 +214,12 @@ private JMenu generateWebUIMenu() { return webUIMenu; } + private void openWebUI() { + if (Main.getServer() != null && Main.getServer().getWebContainer() != null) { + swingGUI.openWebpage(URI.create("http://localhost:" + Main.getServer().getWebContainer().javalin().port() + "?token=" + Main.getServer().getWebContainer().getToken())); + } + } + public void sync() { } diff --git a/src/main/java/com/ghostchu/peerbanhelper/gui/impl/swing/SwingGuiImpl.java b/src/main/java/com/ghostchu/peerbanhelper/gui/impl/swing/SwingGuiImpl.java index ee35d00fed..c6ffe00eb0 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/gui/impl/swing/SwingGuiImpl.java +++ b/src/main/java/com/ghostchu/peerbanhelper/gui/impl/swing/SwingGuiImpl.java @@ -2,7 +2,6 @@ import com.formdev.flatlaf.FlatDarculaLaf; import com.formdev.flatlaf.FlatIntelliJLaf; -import com.formdev.flatlaf.util.SystemInfo; import com.ghostchu.peerbanhelper.Main; import com.ghostchu.peerbanhelper.PeerBanHelperServer; import com.ghostchu.peerbanhelper.exchange.ExchangeMap; @@ -196,32 +195,39 @@ public void createDialog(Level level, String title, String description) { @Override public void createNotification(Level level, String title, String description) { - TrayIcon icon = mainWindow.getTrayIcon(); - if (icon != null) { - if (level.equals(Level.WARNING)) { - icon.displayMessage(title, description, TrayIcon.MessageType.WARNING); - } else if (level.equals(Level.SEVERE)) { - icon.displayMessage(title, description, TrayIcon.MessageType.ERROR); - } else { - icon.displayMessage(title, description, TrayIcon.MessageType.INFO); - } - if (System.getProperty("os.name").contains("Windows")) { - CommonUtil.getScheduler().schedule(this::refreshTrayIcon, 5, TimeUnit.SECONDS); + + var swingTray = mainWindow.getSwingTrayDialog(); + if (swingTray != null) { + var icon = swingTray.getTrayIcon(); + if (swingTray.getTrayIcon() != null) { + if (level.equals(Level.WARNING)) { + icon.displayMessage(title, description, TrayIcon.MessageType.WARNING); + } else if (level.equals(Level.SEVERE)) { + icon.displayMessage(title, description, TrayIcon.MessageType.ERROR); + } else { + icon.displayMessage(title, description, TrayIcon.MessageType.INFO); + } + if (System.getProperty("os.name").contains("Windows")) { + CommonUtil.getScheduler().schedule(this::refreshTrayIcon, 5, TimeUnit.SECONDS); + } + return; } - } else { - super.createNotification(level, title, description); } + super.createNotification(level, title, description); } private synchronized void refreshTrayIcon() { - TrayIcon icon = mainWindow.getTrayIcon(); - if (icon != null) { - try { - SystemTray tray = SystemTray.getSystemTray(); - tray.remove(icon); // fix https://github.com/PBH-BTN/PeerBanHelper/issues/515 - tray.add(icon); - } catch (AWTException e) { - throw new RuntimeException(e); + var swingTray = mainWindow.getSwingTrayDialog(); + if (swingTray != null) { + var icon = swingTray.getTrayIcon(); + if (icon != null) { + try { + SystemTray tray = SystemTray.getSystemTray(); + tray.remove(icon); // fix https://github.com/PBH-BTN/PeerBanHelper/issues/515 + tray.add(icon); + } catch (AWTException e) { + throw new RuntimeException(e); + } } } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/gui/impl/swing/SwingTray.java b/src/main/java/com/ghostchu/peerbanhelper/gui/impl/swing/SwingTray.java new file mode 100644 index 0000000000..11469bfcbd --- /dev/null +++ b/src/main/java/com/ghostchu/peerbanhelper/gui/impl/swing/SwingTray.java @@ -0,0 +1,74 @@ +package com.ghostchu.peerbanhelper.gui.impl.swing; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.List; +import java.util.function.Consumer; + +public class SwingTray { + + private final JDialog jDialog; + private final JPopupMenu jPopupMenu; + private final TrayIcon trayIcon; + + public SwingTray(TrayIcon trayIcon, Consumer clickCallback, Consumer rightClickCallback) { + this.trayIcon = trayIcon; + this.jDialog = new JDialog(); + jDialog.setUndecorated(true); + jDialog.setSize(1, 1); + this.jPopupMenu = new JPopupMenu() { + @Override + public void firePopupMenuWillBecomeInvisible() { + jDialog.setVisible(false); + } + }; + trayIcon.addMouseListener(new MouseAdapter() { + @Override + public void mouseReleased(MouseEvent e) { + // 左键单击 + if (e.getButton() == 1) { + clickCallback.accept(e); + return; + } + if (e.getButton() == 3 && e.isPopupTrigger()) { + rightClickCallback.accept(e); + // 获取屏幕相对位置 + Point point = MouseInfo.getPointerInfo().getLocation(); + // 将jDialog设置为鼠标位置 + jDialog.setLocation(point.x, point.y); + // 显示载体 + jDialog.setVisible(true); + var dimension = jPopupMenu.getPreferredSize(); + jPopupMenu.setSize((int) dimension.getWidth() + 1, (int) dimension.getHeight() + 1); + // 在载体的0,0处显示对话框 + jPopupMenu.show(jDialog, 0, 0); + } + } + }); + } + + public void set(List items) { + jPopupMenu.removeAll(); + items.forEach(ele->{ + if(ele == null) { + jPopupMenu.addSeparator(); + }else{ + jPopupMenu.add(ele); + } + }); + } + + public TrayIcon getTrayIcon() { + return trayIcon; + } + + public JDialog getjDialog() { + return jDialog; + } + + public JPopupMenu getjPopupMenu() { + return jPopupMenu; + } +} 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 1eb5a9f238..8e412f2a79 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 @@ -309,23 +309,30 @@ public boolean isThreadSafe() { return handshaking(); } return getCache().readCacheButWritePassOnly(this, "btn-ban-peer-" + peer.getCacheKey(), () -> { - CheckResult r = null; + List results = new ArrayList<>(); if (rule.getPeerIdRules() != null) { - r = NullUtil.anyNotNull(r, checkPeerIdRule(rule, torrent, peer, ruleExecuteExecutor)); + results.add(checkPeerIdRule(rule, torrent, peer, ruleExecuteExecutor)); } if (rule.getClientNameRules() != null) { - r = NullUtil.anyNotNull(r, checkClientNameRule(rule, torrent, peer, ruleExecuteExecutor)); + results.add(checkClientNameRule(rule, torrent, peer, ruleExecuteExecutor)); } if (rule.getIpRules() != null) { - r = NullUtil.anyNotNull(r, checkIpRule(rule, torrent, peer, ruleExecuteExecutor)); + results.add(checkIpRule(rule, torrent, peer, ruleExecuteExecutor)); } if (rule.getPortRules() != null) { - r = NullUtil.anyNotNull(r, checkPortRule(rule, torrent, peer, ruleExecuteExecutor)); + results.add(checkPortRule(rule, torrent, peer, ruleExecuteExecutor)); } - if (r == null) { - return pass(); + + CheckResult finalResult = pass(); + for (CheckResult result : results) { + if (result != null && result.action() == PeerAction.SKIP) { + return result; + } + if (result != null && result.action() == PeerAction.BAN) { + finalResult = result; + } } - return r; + return finalResult; }, true); } diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ProgressCheatBlocker.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ProgressCheatBlocker.java index 43d61a43a6..976b7beee7 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ProgressCheatBlocker.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ProgressCheatBlocker.java @@ -272,7 +272,8 @@ private void reloadConfig() { } // 计算进度差异 double difference = Math.abs(actualProgress - clientProgress); - if (difference > maximumDifference && !fileTooSmall(torrentSize)) { + // isUploadingToPeer 是为了确认下载器再给对方上传数据,因为对方使用 “超级做种” 时汇报的进度可能并不准确 + if (difference > maximumDifference && !fileTooSmall(torrentSize) && isUploadingToPeer(peer)) { if (banPeerIfConditionReached(clientTask)) { clientTask.setProgressDifferenceCounter(clientTask.getProgressDifferenceCounter() + 1); clientTask.setBanDelayWindowEndAt(0L); @@ -288,7 +289,8 @@ private void reloadConfig() { if (rewindMaximumDifference > 0 && !fileTooSmall(torrentSize)) { double lastRecord = clientTask.getLastReportProgress(); double rewind = lastRecord - peer.getProgress(); - boolean ban = rewind > rewindMaximumDifference; + // isUploadingToPeer 是为了确认下载器再给对方上传数据,因为对方使用 “超级做种” 时汇报的进度可能并不准确 + boolean ban = rewind > rewindMaximumDifference && isUploadingToPeer(peer); if (ban) { clientTask.setRewindCounter(clientTask.getRewindCounter() + 1); clientTask.setBanDelayWindowEndAt(0L); @@ -312,6 +314,10 @@ private void reloadConfig() { } } + private boolean isUploadingToPeer(Peer peer){ + return peer.getUploadSpeed() > 0 || peer.getUploaded() > 0; + } + private boolean fileTooSmall(long torrentSize) { return torrentSize < torrentMinimumSize; } diff --git a/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java b/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java index e6fd324ccb..64248f103d 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java +++ b/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java @@ -433,7 +433,7 @@ public enum Lang { BTN_RULES_SCRIPT_COMPILING, BTN_RULES_SCRIPT_COMPILED, BTN_SERVICES_NEED_RESTART, - EXPRESS_RULE_ENGINE_SAVED; + EXPRESS_RULE_ENGINE_SAVED, GUI_MENU_SHOW_WINDOW, GUI_MENU_STATS_BANNED, GUI_MENU_STATS_DOWNLOADER, GUI_MENU_QUICK_OPERATIONS, GUI_MENU_STATS; public String getKey() { return name(); diff --git a/src/main/java/com/ghostchu/peerbanhelper/text/TextManager.java b/src/main/java/com/ghostchu/peerbanhelper/text/TextManager.java index affb38483e..b62d4aecc6 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/text/TextManager.java +++ b/src/main/java/com/ghostchu/peerbanhelper/text/TextManager.java @@ -127,7 +127,7 @@ public static String[] convert(String locale, @Nullable Object... args) { * Loading Crowdin OTA module and i18n system */ public void load() { - log.info("Loading up translations, this may need a while..."); + log.info("Loading up translations, this may take a while..."); //TODO: This will break the message processing system in-game until loading finished, need to fix it. this.reset(); // first, we need load built-in fallback translation. diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/IPAddressUtil.java b/src/main/java/com/ghostchu/peerbanhelper/util/IPAddressUtil.java index ebac00a7bf..e07bb1d707 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/IPAddressUtil.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/IPAddressUtil.java @@ -16,6 +16,7 @@ * IP 地址工具类 */ public class IPAddressUtil { + private static final IPAddress INVALID_ADDRESS_MISSINGNO = new IPAddressString("127.123.123.123").getAddress(); private static final Cache IP_ADDRESS_CACHE = CacheBuilder.newBuilder() .expireAfterAccess(15, TimeUnit.MINUTES) .maximumSize(100) @@ -50,7 +51,8 @@ public static IPAddress getIPAddress(String ip) { }); } catch (ExecutionException e) { log.error("Unable to get ipaddress from ip {}", ipFinal, e); - return null; + assert false; + return INVALID_ADDRESS_MISSINGNO; } } diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/collection/CircularArrayList.java b/src/main/java/com/ghostchu/peerbanhelper/util/collection/CircularArrayList.java deleted file mode 100644 index 866face139..0000000000 --- a/src/main/java/com/ghostchu/peerbanhelper/util/collection/CircularArrayList.java +++ /dev/null @@ -1,351 +0,0 @@ -package com.ghostchu.peerbanhelper.util.collection; - -import org.jetbrains.annotations.Contract; - -import java.util.AbstractList; -import java.util.Arrays; -import java.util.NoSuchElementException; -import java.util.RandomAccess; - -/** - * @author Glavo - */ -@SuppressWarnings("unchecked") -public final class CircularArrayList extends AbstractList implements RandomAccess { - - private static final int DEFAULT_CAPACITY = 10; - private static final Object[] EMPTY_ARRAY = new Object[0]; - - private Object[] elements; - private int begin = -1; - private int end = 0; - - public CircularArrayList() { - this.elements = EMPTY_ARRAY; - } - - public CircularArrayList(int initialCapacity) { - if (initialCapacity < 0) { - throw new IllegalArgumentException("illegal initialCapacity: " + initialCapacity); - } - - this.elements = initialCapacity == 0 ? EMPTY_ARRAY : new Object[initialCapacity]; - } - - private static int inc(int i, int capacity) { - return i + 1 >= capacity ? 0 : i + 1; - } - - private static int inc(int i, int distance, int capacity) { - if ((i += distance) - capacity >= 0) { - i -= capacity; - } - - return i; - } - - private static int dec(int i, int capacity) { - return i - 1 < 0 ? capacity - 1 : i - 1; - } - - private static int sub(int i, int distance, int capacity) { - if ((i -= distance) < 0) { - i += capacity; - } - return i; - } - - private static int newCapacity(int oldCapacity, int minCapacity) { - return oldCapacity == 0 - ? Math.max(DEFAULT_CAPACITY, minCapacity) - : Math.max(Math.max(oldCapacity, minCapacity), oldCapacity + (oldCapacity >> 1)); - } - - private static void checkElementIndex(int index, int size) throws IndexOutOfBoundsException { - if (index < 0 || index >= size) { - // Optimized for execution by hotspot - checkElementIndexFailed(index, size); - } - } - - @Contract("_, _ -> fail") - private static void checkElementIndexFailed(int index, int size) { - if (size < 0) { - throw new IllegalArgumentException("size(" + size + ") < 0"); - } - if (index < 0) { - throw new IndexOutOfBoundsException("index(" + index + ") < 0"); - } - if (index >= size) { - throw new IndexOutOfBoundsException("index(" + index + ") >= size(" + size + ")"); - } - throw new AssertionError(); - } - - private static void checkPositionIndex(int index, int size) throws IndexOutOfBoundsException { - if (index < 0 || index > size) { - // Optimized for execution by hotspot - checkPositionIndexFailed(index, size); - } - } - - @Contract("_, _ -> fail") - private static void checkPositionIndexFailed(int index, int size) { - if (size < 0) { - throw new IllegalArgumentException("size(" + size + ") < 0"); - } - if (index < 0) { - throw new IndexOutOfBoundsException("index(" + index + ") < 0"); - } - if (index > size) { - throw new IndexOutOfBoundsException("index(" + index + ") > size(" + size + ")"); - } - throw new AssertionError(); - } - - private void grow() { - grow(elements.length + 1); - } - - private void grow(int minCapacity) { - final int oldCapacity = elements.length; - final int size = size(); - final int newCapacity = newCapacity(oldCapacity, minCapacity); - - final Object[] newElements; - if (size == 0) { - newElements = new Object[newCapacity]; - } else if (begin < end) { - newElements = Arrays.copyOf(elements, newCapacity, Object[].class); - } else { - newElements = new Object[newCapacity]; - System.arraycopy(elements, begin, newElements, 0, elements.length - begin); - System.arraycopy(elements, 0, newElements, elements.length - begin, end); - begin = 0; - end = size; - } - this.elements = newElements; - } - - @Override - public boolean isEmpty() { - return begin == -1; - } - - @Override - public int size() { - if (isEmpty()) { - return 0; - } else if (begin < end) { - return end - begin; - } else { - return elements.length - begin + end; - } - } - - @Override - public E get(int index) { - if (isEmpty()) { - throw new IndexOutOfBoundsException("Index out of range: " + index); - } else if (begin < end) { - checkElementIndex(index, end - begin); - return (E) elements[begin + index]; - } else { - checkElementIndex(index, elements.length - begin + end); - return (E) elements[inc(begin, index, elements.length)]; - } - } - - @Override - public E set(int index, E element) { - int arrayIndex; - if (isEmpty()) { - throw new IndexOutOfBoundsException(); - } else if (begin < end) { - checkElementIndex(index, end - begin); - arrayIndex = begin + index; - } else { - final int size = elements.length - begin + end; - checkElementIndex(index, size); - arrayIndex = inc(begin, index, elements.length); - } - - E oldValue = (E) elements[arrayIndex]; - elements[arrayIndex] = element; - return oldValue; - } - - @Override - public void add(int index, E element) { - if (index == 0) { - addFirst(element); - return; - } - - final int oldSize = size(); - if (index == oldSize) { - addLast(element); - return; - } - - checkPositionIndex(index, oldSize); - - if (oldSize == elements.length) { - grow(); - } - - if (begin < end) { - final int targetIndex = begin + index; - if (end < elements.length) { - System.arraycopy(elements, targetIndex, elements, targetIndex + 1, end - targetIndex); - end++; - } else { - System.arraycopy(elements, begin, elements, begin - 1, targetIndex - begin + 1); - begin--; - } - elements[targetIndex] = element; - } else { - int targetIndex = inc(begin, index, elements.length); - if (targetIndex <= end) { - System.arraycopy(elements, targetIndex, elements, targetIndex + 1, end - targetIndex); - elements[targetIndex] = element; - end++; - } else { - System.arraycopy(elements, begin, elements, begin - 1, targetIndex - begin); - elements[targetIndex - 1] = element; - begin--; - } - } - } - - @Override - public E remove(int index) { - final int oldSize = size(); - checkElementIndex(index, oldSize); - - if (index == 0) { - return removeFirst(); - } - - if (index == oldSize - 1) { - return removeLast(); - } - - final Object res; - - if (begin < end) { - final int targetIndex = begin + index; - res = elements[targetIndex]; - System.arraycopy(elements, targetIndex + 1, elements, targetIndex, end - targetIndex - 1); - end--; - } else { - final int targetIndex = inc(begin, index, elements.length); - res = elements[targetIndex]; - if (targetIndex < end) { - System.arraycopy(elements, targetIndex + 1, elements, targetIndex, end - targetIndex - 1); - end--; - } else { - System.arraycopy(elements, begin, elements, begin + 1, targetIndex - begin); - begin = inc(begin, elements.length); - } - } - - return (E) res; - } - - @Override - public void clear() { - if (isEmpty()) { - return; - } - - if (begin < end) { - Arrays.fill(elements, begin, end, null); - } else { - Arrays.fill(elements, 0, end, null); - Arrays.fill(elements, begin, elements.length, null); - } - - begin = -1; - end = 0; - } - - // Deque - - public void addFirst(E e) { - final int oldSize = size(); - if (oldSize == elements.length) { - grow(); - } - - if (oldSize == 0) { - begin = elements.length - 1; - } else { - begin = dec(begin, elements.length); - } - elements[begin] = e; - } - - public void addLast(E e) { - final int oldSize = size(); - if (oldSize == elements.length) { - grow(); - } - elements[end] = e; - end = inc(end, elements.length); - - if (oldSize == 0) { - begin = 0; - } - } - - public E removeFirst() { - final int oldSize = size(); - if (oldSize == 0) { - throw new NoSuchElementException(); - } - - Object res = elements[begin]; - elements[begin] = null; - - if (oldSize == 1) { - begin = -1; - end = 0; - } else { - begin = inc(begin, elements.length); - } - return (E) res; - } - - public E removeLast() { - final int oldSize = size(); - if (oldSize == 0) { - throw new NoSuchElementException(); - } - final int lastIdx = dec(end, elements.length); - E res = (E) elements[lastIdx]; - elements[lastIdx] = null; - - if (oldSize == 1) { - begin = -1; - end = 0; - } else { - end = lastIdx; - } - return res; - } - - public E getFirst() { - if (isEmpty()) - throw new NoSuchElementException(); - - return get(0); - } - - public E getLast() { - if (isEmpty()) - throw new NoSuchElementException(); - - return get(size() - 1); - } -} \ No newline at end of file diff --git a/src/main/java/com/ghostchu/peerbanhelper/util/logger/JListAppender.java b/src/main/java/com/ghostchu/peerbanhelper/util/logger/JListAppender.java index 3e4a48bbcb..5d2829dbde 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/util/logger/JListAppender.java +++ b/src/main/java/com/ghostchu/peerbanhelper/util/logger/JListAppender.java @@ -5,7 +5,7 @@ import ch.qos.logback.core.AppenderBase; import com.ghostchu.peerbanhelper.Main; import com.ghostchu.peerbanhelper.event.NewLogEntryCreatedEvent; -import com.ghostchu.peerbanhelper.util.collection.CircularArrayList; +import com.google.common.collect.EvictingQueue; import org.slf4j.event.Level; import javax.swing.*; @@ -15,7 +15,7 @@ public class JListAppender extends AppenderBase { public static final LinkedBlockingDeque logEntryDeque = new LinkedBlockingDeque<>(); - public static final CircularArrayList ringDeque = new CircularArrayList<>(300); + public static final EvictingQueue ringDeque = EvictingQueue.create(300); private static final AtomicInteger seq = new AtomicInteger(0); private PatternLayout layout; diff --git a/src/main/java/com/ghostchu/peerbanhelper/web/JavalinWebContainer.java b/src/main/java/com/ghostchu/peerbanhelper/web/JavalinWebContainer.java index e52f8d07bc..4f1091ebf5 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/web/JavalinWebContainer.java +++ b/src/main/java/com/ghostchu/peerbanhelper/web/JavalinWebContainer.java @@ -41,7 +41,7 @@ public class JavalinWebContainer { @Setter @Getter private String token; - private Cache FAIL2BAN = CacheBuilder.newBuilder() + private final Cache FAIL2BAN = CacheBuilder.newBuilder() .expireAfterWrite(15, TimeUnit.MINUTES) .build(); diff --git a/src/main/resources/assets/icon/tray/banned.svg b/src/main/resources/assets/icon/tray/banned.svg new file mode 100644 index 0000000000..8879079d1a --- /dev/null +++ b/src/main/resources/assets/icon/tray/banned.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/assets/icon/tray/browser.svg b/src/main/resources/assets/icon/tray/browser.svg new file mode 100644 index 0000000000..2c2062f6ff --- /dev/null +++ b/src/main/resources/assets/icon/tray/browser.svg @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/assets/icon/tray/close.svg b/src/main/resources/assets/icon/tray/close.svg new file mode 100644 index 0000000000..e1b613e5d0 --- /dev/null +++ b/src/main/resources/assets/icon/tray/close.svg @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/assets/icon/tray/connection.svg b/src/main/resources/assets/icon/tray/connection.svg new file mode 100644 index 0000000000..a0b2fb9e95 --- /dev/null +++ b/src/main/resources/assets/icon/tray/connection.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/assets/icon/tray/open.svg b/src/main/resources/assets/icon/tray/open.svg new file mode 100644 index 0000000000..be55c35687 --- /dev/null +++ b/src/main/resources/assets/icon/tray/open.svg @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/lang/en_us/messages.yml b/src/main/resources/lang/en_us/messages.yml index 7b2d30a0b8..e116ff446b 100644 --- a/src/main/resources/lang/en_us/messages.yml +++ b/src/main/resources/lang/en_us/messages.yml @@ -317,7 +317,7 @@ ARB_BANNED_REASON: "IP address {} is in the same ban range as another banned IP TOO_MANY_FAILED_ATTEMPT: "Too many failed login attempts. We paused attempt until {}." AMM_SHUTTING_DOWN: "Please wait ActiveMonitoring flush its data from memory to disk... (up to 5 seconds)" -AMM_CLEANING_TABLES: "[Data Cleanup] ActiveMonitoring deleting expired data, this may need a while..." +AMM_CLEANING_TABLES: "[Data Cleanup] ActiveMonitoring deleting expired data, this may take a while..." AMM_CLEANED_UP: "[Data Cleanup] ActiveMonitoring deleted {} expired data" OOBE_DISALLOW_REINIT: "This PeerBanHelper instance already initialized,OOBE interface no-longer available" @@ -492,4 +492,9 @@ EXPRESS_RULE_ENGINE_DISALLOW_UNSAFE_SOURCE_ACCESS: "For security reason, create EXPRESS_RULE_ENGINE_SAVED: "Script saved" BTN_RULES_SCRIPT_COMPILING: "Compiling the scripts from BTN server, this may take a while..." BTN_RULES_SCRIPT_COMPILED: "Compiled {} scripts, took {}ms" -BTN_SERVICES_NEED_RESTART: "BTN Service Unavailable: Restart PeerBanHelper is required for loading BTN module and apply changes." \ No newline at end of file +BTN_SERVICES_NEED_RESTART: "BTN Service Unavailable: Restart PeerBanHelper is required for loading BTN module and apply changes." +GUI_MENU_QUICK_OPERATIONS: "Quick Actions" +GUI_MENU_SHOW_WINDOW: "Open Window" +GUI_MENU_STATS: "Statistics" +GUI_MENU_STATS_BANNED: "Banned {} peers ({} IP addresses)" +GUI_MENU_STATS_DOWNLOADER: "Connected {}/{} downloader" \ 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 e15c8df5ca..0ae4975f67 100644 --- a/src/main/resources/lang/messages_fallback.yml +++ b/src/main/resources/lang/messages_fallback.yml @@ -490,4 +490,9 @@ EXPRESS_RULE_ENGINE_DISALLOW_UNSAFE_SOURCE_ACCESS: "因安全原因,不允许 EXPRESS_RULE_ENGINE_SAVED: "脚本已保存" BTN_RULES_SCRIPT_COMPILING: "正在编译来自 BTN 服务器的可编程脚本,请稍等,这可能需要一点时间……" BTN_RULES_SCRIPT_COMPILED: "已成功编译 {} 个脚本,用时 {}ms" -BTN_SERVICES_NEED_RESTART: "BTN 服务不可用:需要重启 PeerBanHelper 以加载 BTN 模块并应用更改" \ No newline at end of file +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: "已连接 {}/{} 个下载器" \ 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 8cb666507c..ad580d6e73 100644 --- a/src/main/resources/lang/zh_cn/messages.yml +++ b/src/main/resources/lang/zh_cn/messages.yml @@ -490,4 +490,9 @@ EXPRESS_RULE_ENGINE_DISALLOW_UNSAFE_SOURCE_ACCESS: "因安全原因,不允许 EXPRESS_RULE_ENGINE_SAVED: "脚本已保存" BTN_RULES_SCRIPT_COMPILING: "正在编译来自 BTN 服务器的可编程脚本,请稍等,这可能需要一点时间……" BTN_RULES_SCRIPT_COMPILED: "已成功编译 {} 个脚本,用时 {}ms" -BTN_SERVICES_NEED_RESTART: "BTN 服务不可用:需要重启 PeerBanHelper 以加载 BTN 模块并应用更改" \ No newline at end of file +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: "已连接 {}/{} 个下载器" \ No newline at end of file diff --git a/src/main/resources/profile.yml b/src/main/resources/profile.yml index 5151f7685e..88b8253a08 100644 --- a/src/main/resources/profile.yml +++ b/src/main/resources/profile.yml @@ -53,8 +53,6 @@ module: - '{"method":"STARTS_WITH","content":"-hp"}' - '{"method":"STARTS_WITH","content":"-xm"}' - '{"method":"STARTS_WITH","content":"-dt"}' - - '{"method":"STARTS_WITH","content":"-gt0002"}' - - '{"method":"STARTS_WITH","content":"-gt0003"}' - '{"method":"CONTAINS","content":"-rn0.0.0"}' - '{"method":"STARTS_WITH","content":"-sd"}' - '{"method":"STARTS_WITH","content":"-xf"}' @@ -81,7 +79,6 @@ module: - '{"method":"STARTS_WITH","content":"dt "}' - '{"method":"STARTS_WITH","content":"xm/torrent"}' - '{"method":"STARTS_WITH","content":"xm "}' - - '{"method":"STARTS_WITH","content":"go.torrent"}' - '{"method":"STARTS_WITH","content":"taipei-torrent"}' - '{"method":"CONTAINS","content":"rain 0.0.0"}' - '{"method":"CONTAINS","content":"gopeed dev"}' @@ -92,11 +89,9 @@ module: - '{"method":"CONTAINS","content":"tudou"}' - '{"method":"CONTAINS","content":"torrentstorm"}' - '{"method":"CONTAINS","content":"qqdownload"}' - - '{"method":"CONTAINS","content":"anacrolix/torrent"}' - '{"method":"STARTS_WITH","content":"qbittorrent/3.3.15"}' - '{"method":"STARTS_WITH","content":"github.com/thank423/trafficconsume"}' - '{"method":"STARTS_WITH","content":"ޭ__"}' # 0xde-0xad-0xbe-0xef - - '{"method":"STARTS_WITH","content":"ljyun.cn/hangzhou/monitoring"}' - '{"method":"STARTS_WITH","content":"taipei-torrent"}' - '{"method":"STARTS_WITH","content":"-XL"}' # 进度作弊检查器:Progress Cheat Blocker diff --git a/webui/src/components/copier.vue b/webui/src/components/copier.vue new file mode 100644 index 0000000000..ef86fbd03b --- /dev/null +++ b/webui/src/components/copier.vue @@ -0,0 +1,26 @@ + + diff --git a/webui/src/locale/en-US.ts b/webui/src/locale/en-US.ts index 1a59ef4048..101cca6e4b 100644 --- a/webui/src/locale/en-US.ts +++ b/webui/src/locale/en-US.ts @@ -9,6 +9,7 @@ import topBanPageLocale from '@/views/ranks/locale/en-US' import ruleManageMentLocale from '@/views/rule-management/locale/en-US' import configLocale from '@/views/settings/locale/en-US' import alertLocale from './en-US/alert' +import copierLocale from './en-US/copier' import plusLocale from './en-US/plus' import settingsLocale from './en-US/settings' export default { @@ -60,5 +61,6 @@ export default { ...ruleManageMentLocale, ...chartsLocale, ...configLocale, - ...alertLocale + ...alertLocale, + ...copierLocale } diff --git a/webui/src/locale/en-US/copier.ts b/webui/src/locale/en-US/copier.ts new file mode 100644 index 0000000000..cfed7f8ca2 --- /dev/null +++ b/webui/src/locale/en-US/copier.ts @@ -0,0 +1,4 @@ +export default { + 'copier.copy': 'Copy', + 'copier.copied': 'Copied' +} diff --git a/webui/src/locale/zh-CN.ts b/webui/src/locale/zh-CN.ts index 0d798a83e2..4ef6215347 100644 --- a/webui/src/locale/zh-CN.ts +++ b/webui/src/locale/zh-CN.ts @@ -9,6 +9,7 @@ import topBanPageLocale from '@/views/ranks/locale/zh-CN' import ruleManageMentLocale from '@/views/rule-management/locale/zh-CN' import configLocale from '@/views/settings/locale/zh-CN' import alertLocale from './zh-CN/alert' +import copierLocale from './zh-CN/copier' import plusLocale from './zh-CN/plus' import settingsLocale from './zh-CN/settings' @@ -60,5 +61,6 @@ export default { ...ruleManageMentLocale, ...chartsLocale, ...configLocale, - ...alertLocale + ...alertLocale, + ...copierLocale } diff --git a/webui/src/locale/zh-CN/copier.ts b/webui/src/locale/zh-CN/copier.ts new file mode 100644 index 0000000000..b103b00694 --- /dev/null +++ b/webui/src/locale/zh-CN/copier.ts @@ -0,0 +1,4 @@ +export default { + 'copier.copy': '复制', + 'copier.copied': '已复制' +} diff --git a/webui/src/views/data-view/ipList/components/accessHistoryTable.vue b/webui/src/views/data-view/ipList/components/accessHistoryTable.vue index e980cd5f62..131e8ba25b 100644 --- a/webui/src/views/data-view/ipList/components/accessHistoryTable.vue +++ b/webui/src/views/data-view/ipList/components/accessHistoryTable.vue @@ -15,6 +15,7 @@ }" column-resizable size="medium" + :bordered="false" class="banlog-table" @page-change="changeCurrent" @page-size-change="changePageSize" @@ -57,6 +58,14 @@ > + - + - + @@ -224,19 +230,43 @@ const { data, loading, run, error } = useRequest(GetIPBasicData, { const endpointStore = useEndpointStore() const plusStatus = computed(() => endpointStore.plusStatus) +const activatedTab = ref<(string | number)[]>([]) + const { query } = useRoute() + +const handleSearch = (value: string) => { + if (value) { + data.value = undefined + activatedTab.value = [] + if (value !== query.ip) { + const search = new URLSearchParams(window.location.search) + search.set('ip', value) + window.history.pushState( + {}, + '', + new URL(`${window.location.origin}${window.location.pathname}?${search.toString()}`) + ) + } + run(value) + } +} onMounted(() => { if (query.ip) { searchInput.value = query.ip as string - run(searchInput.value) + handleSearch(searchInput.value) } }) -const handleSearch = (value: string) => { - if (value) { - run(value) + + diff --git a/webui/src/views/settings/components/info/components/logViewer.vue b/webui/src/views/settings/components/info/components/logViewer.vue index 1e8dc31e6f..bf1326db2c 100644 --- a/webui/src/views/settings/components/info/components/logViewer.vue +++ b/webui/src/views/settings/components/info/components/logViewer.vue @@ -1,49 +1,84 @@