Add feature to remove torrent tracker URLs from torrents for private tracker downloads (#99)
- Remove trackers from torrenst/magnet URI --------- Co-authored-by: Mukhtar Akere <akeremukhtar10@gmail.com>
This commit is contained in:
@@ -184,7 +184,11 @@ func (c *Cache) MarkLinkAsInvalid(downloadLink types.DownloadLink, reason string
|
||||
c.logger.Error().Str("token", utils.Mask(downloadLink.Token)).Msg("Account not found to delete download link")
|
||||
return
|
||||
}
|
||||
c.client.DeleteDownloadLink(account, downloadLink)
|
||||
|
||||
if err := c.client.DeleteDownloadLink(account, downloadLink); err != nil {
|
||||
c.logger.Error().Err(err).Str("token", utils.Mask(downloadLink.Token)).Msg("Failed to delete download link from account")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,9 +104,6 @@ func (c *Cache) Stream(ctx context.Context, start, end int64, linkFunc func() (t
|
||||
}
|
||||
|
||||
if streamErr.LinkError {
|
||||
c.logger.Trace().
|
||||
Int("retries", retry).
|
||||
Msg("Link error, getting fresh link")
|
||||
lastErr = streamErr
|
||||
// Try new link
|
||||
downloadLink, err = linkFunc()
|
||||
@@ -116,7 +113,7 @@ func (c *Cache) Stream(ctx context.Context, start, end int64, linkFunc func() (t
|
||||
continue
|
||||
}
|
||||
|
||||
// Retryable HTTP error (429, 503, etc.) - retry network
|
||||
// Retryable HTTP error (429, 503, 404 etc.) - retry network
|
||||
lastErr = streamErr
|
||||
c.logger.Trace().
|
||||
Err(lastErr).
|
||||
@@ -198,9 +195,6 @@ func (c *Cache) doRequest(ctx context.Context, url string, start, end int64) (*h
|
||||
}
|
||||
|
||||
func (c *Cache) handleHTTPError(resp *http.Response, downloadLink types.DownloadLink) StreamError {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
bodyStr := strings.ToLower(string(body))
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusNotFound:
|
||||
c.MarkLinkAsInvalid(downloadLink, "link_not_found")
|
||||
@@ -211,6 +205,8 @@ func (c *Cache) handleHTTPError(resp *http.Response, downloadLink types.Download
|
||||
}
|
||||
|
||||
case http.StatusServiceUnavailable:
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
bodyStr := strings.ToLower(string(body))
|
||||
if strings.Contains(bodyStr, "bandwidth") || strings.Contains(bodyStr, "traffic") {
|
||||
c.MarkLinkAsInvalid(downloadLink, "bandwidth_exceeded")
|
||||
return StreamError{
|
||||
@@ -230,6 +226,7 @@ func (c *Cache) handleHTTPError(resp *http.Response, downloadLink types.Download
|
||||
|
||||
default:
|
||||
retryable := resp.StatusCode >= 500
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return StreamError{
|
||||
Err: fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)),
|
||||
Retryable: retryable,
|
||||
|
||||
+9
-2
@@ -102,6 +102,13 @@ func (q *QBit) handleTorrentsAdd(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.ToLower(r.FormValue("sequentialDownload")) == "true" {
|
||||
action = "download"
|
||||
}
|
||||
rmTrackerUrls := strings.ToLower(r.FormValue("firstLastPiecePrio")) == "true"
|
||||
|
||||
// Check config setting - if always remove tracker URLs is enabled, force it to true
|
||||
if q.AlwaysRmTrackerUrls {
|
||||
rmTrackerUrls = true
|
||||
}
|
||||
|
||||
debridName := r.FormValue("debrid")
|
||||
category := r.FormValue("category")
|
||||
_arr := getArrFromContext(ctx)
|
||||
@@ -118,7 +125,7 @@ func (q *QBit) handleTorrentsAdd(w http.ResponseWriter, r *http.Request) {
|
||||
urlList = append(urlList, strings.TrimSpace(u))
|
||||
}
|
||||
for _, url := range urlList {
|
||||
if err := q.addMagnet(ctx, url, _arr, debridName, action); err != nil {
|
||||
if err := q.addMagnet(ctx, url, _arr, debridName, action, rmTrackerUrls); err != nil {
|
||||
q.logger.Debug().Msgf("Error adding magnet: %s", err.Error())
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
@@ -131,7 +138,7 @@ func (q *QBit) handleTorrentsAdd(w http.ResponseWriter, r *http.Request) {
|
||||
if r.MultipartForm != nil && r.MultipartForm.File != nil {
|
||||
if files := r.MultipartForm.File["torrents"]; len(files) > 0 {
|
||||
for _, fileHeader := range files {
|
||||
if err := q.addTorrent(ctx, fileHeader, _arr, debridName, action); err != nil {
|
||||
if err := q.addTorrent(ctx, fileHeader, _arr, debridName, action, rmTrackerUrls); err != nil {
|
||||
q.logger.Debug().Err(err).Msgf("Error adding torrent")
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
|
||||
+15
-13
@@ -8,25 +8,27 @@ import (
|
||||
)
|
||||
|
||||
type QBit struct {
|
||||
Username string
|
||||
Password string
|
||||
DownloadFolder string
|
||||
Categories []string
|
||||
storage *wire.TorrentStorage
|
||||
logger zerolog.Logger
|
||||
Tags []string
|
||||
Username string
|
||||
Password string
|
||||
DownloadFolder string
|
||||
Categories []string
|
||||
AlwaysRmTrackerUrls bool
|
||||
storage *wire.TorrentStorage
|
||||
logger zerolog.Logger
|
||||
Tags []string
|
||||
}
|
||||
|
||||
func New() *QBit {
|
||||
_cfg := config.Get()
|
||||
cfg := _cfg.QBitTorrent
|
||||
return &QBit{
|
||||
Username: cfg.Username,
|
||||
Password: cfg.Password,
|
||||
DownloadFolder: cfg.DownloadFolder,
|
||||
Categories: cfg.Categories,
|
||||
storage: wire.Get().Torrents(),
|
||||
logger: logger.New("qbit"),
|
||||
Username: cfg.Username,
|
||||
Password: cfg.Password,
|
||||
DownloadFolder: cfg.DownloadFolder,
|
||||
Categories: cfg.Categories,
|
||||
AlwaysRmTrackerUrls: cfg.AlwaysRmTrackerUrls,
|
||||
storage: wire.Get().Torrents(),
|
||||
logger: logger.New("qbit"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -14,8 +14,8 @@ import (
|
||||
)
|
||||
|
||||
// All torrent-related helpers goes here
|
||||
func (q *QBit) addMagnet(ctx context.Context, url string, arr *arr.Arr, debrid string, action string) error {
|
||||
magnet, err := utils.GetMagnetFromUrl(url)
|
||||
func (q *QBit) addMagnet(ctx context.Context, url string, arr *arr.Arr, debrid string, action string, rmTrackerUrls bool) error {
|
||||
magnet, err := utils.GetMagnetFromUrl(url, rmTrackerUrls)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing magnet link: %w", err)
|
||||
}
|
||||
@@ -30,11 +30,11 @@ func (q *QBit) addMagnet(ctx context.Context, url string, arr *arr.Arr, debrid s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *QBit) addTorrent(ctx context.Context, fileHeader *multipart.FileHeader, arr *arr.Arr, debrid string, action string) error {
|
||||
func (q *QBit) addTorrent(ctx context.Context, fileHeader *multipart.FileHeader, arr *arr.Arr, debrid string, action string, rmTrackerUrls bool) error {
|
||||
file, _ := fileHeader.Open()
|
||||
defer file.Close()
|
||||
var reader io.Reader = file
|
||||
magnet, err := utils.GetMagnetFromFile(reader, fileHeader.Filename)
|
||||
magnet, err := utils.GetMagnetFromFile(reader, fileHeader.Filename, rmTrackerUrls)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading file: %s \n %w", fileHeader.Filename, err)
|
||||
}
|
||||
|
||||
+9
-2
@@ -46,6 +46,13 @@ func (wb *Web) handleAddContent(w http.ResponseWriter, r *http.Request) {
|
||||
skipMultiSeason := r.FormValue("skipMultiSeason") == "true"
|
||||
|
||||
downloadUncached := r.FormValue("downloadUncached") == "true"
|
||||
rmTrackerUrls := r.FormValue("rmTrackerUrls") == "true"
|
||||
|
||||
// Check config setting - if always remove tracker URLs is enabled, force it to true
|
||||
cfg := config.Get()
|
||||
if cfg.QBitTorrent.AlwaysRmTrackerUrls {
|
||||
rmTrackerUrls = true
|
||||
}
|
||||
|
||||
_arr := _store.Arr().Get(arrName)
|
||||
if _arr == nil {
|
||||
@@ -63,7 +70,7 @@ func (wb *Web) handleAddContent(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
for _, url := range urlList {
|
||||
magnet, err := utils.GetMagnetFromUrl(url)
|
||||
magnet, err := utils.GetMagnetFromUrl(url, rmTrackerUrls)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Sprintf("Failed to parse URL %s: %v", url, err))
|
||||
continue
|
||||
@@ -88,7 +95,7 @@ func (wb *Web) handleAddContent(w http.ResponseWriter, r *http.Request) {
|
||||
continue
|
||||
}
|
||||
|
||||
magnet, err := utils.GetMagnetFromFile(file, fileHeader.Filename)
|
||||
magnet, err := utils.GetMagnetFromFile(file, fileHeader.Filename, rmTrackerUrls)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Sprintf("Failed to parse torrent file %s: %v", fileHeader.Filename, err))
|
||||
continue
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -114,7 +114,7 @@ class ConfigManager {
|
||||
populateQBittorrentSettings(qbitConfig) {
|
||||
if (!qbitConfig) return;
|
||||
|
||||
const fields = ['download_folder', 'refresh_interval', 'max_downloads', 'skip_pre_cache'];
|
||||
const fields = ['download_folder', 'refresh_interval', 'max_downloads', 'skip_pre_cache', 'always_rm_tracker_urls'];
|
||||
|
||||
fields.forEach(field => {
|
||||
const element = document.querySelector(`[name="qbit.${field}"]`);
|
||||
@@ -1183,7 +1183,8 @@ class ConfigManager {
|
||||
download_folder: document.querySelector('[name="qbit.download_folder"]').value,
|
||||
refresh_interval: parseInt(document.querySelector('[name="qbit.refresh_interval"]').value) || 30,
|
||||
max_downloads: parseInt(document.querySelector('[name="qbit.max_downloads"]').value) || 0,
|
||||
skip_pre_cache: document.querySelector('[name="qbit.skip_pre_cache"]').checked
|
||||
skip_pre_cache: document.querySelector('[name="qbit.skip_pre_cache"]').checked,
|
||||
always_rm_tracker_urls: document.querySelector('[name="qbit.always_rm_tracker_urls"]').checked
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ class DownloadManager {
|
||||
arr: document.getElementById('arr'),
|
||||
downloadAction: document.getElementById('downloadAction'),
|
||||
downloadUncached: document.getElementById('downloadUncached'),
|
||||
rmTrackerUrls: document.getElementById('rmTrackerUrls'),
|
||||
downloadFolder: document.getElementById('downloadFolder'),
|
||||
debrid: document.getElementById('debrid'),
|
||||
submitBtn: document.getElementById('submitDownload'),
|
||||
@@ -34,6 +35,7 @@ class DownloadManager {
|
||||
this.refs.arr.addEventListener('change', () => this.saveOptions());
|
||||
this.refs.downloadAction.addEventListener('change', () => this.saveOptions());
|
||||
this.refs.downloadUncached.addEventListener('change', () => this.saveOptions());
|
||||
this.refs.rmTrackerUrls.addEventListener('change', () => this.saveOptions());
|
||||
this.refs.downloadFolder.addEventListener('change', () => this.saveOptions());
|
||||
|
||||
// File input enhancement
|
||||
@@ -48,12 +50,14 @@ class DownloadManager {
|
||||
category: localStorage.getItem('downloadCategory') || '',
|
||||
action: localStorage.getItem('downloadAction') || 'symlink',
|
||||
uncached: localStorage.getItem('downloadUncached') === 'true',
|
||||
rmTrackerUrls: localStorage.getItem('rmTrackerUrls') === 'true',
|
||||
folder: localStorage.getItem('downloadFolder') || this.downloadFolder
|
||||
};
|
||||
|
||||
this.refs.arr.value = savedOptions.category;
|
||||
this.refs.downloadAction.value = savedOptions.action;
|
||||
this.refs.downloadUncached.checked = savedOptions.uncached;
|
||||
this.refs.rmTrackerUrls.checked = savedOptions.rmTrackerUrls;
|
||||
this.refs.downloadFolder.value = savedOptions.folder;
|
||||
}
|
||||
|
||||
@@ -61,6 +65,12 @@ class DownloadManager {
|
||||
localStorage.setItem('downloadCategory', this.refs.arr.value);
|
||||
localStorage.setItem('downloadAction', this.refs.downloadAction.value);
|
||||
localStorage.setItem('downloadUncached', this.refs.downloadUncached.checked.toString());
|
||||
|
||||
// Only save rmTrackerUrls if not disabled (i.e., not forced by config)
|
||||
if (!this.refs.rmTrackerUrls.disabled) {
|
||||
localStorage.setItem('rmTrackerUrls', this.refs.rmTrackerUrls.checked.toString());
|
||||
}
|
||||
|
||||
localStorage.setItem('downloadFolder', this.refs.downloadFolder.value);
|
||||
}
|
||||
|
||||
@@ -114,6 +124,7 @@ class DownloadManager {
|
||||
formData.append('downloadFolder', this.refs.downloadFolder.value);
|
||||
formData.append('action', this.refs.downloadAction.value);
|
||||
formData.append('downloadUncached', this.refs.downloadUncached.checked);
|
||||
formData.append('rmTrackerUrls', this.refs.rmTrackerUrls.checked);
|
||||
|
||||
if (this.refs.debrid) {
|
||||
formData.append('debrid', this.refs.debrid.value);
|
||||
|
||||
@@ -346,6 +346,16 @@
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" class="checkbox" name="qbit.always_rm_tracker_urls" id="qbit.always_rm_tracker_urls">
|
||||
<div>
|
||||
<span class="label-text font-medium">Always Remove Tracker URLs</span>
|
||||
<div class="label-text-alt">Allows you to <a href="https://sirrobot01.github.io/decypharr/features/repair-worker/private-tracker-downloads" class="link link-hover font-semibold" target="_blank">download private tracker torrents</a> with lower risk</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -131,6 +131,15 @@
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" class="checkbox" name="rmTrackerUrls" id="rmTrackerUrls" {{ if .AlwaysRmTrackerUrls }}checked disabled{{ end }}>
|
||||
<div>
|
||||
<span class="label-text font-medium">Remove Tracker</span>
|
||||
<div class="label-text-alt">Allows you to <a href="https://sirrobot01.github.io/decypharr/features/repair-worker/private-tracker-downloads" class="link link-hover font-semibold" target="_blank">download private tracker torrents</a> with lower risk</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
|
||||
+9
-8
@@ -130,14 +130,15 @@ func (wb *Web) DownloadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
debrids = append(debrids, d.Name)
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"URLBase": cfg.URLBase,
|
||||
"Page": "download",
|
||||
"Title": "Download",
|
||||
"Debrids": debrids,
|
||||
"HasMultiDebrid": len(debrids) > 1,
|
||||
"DownloadFolder": cfg.QBitTorrent.DownloadFolder,
|
||||
"NeedSetup": cfg.CheckSetup() != nil,
|
||||
"SetupError": cfg.CheckSetup(),
|
||||
"URLBase": cfg.URLBase,
|
||||
"Page": "download",
|
||||
"Title": "Download",
|
||||
"Debrids": debrids,
|
||||
"HasMultiDebrid": len(debrids) > 1,
|
||||
"DownloadFolder": cfg.QBitTorrent.DownloadFolder,
|
||||
"AlwaysRmTrackerUrls": cfg.QBitTorrent.AlwaysRmTrackerUrls,
|
||||
"NeedSetup": cfg.CheckSetup() != nil,
|
||||
"SetupError": cfg.CheckSetup(),
|
||||
}
|
||||
_ = wb.templates.ExecuteTemplate(w, "layout", data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user