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.")]
|
[Description("When a torrent file or magnet is added, create a copy in this directory.")]
|
||||||
public String? CopyAddedTorrents { get; set; } = null;
|
public String? CopyAddedTorrents { get; set; } = null;
|
||||||
|
|
||||||
[DisplayName("Magnet tracker enrichment")]
|
[DisplayName("Magnet enrichment list")]
|
||||||
[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.")]
|
[Description("Optional. Specify the URL of a tracker list file to be appended to magnet links.")]
|
||||||
public MagnetTrackerEnrichment MagnetTrackerEnrichment { get; set; } = MagnetTrackerEnrichment.None;
|
public String? MagnetTrackerEnrichment { get; set; } = null;
|
||||||
|
|
||||||
[DisplayName("Disable update notifications")]
|
[DisplayName("Disable update notifications")]
|
||||||
[Description("Ignore update notifications. You will still be notified if the version you are running has a security vulnerability.")]
|
[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<ILogger<TorrentsService>> TorrentsLoggerMock;
|
||||||
public readonly Mock<IDownloads> DownloadsMock;
|
public readonly Mock<IDownloads> DownloadsMock;
|
||||||
public readonly Mock<ITorrentData> TorrentDataMock;
|
public readonly Mock<ITorrentData> TorrentDataMock;
|
||||||
|
public readonly Mock<ITrackerListGrabber> TrackerListGrabberMock;
|
||||||
|
|
||||||
public Mocks()
|
public Mocks()
|
||||||
{
|
{
|
||||||
TorrentDataMock = new();
|
TorrentDataMock = new();
|
||||||
DownloadsMock = new();
|
DownloadsMock = new();
|
||||||
|
TrackerListGrabberMock = new();
|
||||||
|
|
||||||
TorrentsLoggerMock = new();
|
TorrentsLoggerMock = new();
|
||||||
|
|
||||||
|
|
@ -58,7 +60,7 @@ public class TorrentsTest
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
return new ()
|
return new()
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
torrent,
|
torrent,
|
||||||
|
|
@ -102,6 +104,7 @@ public class TorrentsTest
|
||||||
mocks.DownloadsMock.Object,
|
mocks.DownloadsMock.Object,
|
||||||
mocks.ProcessFactoryMock.Object,
|
mocks.ProcessFactoryMock.Object,
|
||||||
fileSystemMock,
|
fileSystemMock,
|
||||||
|
mocks.TrackerListGrabberMock.Object,
|
||||||
null!, // Torrent Clients are not used by `RunTorrentComplete`, this is fine
|
null!, // Torrent Clients are not used by `RunTorrentComplete`, this is fine
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
|
|
@ -167,6 +170,7 @@ public class TorrentsTest
|
||||||
mocks.DownloadsMock.Object,
|
mocks.DownloadsMock.Object,
|
||||||
mocks.ProcessFactoryMock.Object,
|
mocks.ProcessFactoryMock.Object,
|
||||||
fileSystemMock,
|
fileSystemMock,
|
||||||
|
mocks.TrackerListGrabberMock.Object,
|
||||||
null!, // Torrent Clients are not used by `RunTorrentComplete`, this is fine
|
null!, // Torrent Clients are not used by `RunTorrentComplete`, this is fine
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
|
|
@ -214,6 +218,7 @@ public class TorrentsTest
|
||||||
mocks.DownloadsMock.Object,
|
mocks.DownloadsMock.Object,
|
||||||
mocks.ProcessFactoryMock.Object,
|
mocks.ProcessFactoryMock.Object,
|
||||||
fileSystemMock,
|
fileSystemMock,
|
||||||
|
mocks.TrackerListGrabberMock.Object,
|
||||||
null!, // Torrent Clients are not used by `RunTorrentComplete`, this is fine
|
null!, // Torrent Clients are not used by `RunTorrentComplete`, this is fine
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
|
|
@ -280,6 +285,7 @@ public class TorrentsTest
|
||||||
mocks.DownloadsMock.Object,
|
mocks.DownloadsMock.Object,
|
||||||
mocks.ProcessFactoryMock.Object,
|
mocks.ProcessFactoryMock.Object,
|
||||||
fileSystemMock,
|
fileSystemMock,
|
||||||
|
mocks.TrackerListGrabberMock.Object,
|
||||||
null!, // Torrent Clients are not used by `RunTorrentComplete`, this is fine
|
null!, // Torrent Clients are not used by `RunTorrentComplete`, this is fine
|
||||||
null!,
|
null!,
|
||||||
null!,
|
null!,
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ public static class DiConfig
|
||||||
services.AddScoped<DebridLinkClient>();
|
services.AddScoped<DebridLinkClient>();
|
||||||
|
|
||||||
services.AddSingleton<IDownloadableFileFilter, DownloadableFileFilter>();
|
services.AddSingleton<IDownloadableFileFilter, DownloadableFileFilter>();
|
||||||
|
services.AddSingleton<ITrackerListGrabber, TrackerListGrabber>();
|
||||||
|
|
||||||
services.AddSingleton<IAuthorizationHandler, AuthSettingHandler>();
|
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,
|
IDownloads downloads,
|
||||||
IProcessFactory processFactory,
|
IProcessFactory processFactory,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
|
ITrackerListGrabber trackerListGrabber,
|
||||||
AllDebridTorrentClient allDebridTorrentClient,
|
AllDebridTorrentClient allDebridTorrentClient,
|
||||||
PremiumizeTorrentClient premiumizeTorrentClient,
|
PremiumizeTorrentClient premiumizeTorrentClient,
|
||||||
RealDebridTorrentClient realDebridTorrentClient,
|
RealDebridTorrentClient realDebridTorrentClient,
|
||||||
|
|
@ -32,6 +33,7 @@ public class Torrents(
|
||||||
TorBoxTorrentClient torBoxTorrentClient)
|
TorBoxTorrentClient torBoxTorrentClient)
|
||||||
{
|
{
|
||||||
private static readonly SemaphoreSlim RealDebridUpdateLock = new(1, 1);
|
private static readonly SemaphoreSlim RealDebridUpdateLock = new(1, 1);
|
||||||
|
private readonly ITrackerListGrabber _trackerListGrabber = trackerListGrabber;
|
||||||
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
|
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
|
||||||
{
|
{
|
||||||
ReferenceHandler = ReferenceHandler.IgnoreCycles
|
ReferenceHandler = ReferenceHandler.IgnoreCycles
|
||||||
|
|
@ -107,45 +109,37 @@ public class Torrents(
|
||||||
await torrentData.UpdateCategory(torrent.TorrentId, category);
|
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 (string.IsNullOrWhiteSpace(Settings.Get.General.MagnetTrackerEnrichment)) return magnetLink;
|
||||||
if (enrichment == MagnetTrackerEnrichment.None) { return magnetLink; }
|
|
||||||
|
|
||||||
var url = $"https://github.com/ngosang/trackerslist/raw/refs/heads/master/{enrichment}.txt";
|
try
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
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;
|
trackerQuery = "&tr=" + trackerQuery;
|
||||||
foreach (var val in decodedParams.GetValues(key)!)
|
|
||||||
{
|
|
||||||
pieces.Add($"{key}={HttpUtility.UrlEncode(val)}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
var enriched = await EnrichMagnetLink(magnetLink);
|
||||||
MagnetLink magnet;
|
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