From 032957f0f8da1f63a54601476b004230ca9e87b8 Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Tue, 1 Jul 2025 08:43:42 -0700 Subject: [PATCH] WebUI: Support reannouncing individual trackers --- WebAPI_Changelog.md | 5 +++ src/webui/api/torrentscontroller.cpp | 24 ++++++++++- src/webui/webapplication.h | 2 +- src/webui/www/private/index.html | 2 + src/webui/www/private/scripts/dynamicTable.js | 5 +++ .../www/private/scripts/prop-trackers.js | 40 +++++++++++++++++++ 6 files changed, 75 insertions(+), 3 deletions(-) diff --git a/WebAPI_Changelog.md b/WebAPI_Changelog.md index 375e24d69..08f5a0b37 100644 --- a/WebAPI_Changelog.md +++ b/WebAPI_Changelog.md @@ -1,5 +1,10 @@ # WebAPI Changelog +## 2.11.10 + +* [#22954](https://github.com/qbittorrent/qBittorrent/pull/22954) + * `torrents/reannounce` supports specifying trackers via `trackers` field + ## 2.11.9 * [#21015](https://github.com/qbittorrent/qBittorrent/pull/21015) diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 0574c8843..3d91d0203 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -1619,13 +1619,33 @@ void TorrentsController::reannounceAction() requireParams({u"hashes"_s}); const QStringList hashes {params()[u"hashes"_s].split(u'|')}; - applyToTorrents(hashes, [](BitTorrent::Torrent *const torrent) + const QStringList urlsParam {params()[u"urls"_s].split(u'|', Qt::SkipEmptyParts)}; + + QSet urls; + urls.reserve(urlsParam.size()); + for (const QString &urlStr : urlsParam) + urls << QUrl::fromPercentEncoding(urlStr.toLatin1()); + + applyToTorrents(hashes, [&urls](BitTorrent::Torrent *const torrent) { - if (!torrent->isStopped()) + if (torrent->isStopped()) + return; + + if (urls.isEmpty()) { torrent->forceReannounce(); torrent->forceDHTAnnounce(); } + else + { + const QList &trackers = torrent->trackers(); + for (qsizetype i = 0; i < trackers.size(); ++i) + { + const BitTorrent::TrackerEntryStatus &status = trackers.at(i); + if (urls.contains(status.url)) + torrent->forceReannounce(i); + } + } }); setResult(QString()); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 6ceb28593..2098e3635 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -53,7 +53,7 @@ #include "base/utils/version.h" #include "api/isessionmanager.h" -inline const Utils::Version<3, 2> API_VERSION {2, 11, 9}; +inline const Utils::Version<3, 2> API_VERSION {2, 11, 10}; class APIController; class AuthController; diff --git a/src/webui/www/private/index.html b/src/webui/www/private/index.html index 47fa063e9..255c4d4f1 100644 --- a/src/webui/www/private/index.html +++ b/src/webui/www/private/index.html @@ -248,6 +248,8 @@
  • QBT_TR(Edit tracker URL...)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(Edit tracker URL...)QBT_TR[CONTEXT=TrackerListWidget]
  • QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerListWidget]
  • QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget]
  • +
  • QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(Force reannounce to selected tracker(s))QBT_TR[CONTEXT=TrackerListWidget]
  • +
  • QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(Force reannounce to all trackers)QBT_TR[CONTEXT=TrackerListWidget]
    • QBT_TR(Add peers...)QBT_TR[CONTEXT=PeerListWidget] QBT_TR(Add peers...)QBT_TR[CONTEXT=PeerListWidget]
    • diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index 032a79a1e..3d3f656a8 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -1818,6 +1818,11 @@ window.qBittorrent.DynamicTable ??= (() => { onSelectedRowChanged() { updatePropertiesPanel(); } + + isStopped(hash) { + const row = this.getRow(hash); + return (row === undefined) ? true : row.full_data.state.includes("stopped"); + } } class TorrentPeersTable extends DynamicTable { diff --git a/src/webui/www/private/scripts/prop-trackers.js b/src/webui/www/private/scripts/prop-trackers.js index 945688aef..1ad3864c8 100644 --- a/src/webui/www/private/scripts/prop-trackers.js +++ b/src/webui/www/private/scripts/prop-trackers.js @@ -148,6 +148,12 @@ window.qBittorrent.PropTrackers ??= (() => { }, RemoveTracker: (element, ref) => { removeTrackerFN(element); + }, + ReannounceTrackers: (element, ref) => { + reannounceTrackersFN(element, torrentTrackersTable.selectedRowsIds()); + }, + ReannounceAllTrackers: (element, ref) => { + reannounceTrackersFN(element, []); } }, offsets: { @@ -164,6 +170,8 @@ window.qBittorrent.PropTrackers ??= (() => { this.hideItem("EditTracker"); this.hideItem("RemoveTracker"); this.hideItem("CopyTrackerUrl"); + this.hideItem("ReannounceTrackers"); + this.hideItem("ReannounceAllTrackers"); } else { if (selectedTrackers.length === 1) @@ -173,6 +181,16 @@ window.qBittorrent.PropTrackers ??= (() => { this.showItem("RemoveTracker"); this.showItem("CopyTrackerUrl"); + + const torrentHash = torrentsTable.getCurrentTorrentID(); + if (torrentsTable.isStopped(torrentHash)) { + this.hideItem("ReannounceTrackers"); + this.hideItem("ReannounceAllTrackers"); + } + else { + this.showItem("ReannounceTrackers"); + this.showItem("ReannounceAllTrackers"); + } } } }); @@ -253,6 +271,28 @@ window.qBittorrent.PropTrackers ??= (() => { }); }; + const reannounceTrackersFN = (element, trackers) => { + if (current_hash.length === 0) + return; + + const body = new URLSearchParams({ + hashes: current_hash + }); + if (trackers.length > 0) + body.set("urls", trackers.map(encodeURIComponent).join("|")); + + fetch("api/v2/torrents/reannounce", { + method: "POST", + body: body + }) + .then((response) => { + if (!response.ok) + return; + + updateData(); + }); + }; + const clear = () => { torrentTrackersTable.clear(); };