From d8a48b2e502f8bd1f6605c729a92d3a6d52b533c Mon Sep 17 00:00:00 2001
From: Mouton99 <66394380+Mouton99@users.noreply.github.com>
Date: Wed, 29 Dec 2021 20:33:23 -0500
Subject: [PATCH] TorrentSeeds now uses UNIT3D (#12752)
---
.../Definitions/torrentseeds-api.yml | 141 ++++++++
src/Jackett.Common/Indexers/TorrentSeeds.cs | 307 ------------------
2 files changed, 141 insertions(+), 307 deletions(-)
create mode 100644 src/Jackett.Common/Definitions/torrentseeds-api.yml
delete mode 100644 src/Jackett.Common/Indexers/TorrentSeeds.cs
diff --git a/src/Jackett.Common/Definitions/torrentseeds-api.yml b/src/Jackett.Common/Definitions/torrentseeds-api.yml
new file mode 100644
index 000000000..67184127a
--- /dev/null
+++ b/src/Jackett.Common/Definitions/torrentseeds-api.yml
@@ -0,0 +1,141 @@
+---
+id: torrentseeds-api
+name: TorrentSeeds (API)
+description: "TorrentSeeds is a Private Torrent Tracker for MOVIES / TV / MUSIC / GENERAL"
+language: en-US
+type: private
+encoding: UTF-8
+links:
+ - https://torrentseeds.org/
+
+caps:
+ # dont forget to update the case block in the search fields category
+ categorymappings:
+ - {id: 1, cat: PC, desc: "Apps"}
+ - {id: 5, cat: TV/Anime, desc: "Anime"}
+ - {id: 2, cat: PC/Games, desc: "Games"}
+ - {id: 3, cat: Movies, desc: "Movies"}
+ - {id: 4, cat: Audio, desc: "Music"}
+ - {id: 6, cat: XXX, desc: "Porn"}
+ - {id: 7, cat: TV/Sport, desc: "Sport"}
+ - {id: 3205, cat: TV, desc: "TV"}
+ - {id: 8, cat: TV, desc: "Packs"}
+ - {id: 3206, cat: TV/Foreign, desc: "TV/Foreign"}
+ - {id: 3207, cat: Movies/Foreign, desc: "Movies/Foreign"}
+
+ modes:
+ search: [q]
+ tv-search: [q, season, ep, imdbid, tvdbid]
+ movie-search: [q, imdbid, tmdbid]
+ music-search: [q]
+ book-search: [q]
+
+settings:
+ - name: apikey
+ type: text
+ label: APIKey
+ - name: info_key
+ type: info
+ label: About your API key
+ default: "Find or Generate a new API Token by accessing your TorrentSeeds account My Security page and clicking on the API Token tab."
+ - name: freeleech
+ type: checkbox
+ label: Search freeleech only
+ default: false
+ - name: sort
+ type: select
+ label: Sort requested from site
+ default: created_at
+ options:
+ created_at: created
+ seeders: seeders
+ size: size
+ name: title
+ - name: type
+ type: select
+ label: Order requested from site
+ default: desc
+ options:
+ desc: desc
+ asc: asc
+
+search:
+ paths:
+ # https://hdinnovations.github.io/UNIT3D-Community-Edition-Docs/api_endpoints.html
+ # https://github.com/HDInnovations/UNIT3D-Community-Edition/blob/master/app/Http/Controllers/API/TorrentController.php
+ - path: "/api/torrents/filter?api_token={{ .Config.apikey }}&name={{ if .Query.IMDBID }}{{ else }}{{ .Keywords }}{{ end }}{{ if .Query.TMDBID }}&tmdbId={{ .Query.TMDBID }}{{ else }}{{ end }}{{ if .Query.IMDBIDShort }}&imdbId={{ .Query.IMDBIDShort }}{{ else }}{{ end }}{{ if .Query.TVDBID }}&tvdbId={{ .Query.TVDBID }}{{ else }}{{ end }}&sortField={{ .Config.sort }}&sortDirection={{ .Config.type }}&perPage=100&page=1{{ range .Categories }}&categories[]={{.}}{{end}}{{ if .Config.freeleech }}&free=1{{ else }}{{ end }}"
+ response:
+ type: json
+ attribute: attributes
+
+ rows:
+ selector: data
+ count:
+ selector: meta.total
+
+ fields:
+ category:
+ selector: category
+ case:
+ "Apps": 1
+ "Games": 2
+ "Movies": 3
+ "Music": 4
+ "Anime": 5
+ "Porn": 6
+ "Sport": 7
+ "Packs": 8
+ "TV": 3205
+ "TV/Foreign": 3206
+ "Movies/Foreign": 3207
+ title:
+ selector: name
+ details:
+ selector: details_link
+ download:
+ selector: download_link
+ poster:
+ selector: poster
+ filters:
+ - name: replace
+ args: ["https://via.placeholder.com/90x135", ""]
+ - name: replace
+ args: ["https://via.placeholder.com/400x600", ""]
+ imdbid:
+ selector: imdb_id
+ tmdbid:
+ selector: tmdb_id
+ tvdbid:
+ selector: tvdb_id
+ files:
+ selector: num_file
+ seeders:
+ selector: seeders
+ leechers:
+ selector: leechers
+ grabs:
+ selector: times_completed
+ date:
+ # 2021-10-18T00:34:50.000000Z"
+ selector: created_at
+ size:
+ selector: size
+ downloadvolumefactor:
+ # api returns 0=false, 1=true
+ selector: freeleech
+ case:
+ 0: 1 # not free
+ 1: 0 # freeleech
+ uploadvolumefactor:
+ # api returns 0=false, 1=true
+ selector: double_upload
+ case:
+ 0: 1 # normal
+ 1: 2 # double
+# global MR is 0.5 but torrents must be seeded for 3 days regardless of ratio
+# minimumratio:
+# text: 0.5
+ minimumseedtime:
+ # 120 hours (as seconds = 120 x 60 x 60)
+ text: 432000
+# json UNIT3D 5.3.0
diff --git a/src/Jackett.Common/Indexers/TorrentSeeds.cs b/src/Jackett.Common/Indexers/TorrentSeeds.cs
deleted file mode 100644
index c3982f2a8..000000000
--- a/src/Jackett.Common/Indexers/TorrentSeeds.cs
+++ /dev/null
@@ -1,307 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Net;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-using AngleSharp.Html.Parser;
-using Jackett.Common.Models;
-using Jackett.Common.Models.IndexerConfig;
-using Jackett.Common.Services.Interfaces;
-using Jackett.Common.Utils;
-using Jackett.Common.Utils.Clients;
-using Newtonsoft.Json.Linq;
-using NLog;
-
-namespace Jackett.Common.Indexers
-{
- [ExcludeFromCodeCoverage]
- public class TorrentSeeds : BaseWebIndexer
- {
- private string LoginUrl => SiteLink + "takelogin.php";
- private string CaptchaUrl => SiteLink + "simpleCaptcha.php?numImages=1";
- private string SearchUrl => SiteLink + "browse_elastic.php";
-
- private new ConfigurationDataBasicLoginWithRSSAndDisplay configData => (ConfigurationDataBasicLoginWithRSSAndDisplay)base.configData;
-
- public TorrentSeeds(IIndexerConfigurationService configService, Utils.Clients.WebClient wc, Logger l,
- IProtectionService ps, ICacheService cs)
- : base(id: "torrentseeds",
- name: "TorrentSeeds",
- description: "TorrentSeeds is a Private site for MOVIES / TV / GENERAL",
- link: "https://torrentseeds.org/",
- caps: new TorznabCapabilities
- {
- TvSearchParams = new List
- {
- TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
- },
- MovieSearchParams = new List
- {
- MovieSearchParam.Q
- },
- MusicSearchParams = new List
- {
- MusicSearchParam.Q
- },
- BookSearchParams = new List
- {
- BookSearchParam.Q
- }
- },
- configService: configService,
- client: wc,
- logger: l,
- p: ps,
- cacheService: cs,
- configData: new ConfigurationDataBasicLoginWithRSSAndDisplay("For best results, change the Torrents per page: setting to 100 on your account profile."))
- {
- Encoding = Encoding.UTF8;
- Language = "en-US";
- Type = "private";
-
- // NOTE: Tracker Category Description must match Type/Category in details page!
- AddCategoryMapping(37, TorznabCatType.TVAnime, "Anime/HD");
- AddCategoryMapping(9, TorznabCatType.TVAnime, "Anime/SD");
- AddCategoryMapping(72, TorznabCatType.TVAnime, "Anime/UHD");
- AddCategoryMapping(13, TorznabCatType.PC0day, "Apps/0DAY");
- AddCategoryMapping(27, TorznabCatType.Books, "Apps/Bookware");
- AddCategoryMapping(1, TorznabCatType.PCISO, "Apps/ISO");
- AddCategoryMapping(73, TorznabCatType.AudioAudiobook, "Music/Audiobooks");
- AddCategoryMapping(47, TorznabCatType.ConsoleOther, "Console/NSW");
- AddCategoryMapping(8, TorznabCatType.ConsolePS3, "Console/PS3");
- AddCategoryMapping(30, TorznabCatType.ConsolePS4, "Console/PS4");
- AddCategoryMapping(71, TorznabCatType.ConsolePS4, "Console/PS5");
- AddCategoryMapping(7, TorznabCatType.ConsolePSP, "Console/PSP");
- AddCategoryMapping(70, TorznabCatType.ConsolePSVita, "Console/PSV");
- AddCategoryMapping(16, TorznabCatType.ConsoleWii, "Console/WII");
- AddCategoryMapping(29, TorznabCatType.ConsoleWiiU, "Console/WIIU");
- AddCategoryMapping(17, TorznabCatType.ConsoleXBox360, "Console/XBOX360");
- AddCategoryMapping(32, TorznabCatType.BooksEBook, "E-books");
- AddCategoryMapping(63, TorznabCatType.ConsoleOther, "Games/DOX");
- AddCategoryMapping(2, TorznabCatType.PCGames, "Games/ISO");
- AddCategoryMapping(12, TorznabCatType.PCGames, "Games/PC Rips");
- AddCategoryMapping(31, TorznabCatType.MoviesBluRay, "Movies/Bluray");
- AddCategoryMapping(50, TorznabCatType.MoviesBluRay, "Movies/Bluray-UHD");
- AddCategoryMapping(3, TorznabCatType.MoviesDVD, "Movies/DVDR");
- AddCategoryMapping(69, TorznabCatType.MoviesForeign, "Movies/DVDR-Foreign");
- AddCategoryMapping(19, TorznabCatType.MoviesHD, "Movies/HD");
- AddCategoryMapping(39, TorznabCatType.MoviesForeign, "Movies/HD-Foreign");
- AddCategoryMapping(74, TorznabCatType.MoviesHD, "Movies/Remuxes");
- AddCategoryMapping(25, TorznabCatType.MoviesSD, "Movies/SD");
- AddCategoryMapping(62, TorznabCatType.MoviesForeign, "Movies/SD-Foreign");
- AddCategoryMapping(49, TorznabCatType.MoviesUHD, "Movies/UHD");
- AddCategoryMapping(76, TorznabCatType.MoviesForeign, "Movies/UHD-Foreign");
- AddCategoryMapping(33, TorznabCatType.AudioLossless, "Music/FLAC");
- AddCategoryMapping(89, TorznabCatType.AudioVideo, "Music/MBluRay");
- AddCategoryMapping(28, TorznabCatType.AudioVideo, "Music/MBluRay-Rips");
- AddCategoryMapping(34, TorznabCatType.AudioVideo, "Music/MDVDR");
- AddCategoryMapping(4, TorznabCatType.AudioMP3, "Music/MP3");
- AddCategoryMapping(20, TorznabCatType.AudioVideo, "Music/MVID");
- AddCategoryMapping(77, TorznabCatType.TVAnime, "Anime/Packs");
- AddCategoryMapping(78, TorznabCatType.BooksEBook, "Books/Packs");
- AddCategoryMapping(80, TorznabCatType.MoviesHD, "Movies/HD-Packs");
- AddCategoryMapping(81, TorznabCatType.MoviesHD, "Movies/Remux-Packs");
- AddCategoryMapping(79, TorznabCatType.MoviesSD, "Movies/SD-Packs");
- AddCategoryMapping(68, TorznabCatType.Audio, "Music/Packs");
- AddCategoryMapping(67, TorznabCatType.TVHD, "TV/HD-Packs");
- AddCategoryMapping(82, TorznabCatType.TVHD, "TV/Remux-Packs");
- AddCategoryMapping(65, TorznabCatType.TVSD, "TV/SD-Packs");
- AddCategoryMapping(84, TorznabCatType.TVUHD, "TV/UHD-Packs");
- AddCategoryMapping(85, TorznabCatType.XXX, "XXX/Packs");
- AddCategoryMapping(23, TorznabCatType.TVSD, "TV/DVDR");
- AddCategoryMapping(26, TorznabCatType.TVHD, "TV/HD");
- AddCategoryMapping(64, TorznabCatType.TVForeign, "TV/HD-Foreign");
- AddCategoryMapping(11, TorznabCatType.TVHD, "TV/HD-Retail");
- AddCategoryMapping(36, TorznabCatType.TVSport, "TV/HD-Sport");
- AddCategoryMapping(18, TorznabCatType.TVSD, "TV/SD");
- AddCategoryMapping(86, TorznabCatType.TVForeign, "TV/SD-Foreign");
- AddCategoryMapping(24, TorznabCatType.TVSD, "TV/SD-Retail");
- AddCategoryMapping(35, TorznabCatType.TVSport, "TV/SD-Sport");
- AddCategoryMapping(61, TorznabCatType.TVUHD, "TV/UHD");
- AddCategoryMapping(87, TorznabCatType.TVForeign, "TV/UHD-Foreign");
- AddCategoryMapping(53, TorznabCatType.XXX, "XXX/HD");
- AddCategoryMapping(88, TorznabCatType.XXXImageSet, "XXX/Image-Sets");
- AddCategoryMapping(57, TorznabCatType.XXX, "XXX/Paysite");
- AddCategoryMapping(6, TorznabCatType.XXXSD, "XXX/SD");
- }
-
- public override async Task ApplyConfiguration(JToken configJson)
- {
- LoadValuesFromJson(configJson);
- CookieHeader = ""; // clear old cookies
-
- var result1 = await RequestWithCookiesAsync(CaptchaUrl);
- var json1 = JObject.Parse(result1.ContentString);
- var captchaSelection = json1["images"][0]["hash"];
-
- var pairs = new Dictionary {
- { "username", configData.Username.Value },
- { "password", configData.Password.Value },
- { "submitme", "X" },
- { "captchaSelection", (string)captchaSelection }
- };
-
- var result2 = await RequestLoginAndFollowRedirect(LoginUrl, pairs, result1.Cookies, true, null, null, true);
-
- await ConfigureIfOK(result2.Cookies, result2.ContentString.Contains("logout.php"), () =>
- throw new ExceptionWithConfigData("Login Failed", configData));
- return IndexerConfigurationStatus.RequiresTesting;
-
- }
-
- protected override async Task> PerformQuery(TorznabQuery query)
- {
- // remove operator characters
- var cleanSearchString = Regex.Replace(query.GetQueryString().Trim(), "[ _.+-]+", " ", RegexOptions.Compiled);
-
- var searchUrl = SearchUrl;
- var queryCollection = new NameValueCollection
- {
- { "search_in", "name" },
- { "search_mode", "all" },
- { "order_by", "added" },
- { "order_way", "desc" }
- };
- if (!string.IsNullOrWhiteSpace(cleanSearchString))
- queryCollection.Add("query", cleanSearchString);
- foreach (var cat in MapTorznabCapsToTrackers(query))
- queryCollection.Add($"cat[{cat}]", "1");
-
- searchUrl += "?" + queryCollection.GetQueryString();
- var response = await RequestWithCookiesAndRetryAsync(searchUrl);
-
- // handle cookie expiration
- var results = response.ContentString;
- if ((response.IsRedirect && response.RedirectingTo.Contains("/login.php?")) ||
- (!response.IsRedirect && !results.Contains("/logout.php?")))
- {
- await ApplyConfiguration(null); // re-login
- response = await RequestWithCookiesAndRetryAsync(searchUrl);
- }
-
- // handle single entries
- if (response.IsRedirect)
- {
- var detailsLink = new Uri(response.RedirectingTo);
- await FollowIfRedirect(response, accumulateCookies: true);
- return ParseSingleResult(response, detailsLink);
- }
-
- return ParseMultiResult(response);
- }
-
- private List ParseSingleResult(WebResult response, Uri detailsLink)
- {
- var releases = new List();
- var results = response.ContentString;
-
- try
- {
- var parser = new HtmlParser();
- var dom = parser.ParseDocument(results);
- var content = dom.QuerySelector("tbody:has(script)");
- if (content == null)
- return releases; // no results
- var release = new ReleaseInfo();
- release.MinimumRatio = 1;
- release.MinimumSeedTime = 72 * 60 * 60;
- var catStr = content.QuerySelector("tr:has(td.heading:contains(\"Type\"))").Children[1].TextContent;
- release.Category = MapTrackerCatDescToNewznab(catStr);
- var qLink = content.QuerySelector("tr:has(td.heading:contains(\"Download\"))")
- .QuerySelector("a[href*=\"download.php?torrent=\"]");
- release.Link = new Uri(SiteLink + qLink.GetAttribute("href"));
- release.Title = dom.QuerySelector("h1").TextContent.Trim();
- release.Details = detailsLink;
- release.Guid = detailsLink;
- var qSize = content.QuerySelector("tr:has(td.heading:contains(\"Size\"))").Children[1].TextContent
- .Split('(')[0].Trim();
- release.Size = ReleaseInfo.GetBytes(qSize);
- var peerStats = content.QuerySelector("tr:has(td:has(a[href^=\"./peerlist_xbt.php?id=\"]))").Children[1]
- .TextContent.Split(',');
- var qSeeders = peerStats[0].Replace(" seeder(s)", "").Trim();
- var qLeechers = peerStats[1].Split('=')[0].Replace(" leecher(s) ", "").Trim();
- release.Seeders = ParseUtil.CoerceInt(qSeeders);
- release.Peers = ParseUtil.CoerceInt(qLeechers) + release.Seeders;
- var rawDateStr = content.QuerySelector("tr:has(td.heading:contains(\"Added\"))").Children[1].TextContent;
- var dateUpped = DateTimeUtil.FromUnknown(rawDateStr.Replace(",", string.Empty));
-
- // Mar 4 2020, 05:47 AM
- release.PublishDate = dateUpped.ToLocalTime();
- var qGrabs = content.QuerySelector("tr:has(td.heading:contains(\"Snatched\"))").Children[1];
- release.Grabs = ParseUtil.CoerceInt(qGrabs.TextContent.Replace(" time(s)", ""));
- var qFiles = content.QuerySelector("tr:has(td.heading:has(a[href^=\"./filelist.php?id=\"]))").Children[1];
- release.Files = ParseUtil.CoerceInt(qFiles.TextContent.Replace(" files", ""));
- var qRatio = content.QuerySelector("tr:has(td.heading:contains(\"Ratio After Download\"))").Children[1];
- release.DownloadVolumeFactor = qRatio.QuerySelector("del") != null ? 0 : 1;
- release.UploadVolumeFactor = 1;
- releases.Add(release);
- }
- catch (Exception ex)
- {
- OnParseError(results, ex);
- }
-
- return releases;
- }
-
- private List ParseMultiResult(WebResult response)
- {
- var releases = new List();
- var results = response.ContentString;
-
- try
- {
- var parser = new HtmlParser();
- var dom = parser.ParseDocument(results);
- var rows = dom.QuerySelectorAll("table.table-bordered > tbody > tr[class*=\"torrent_row_\"]");
- foreach (var row in rows)
- {
- var release = new ReleaseInfo();
- release.MinimumRatio = 1;
- release.MinimumSeedTime = 72 * 60 * 60;
- var qCatLink = row.QuerySelector("a[href^=\"/browse_elastic.php?cat=\"]");
- var catStr = qCatLink.GetAttribute("href").Split('=')[1];
- release.Category = MapTrackerCatToNewznab(catStr);
- var qDetailsLink = row.QuerySelector("a[href^=\"/details.php?id=\"]");
- var qDetailsTitle = row.QuerySelector("td:has(a[href^=\"/details.php?id=\"]) b");
- release.Title = qDetailsTitle.TextContent.Trim();
- var qDlLink = row.QuerySelector("a[href^=\"/download.php?torrent=\"]");
-
- release.Link = new Uri(SiteLink + qDlLink.GetAttribute("href").TrimStart('/'));
- release.Details = new Uri(SiteLink + qDetailsLink.GetAttribute("href").TrimStart('/'));
- release.Guid = release.Details;
-
- var qColumns = row.QuerySelectorAll("td");
- release.Files = ParseUtil.CoerceInt(qColumns[3].TextContent);
- release.PublishDate = DateTimeUtil.FromUnknown(qColumns[5].TextContent);
- release.Size = ReleaseInfo.GetBytes(qColumns[6].TextContent);
- release.Grabs = ParseUtil.CoerceInt(qColumns[7].TextContent.Replace("Times", ""));
- release.Seeders = ParseUtil.CoerceInt(qColumns[8].TextContent);
- release.Peers = ParseUtil.CoerceInt(qColumns[9].TextContent) + release.Seeders;
-
- var qImdb = row.QuerySelector("a[href*=\"www.imdb.com\"]");
- if (qImdb != null)
- {
- var deRefUrl = qImdb.GetAttribute("href");
- release.Imdb = ParseUtil.GetImdbID(WebUtility.UrlDecode(deRefUrl).Split('/').Last());
- }
-
- release.DownloadVolumeFactor = row.QuerySelector("span.freeleech") != null ? 0 : 1;
- release.UploadVolumeFactor = 1;
- releases.Add(release);
- }
- }
- catch (Exception ex)
- {
- OnParseError(results, ex);
- }
-
- return releases;
- }
-
- }
-}