From 14fdea164ce78cfa6b2ca7c288692a1c7a7e891b Mon Sep 17 00:00:00 2001 From: Diego Heras Date: Mon, 2 Nov 2020 17:43:13 +0100 Subject: [PATCH] core: generate magnet link from infohash and vice versa. resolves #8590 (#10087) * global list of public trackers * infohash => magnet link (only in public trackers) * magnet link => infohash --- src/Jackett.Common/Indexers/BaseIndexer.cs | 8 ++++ src/Jackett.Common/Utils/MagnetUtil.cs | 48 +++++++++++++++++++ .../Common/Indexers/BaseWebIndexerTests.cs | 40 +++++++++++++++- .../Common/Utils/MagnetUtilTests.cs | 45 +++++++++++++++++ .../TestHelpers/TestWebIndexer.cs | 1 + 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 src/Jackett.Common/Utils/MagnetUtil.cs create mode 100644 src/Jackett.Test/Common/Utils/MagnetUtilTests.cs diff --git a/src/Jackett.Common/Indexers/BaseIndexer.cs b/src/Jackett.Common/Indexers/BaseIndexer.cs index 8500af18c..b9389a39a 100644 --- a/src/Jackett.Common/Indexers/BaseIndexer.cs +++ b/src/Jackett.Common/Indexers/BaseIndexer.cs @@ -296,6 +296,14 @@ namespace Jackett.Common.Indexers if (r.PublishDate > DateTime.Now) r.PublishDate = DateTime.Now; + // generate magnet link from info hash (not allowed for private sites) + if (r.MagnetUri == null && !string.IsNullOrWhiteSpace(r.InfoHash) && Type != "private") + r.MagnetUri = MagnetUtil.InfoHashToPublicMagnet(r.InfoHash, r.Title); + + // generate info hash from magnet link + if (r.MagnetUri != null && string.IsNullOrWhiteSpace(r.InfoHash)) + r.InfoHash = MagnetUtil.MagnetToInfoHash(r.MagnetUri); + return r; }); return fixedResults; diff --git a/src/Jackett.Common/Utils/MagnetUtil.cs b/src/Jackett.Common/Utils/MagnetUtil.cs new file mode 100644 index 000000000..ab71d64d4 --- /dev/null +++ b/src/Jackett.Common/Utils/MagnetUtil.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Specialized; +using System.Linq; +using System.Text; +using Jackett.Common.Helpers; + +namespace Jackett.Common.Utils +{ + public static class MagnetUtil + { + // Update best trackers from https://github.com/ngosang/trackerslist + private static readonly NameValueCollection _Trackers = new NameValueCollection + { + {"tr", "udp://tracker.opentrackr.org:1337/announce"}, + {"tr", "udp://tracker.coppersurfer.tk:6969/announce"}, + {"tr", "udp://9.rarbg.to:2710/announce"}, + {"tr", "udp://tracker.internetwarriors.net:1337/announce"}, + {"tr", "udp://tracker.cyberia.is:6969/announce"}, + {"tr", "udp://exodus.desync.com:6969/announce"}, + {"tr", "udp://explodie.org:6969/announce"}, + {"tr", "http://tracker1.itzmx.com:8080/announce"}, + {"tr", "udp://tracker.tiny-vps.com:6969/announce"}, + {"tr", "udp://open.stealth.si:80/announce"} + }; + + private static readonly string _TrackersEncoded = _Trackers.GetQueryString(); + + public static Uri InfoHashToPublicMagnet(string infoHash, string title) + { + if (string.IsNullOrWhiteSpace(infoHash) || string.IsNullOrWhiteSpace(title)) + return null; + return new Uri($"magnet:?xt=urn:btih:{infoHash}&dn={WebUtilityHelpers.UrlEncode(title, Encoding.UTF8)}&{_TrackersEncoded}"); + } + + public static string MagnetToInfoHash(Uri magnet) + { + try + { + var xt = ParseUtil.GetArgumentFromQueryString(magnet.ToString(), "xt"); + return xt.Split(':').Last(); // remove prefix urn:btih: + } + catch (Exception) + { + return null; + } + } + } +} diff --git a/src/Jackett.Test/Common/Indexers/BaseWebIndexerTests.cs b/src/Jackett.Test/Common/Indexers/BaseWebIndexerTests.cs index ea30a44cf..11338e150 100644 --- a/src/Jackett.Test/Common/Indexers/BaseWebIndexerTests.cs +++ b/src/Jackett.Test/Common/Indexers/BaseWebIndexerTests.cs @@ -124,7 +124,7 @@ namespace Jackett.Test.Common.Indexers } [Test] - public void TestFixResults() + public void TestFixResultsOriginPublishDate() { var indexer = new TestWebIndexer(); var query = new TorznabQuery(); @@ -144,6 +144,44 @@ namespace Jackett.Test.Common.Indexers Assert.AreEqual(DateTime.Now.Year, fixedResults.First().PublishDate.Year); } + [Test] + public void TestFixResultsMagnet() + { + var indexer = new TestWebIndexer(); + var query = new TorznabQuery(); + + // get info_hash from magnet + var results = new List + { + new ReleaseInfo + { + MagnetUri = new Uri("magnet:?xt=urn:btih:3333333333333333333333333333333333333333&dn=Title&tr=udp%3A%2F%2Ftracker.com%3A6969") + } + }; + Assert.AreEqual(null, results.First().InfoHash); + var fixedResults = indexer._FixResults(query, results).ToList(); + Assert.AreEqual("3333333333333333333333333333333333333333", fixedResults.First().InfoHash); + + // build magnet from info_hash (private site), not allowed + results = new List + { + new ReleaseInfo + { + Title = "Tracker Title", + InfoHash = "3333333333333333333333333333333333333333" + } + }; + Assert.AreEqual(null, results.First().MagnetUri); + fixedResults = indexer._FixResults(query, results).ToList(); + Assert.AreEqual(null, fixedResults.First().MagnetUri); + + // build magnet from info_hash (public, semi-private sites) + indexer.SetType("public"); + Assert.AreEqual(null, results.First().MagnetUri); + fixedResults = indexer._FixResults(query, results).ToList(); + Assert.True(fixedResults.First().MagnetUri.ToString().Contains("3333333333333333333333333333333333333333")); + } + [Test] public void TestAddCategoryMapping() { diff --git a/src/Jackett.Test/Common/Utils/MagnetUtilTests.cs b/src/Jackett.Test/Common/Utils/MagnetUtilTests.cs new file mode 100644 index 000000000..04e9c8eca --- /dev/null +++ b/src/Jackett.Test/Common/Utils/MagnetUtilTests.cs @@ -0,0 +1,45 @@ +using System; +using Jackett.Common.Utils; +using NUnit.Framework; +using Assert = NUnit.Framework.Assert; + +namespace Jackett.Test.Common.Utils +{ + [TestFixture] + public class MagnetUtilTests + { + [Test] + public void TestInfoHashToPublicMagnet() + { + const string infoHash = "3333333333333333333333333333333333333333"; + const string title = "Torrent Title 😀"; // with unicode characters + + // TODO: I'm not sure if unicode characters must be encoded + // good magnet + var magnet = MagnetUtil.InfoHashToPublicMagnet(infoHash, title); + Assert.True(magnet.ToString().StartsWith("magnet:?xt=urn:btih:3333333333333333333333333333333333333333&dn=Torrent+Title+😀&tr=")); + + // bad magnet (no info hash) + magnet = MagnetUtil.InfoHashToPublicMagnet("", title); + Assert.AreEqual(null, magnet); + + // bad magnet (no title) + magnet = MagnetUtil.InfoHashToPublicMagnet(infoHash, ""); + Assert.AreEqual(null, magnet); + } + + [Test] + public void TestMagnetToInfoHash() + { + // good magnet + var magnet = new Uri("magnet:?xt=urn:btih:3333333333333333333333333333333333333333&dn=Torrent+Title+😀&tr=udp%3A%2F%2Ftracker.com%3A6969%2Fannounce"); + var infoHash = MagnetUtil.MagnetToInfoHash(magnet); + Assert.AreEqual("3333333333333333333333333333333333333333", infoHash); + + // bad magnet + magnet = new Uri("magnet:?tr=udp%3A%2F%2Ftracker.com%3A6969%2Fannounce"); + infoHash = MagnetUtil.MagnetToInfoHash(magnet); + Assert.AreEqual(null, infoHash); + } + } +} diff --git a/src/Jackett.Test/TestHelpers/TestWebIndexer.cs b/src/Jackett.Test/TestHelpers/TestWebIndexer.cs index c8f488595..1a24f3197 100644 --- a/src/Jackett.Test/TestHelpers/TestWebIndexer.cs +++ b/src/Jackett.Test/TestHelpers/TestWebIndexer.cs @@ -44,6 +44,7 @@ namespace Jackett.Test.TestHelpers //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // public methods to test private methods + public void SetType(string type) => Type = type; public IEnumerable _FilterResults(TorznabQuery query, IEnumerable results) => FilterResults(query, results);