Allow user to define tracker list
This commit is contained in:
parent
e6a22f6780
commit
ae66b40a3d
7 changed files with 76 additions and 71 deletions
|
|
@ -1,36 +0,0 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace RdtClient.Data.Enums;
|
||||
|
||||
public enum MagnetTrackerEnrichment
|
||||
{
|
||||
[Description("None (do not modify magnet links)")]
|
||||
None,
|
||||
|
||||
[Description("Best trackers")]
|
||||
trackers_best,
|
||||
|
||||
[Description("All trackers")]
|
||||
trackers_all,
|
||||
|
||||
[Description("All UDP trackers")]
|
||||
trackers_all_udp,
|
||||
|
||||
[Description("All HTTP trackers")]
|
||||
trackers_all_http,
|
||||
|
||||
[Description("All HTTPS trackers")]
|
||||
trackers_all_https,
|
||||
|
||||
[Description("All WebSocket (WS) trackers")]
|
||||
trackers_all_ws,
|
||||
|
||||
[Description("Best IP-only trackers")]
|
||||
trackers_best_ip,
|
||||
|
||||
[Description("All I2P trackers")]
|
||||
trackers_all_i2p,
|
||||
|
||||
[Description("All IP-only trackers")]
|
||||
trackers_all_ip
|
||||
}
|
||||
|
|
@ -75,9 +75,9 @@ Supports the following parameters:
|
|||
[Description("When a torrent file or magnet is added, create a copy in this directory.")]
|
||||
public String? CopyAddedTorrents { get; set; } = null;
|
||||
|
||||
[DisplayName("Magnet tracker enrichment")]
|
||||
[Description(@"Choose which tracker list to append to magnet links, see this <a href=""https://github.com/ngosang/trackerslist/blob/master/README.md"" target=""_blank"">README</a> for more info.")]
|
||||
public MagnetTrackerEnrichment MagnetTrackerEnrichment { get; set; } = MagnetTrackerEnrichment.None;
|
||||
[DisplayName("Magnet enrichment list")]
|
||||
[Description("Optional. Specify the URL of a tracker list file to be appended to magnet links.")]
|
||||
public String? MagnetTrackerEnrichment { get; set; } = null;
|
||||
|
||||
[DisplayName("Disable update notifications")]
|
||||
[Description("Ignore update notifications. You will still be notified if the version you are running has a security vulnerability.")]
|
||||
|
|
|
|||
|
|
@ -20,11 +20,13 @@ class Mocks
|
|||
public readonly Mock<ILogger<TorrentsService>> TorrentsLoggerMock;
|
||||
public readonly Mock<IDownloads> DownloadsMock;
|
||||
public readonly Mock<ITorrentData> TorrentDataMock;
|
||||
public readonly Mock<ITrackerListGrabber> TrackerListGrabberMock;
|
||||
|
||||
public Mocks()
|
||||
{
|
||||
TorrentDataMock = new();
|
||||
DownloadsMock = new();
|
||||
TrackerListGrabberMock = new();
|
||||
|
||||
TorrentsLoggerMock = new();
|
||||
|
||||
|
|
@ -58,7 +60,7 @@ public class TorrentsTest
|
|||
}
|
||||
];
|
||||
|
||||
return new ()
|
||||
return new()
|
||||
{
|
||||
{
|
||||
torrent,
|
||||
|
|
@ -102,6 +104,7 @@ public class TorrentsTest
|
|||
mocks.DownloadsMock.Object,
|
||||
mocks.ProcessFactoryMock.Object,
|
||||
fileSystemMock,
|
||||
mocks.TrackerListGrabberMock.Object,
|
||||
null!, // Torrent Clients are not used by `RunTorrentComplete`, this is fine
|
||||
null!,
|
||||
null!,
|
||||
|
|
@ -167,6 +170,7 @@ public class TorrentsTest
|
|||
mocks.DownloadsMock.Object,
|
||||
mocks.ProcessFactoryMock.Object,
|
||||
fileSystemMock,
|
||||
mocks.TrackerListGrabberMock.Object,
|
||||
null!, // Torrent Clients are not used by `RunTorrentComplete`, this is fine
|
||||
null!,
|
||||
null!,
|
||||
|
|
@ -214,6 +218,7 @@ public class TorrentsTest
|
|||
mocks.DownloadsMock.Object,
|
||||
mocks.ProcessFactoryMock.Object,
|
||||
fileSystemMock,
|
||||
mocks.TrackerListGrabberMock.Object,
|
||||
null!, // Torrent Clients are not used by `RunTorrentComplete`, this is fine
|
||||
null!,
|
||||
null!,
|
||||
|
|
@ -280,6 +285,7 @@ public class TorrentsTest
|
|||
mocks.DownloadsMock.Object,
|
||||
mocks.ProcessFactoryMock.Object,
|
||||
fileSystemMock,
|
||||
mocks.TrackerListGrabberMock.Object,
|
||||
null!, // Torrent Clients are not used by `RunTorrentComplete`, this is fine
|
||||
null!,
|
||||
null!,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ public static class DiConfig
|
|||
services.AddScoped<DebridLinkClient>();
|
||||
|
||||
services.AddSingleton<IDownloadableFileFilter, DownloadableFileFilter>();
|
||||
services.AddSingleton<ITrackerListGrabber, TrackerListGrabber>();
|
||||
|
||||
services.AddSingleton<IAuthorizationHandler, AuthSettingHandler>();
|
||||
|
||||
|
|
|
|||
6
server/RdtClient.Service/Services/ITrackerListGrabber.cs
Normal file
6
server/RdtClient.Service/Services/ITrackerListGrabber.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
namespace RdtClient.Service.Services;
|
||||
|
||||
public interface ITrackerListGrabber
|
||||
{
|
||||
Task<String[]> GetTrackers();
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ public class Torrents(
|
|||
IDownloads downloads,
|
||||
IProcessFactory processFactory,
|
||||
IFileSystem fileSystem,
|
||||
ITrackerListGrabber trackerListGrabber,
|
||||
AllDebridTorrentClient allDebridTorrentClient,
|
||||
PremiumizeTorrentClient premiumizeTorrentClient,
|
||||
RealDebridTorrentClient realDebridTorrentClient,
|
||||
|
|
@ -32,6 +33,7 @@ public class Torrents(
|
|||
TorBoxTorrentClient torBoxTorrentClient)
|
||||
{
|
||||
private static readonly SemaphoreSlim RealDebridUpdateLock = new(1, 1);
|
||||
private readonly ITrackerListGrabber _trackerListGrabber = trackerListGrabber;
|
||||
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
|
||||
{
|
||||
ReferenceHandler = ReferenceHandler.IgnoreCycles
|
||||
|
|
@ -107,45 +109,37 @@ public class Torrents(
|
|||
await torrentData.UpdateCategory(torrent.TorrentId, category);
|
||||
}
|
||||
|
||||
public static async Task<string> EnrichMagnetLink(string magnetLink)
|
||||
private async Task<String> EnrichMagnetLink(String magnetLink)
|
||||
{
|
||||
var enrichment = Settings.Get.General.MagnetTrackerEnrichment;
|
||||
if (enrichment == MagnetTrackerEnrichment.None) { return magnetLink; }
|
||||
if (string.IsNullOrWhiteSpace(Settings.Get.General.MagnetTrackerEnrichment)) return magnetLink;
|
||||
|
||||
var url = $"https://github.com/ngosang/trackerslist/raw/refs/heads/master/{enrichment}.txt";
|
||||
var newTrackers = (await new HttpClient().GetStringAsync(url)).Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
||||
|
||||
var originalUri = new Uri(magnetLink);
|
||||
var decodedParams = HttpUtility.ParseQueryString(originalUri.Query);
|
||||
var rawXt = decodedParams["xt"]!;
|
||||
decodedParams.Remove("xt");
|
||||
|
||||
var existingTrackers = decodedParams.GetValues("tr") ?? Array.Empty<string>();
|
||||
decodedParams.Remove("tr");
|
||||
var allTrackers = existingTrackers.Concat(newTrackers).Distinct(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var pieces = new List<string> { $"xt={rawXt}" };
|
||||
var allKeys = decodedParams.AllKeys;
|
||||
if (allKeys != null)
|
||||
try
|
||||
{
|
||||
foreach (string? key in allKeys)
|
||||
var newTrackers = await _trackerListGrabber.GetTrackers();
|
||||
|
||||
var uri = new Uri(magnetLink);
|
||||
var query = HttpUtility.ParseQueryString(uri.Query);
|
||||
var existingTrackers = query.GetValues("tr") ?? [];
|
||||
var allTrackers = existingTrackers.Concat(newTrackers).Distinct(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var trackerQuery = string.Join("&tr=", allTrackers.Select(Uri.EscapeDataString));
|
||||
if (!string.IsNullOrEmpty(trackerQuery))
|
||||
{
|
||||
if (key == null) continue;
|
||||
foreach (var val in decodedParams.GetValues(key)!)
|
||||
{
|
||||
pieces.Add($"{key}={HttpUtility.UrlEncode(val)}");
|
||||
}
|
||||
trackerQuery = "&tr=" + trackerQuery;
|
||||
}
|
||||
|
||||
var baseWithoutTrackers = magnetLink.Split(["&tr="], StringSplitOptions.None)[0];
|
||||
var separator = baseWithoutTrackers.Contains('?') ? "&" : "?";
|
||||
return baseWithoutTrackers + separator + trackerQuery.TrimStart('&');
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "{Message}, trying to enrich {Magnet}", ex.Message, magnetLink);
|
||||
return magnetLink;
|
||||
}
|
||||
foreach (var tr in allTrackers) { pieces.Add($"tr={HttpUtility.UrlEncode(tr)}"); }
|
||||
|
||||
var basePart = originalUri.GetLeftPart(UriPartial.Path);
|
||||
var enriched = $"{basePart}?{string.Join("&", pieces)}";
|
||||
|
||||
return enriched;
|
||||
}
|
||||
|
||||
public async Task<Torrent> AddMagnetToDebridQueue(string magnetLink, Torrent torrent)
|
||||
public async Task<Torrent> AddMagnetToDebridQueue(String magnetLink, Torrent torrent)
|
||||
{
|
||||
var enriched = await EnrichMagnetLink(magnetLink);
|
||||
MagnetLink magnet;
|
||||
|
|
|
|||
34
server/RdtClient.Service/Services/TrackerListGrabber.cs
Normal file
34
server/RdtClient.Service/Services/TrackerListGrabber.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
namespace RdtClient.Service.Services;
|
||||
|
||||
public class TrackerListGrabber : ITrackerListGrabber
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public TrackerListGrabber(IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
public async Task<String[]> GetTrackers()
|
||||
{
|
||||
var trackerUrlList = Settings.Get.General.MagnetTrackerEnrichment;
|
||||
if (string.IsNullOrWhiteSpace(trackerUrlList))
|
||||
{
|
||||
return Array.Empty<String>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var httpClient = _httpClientFactory.CreateClient();
|
||||
var result = await httpClient.GetStringAsync(trackerUrlList);
|
||||
return result
|
||||
.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Array.Empty<String>();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue