diff --git a/src/Jackett.Common/Definitions/awesomehd.yml b/src/Jackett.Common/Definitions/awesomehd.yml deleted file mode 100644 index f615f1008..000000000 --- a/src/Jackett.Common/Definitions/awesomehd.yml +++ /dev/null @@ -1,101 +0,0 @@ ---- - site: awesomehd - name: Awesome-HD - description: "An HD tracker" - language: en-us - type: private - encoding: UTF-8 - links: - - https://awesome-hd.me/ - - caps: - categorymappings: - - {id: 1, cat: Movies/HD, desc: "Movies"} - - {id: 2, cat: TV/HD, desc: "TV-Shows"} - - modes: - search: [q] - tv-search: [q, season, ep] - movie-search: [q] - - settings: - - name: username - type: text - label: Username - - name: password - type: password - label: Password - - name: sort - type: select - label: Sort requested from site - default: "time" - options: - "time": "created" - "seeders": "seeders" - "size": "size" - - name: type - type: select - label: Order requested from site - default: "desc" - options: - "desc": "desc" - "asc": "asc" - - name: info_login - type: info - label: "Password Changes" - default: "This site forces you to change your Password every 90 days.
If you get a Login Failed, got redirected error, then access the site with your browser and check if you need to change your password. Logout after saving, and update this config to login." - - login: - path: login.php - method: form - form: form#loginform - inputs: - username: "{{ .Config.username }}" - password: "{{ .Config.password }}" - keeplogged: 1 - error: - - selector: form#loginform .warning - test: - path: torrents.php - - search: - paths: - - path: torrents.php - inputs: - $raw: "{{ range .Categories }}filter_cat[{{.}}]=1&{{end}}" - searchstr: "{{ .Keywords }}" - page: torrents - order_by: "{{ .Config.sort }}" - order_way: "{{ .Config.type }}" - - rows: - selector: table#torrent_table > tbody > tr.group, tr.torrent, tr.group_torrent:not(.edition_info) - - fields: - download: - selector: a[href^="torrents.php?action=download&id="] - attribute: href - optional: true - details: - selector: a[href^="torrents.php?id="] - attribute: href - title: - selector: td:nth-child(3) > a - category: - selector: td:nth-child(2) - date: - selector: td:nth-last-child(5) - size: - selector: td:nth-last-child(4) - grabs: - selector: td:nth-last-child(3) - seeders: - selector: td:nth-last-child(2) - leechers: - selector: td:nth-last-child(1) - downloadvolumefactor: - case: - "*": 1 - uploadvolumefactor: - case: - "*": 1 diff --git a/src/Jackett.Common/Indexers/AwesomeHD.cs b/src/Jackett.Common/Indexers/AwesomeHD.cs new file mode 100644 index 000000000..d25457353 --- /dev/null +++ b/src/Jackett.Common/Indexers/AwesomeHD.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using Jackett.Common.Models; +using Jackett.Common.Models.IndexerConfig; +using Jackett.Common.Services.Interfaces; +using Jackett.Common.Utils; +using Newtonsoft.Json.Linq; +using NLog; + +namespace Jackett.Common.Indexers +{ + public class AwesomeHD : BaseWebIndexer + { + private string SearchUrl => SiteLink + "searchapi.php"; + private string TorrentUrl => SiteLink + "torrents.php"; + private new ConfigurationDataPasskey configData => (ConfigurationDataPasskey)base.configData; + + public AwesomeHD(IIndexerConfigurationService configService, Utils.Clients.WebClient c, Logger l, IProtectionService ps) + : base("Awesome-HD", + description: "An HD tracker", + link: "https://awesome-hd.me/", + caps: new TorznabCapabilities(), + configService: configService, + client: c, + logger: l, + p: ps, + configData: new ConfigurationDataPasskey("Note: You can find the Passkey in your profile, next to Personal information.")) + { + Encoding = Encoding.UTF8; + Language = "en-us"; + Type = "private"; + + TorznabCaps.SupportsImdbMovieSearch = true; + TorznabCaps.SupportsImdbTVSearch = true; + + AddCategoryMapping(1, TorznabCatType.MoviesHD); + AddCategoryMapping(2, TorznabCatType.TVHD); + } + + public override async Task ApplyConfiguration(JToken configJson) + { + LoadValuesFromJson(configJson); + + if (configData.Passkey.Value.Length != 32) + throw new Exception("Invalid Passkey configured. Expected length: 32"); + + var releases = await PerformQuery(new TorznabQuery()); + await ConfigureIfOK(string.Empty, releases.Any(), + () => throw new Exception("Could not find release from this URL.")); + + return IndexerConfigurationStatus.Completed; + } + + protected override async Task> PerformQuery(TorznabQuery query) + { + var releases = new List(); + var passkey = configData.Passkey.Value; + + var qc = new NameValueCollection + { + {"passkey", passkey} + }; + + if (!string.IsNullOrWhiteSpace(query.ImdbID)) + { + qc.Add("action", "imdbsearch"); + qc.Add("imdb", query.ImdbID); + } + else if (!string.IsNullOrWhiteSpace(query.GetQueryString())) + { + qc.Add("action", "titlesearch"); + qc.Add("title", query.SearchTerm); // not use query.GetQueryString(), see the season code below + } + else + { + qc.Add("action", "latestmovies"); + // the endpoint 'latestmovies' only returns movies, this hack overwrites categories to get movies even if + // you are searching for tv series. this allows to configure the tracker in Sonarr + query.Categories = new int[] {}; + } + + var searchUrl = SearchUrl + "?" + qc.GetQueryString(); + var results = await RequestStringWithCookies(searchUrl); + if (string.IsNullOrWhiteSpace(results.Content)) + throw new Exception("Empty response. Please, check the Passkey."); + + try + { + var doc = XDocument.Parse(results.Content); + + var errorMsg = doc.Descendants("error").FirstOrDefault()?.Value; + if (errorMsg?.Contains("No Results") == true) + return releases; // no results + if (errorMsg != null) + throw new Exception(errorMsg); + + var authkey = doc.Descendants("authkey").First().Value; + var torrents = doc.Descendants("torrent"); + foreach (var torrent in torrents) + { + var torrentName = torrent.FirstValue("name").Trim(); + + // the field is always Movie, so we have to guess if it's a tv series + var isSerie = torrentName.Contains(": Season "); + if (isSerie) + torrentName = torrentName.Replace(": Season ", " S"); + + // if the category is not in the search categories, skip + var cat = new List {isSerie ? TorznabCatType.TVHD.ID : TorznabCatType.MoviesHD.ID}; + if (query.Categories.Any() && !query.Categories.Intersect(cat).Any()) + continue; + + // if it's a tv series season search, skip movies and other seasons + if (query.Season > 0 && (!isSerie || !torrentName.EndsWith($" S{query.Season:D2}"))) + continue; + + var title = new StringBuilder(torrentName); + if (!isSerie && torrent.Element("year") != null) // only for movies + title.Append($" {torrent.FirstValue("year")}"); + if (torrent.Element("internal")?.Value == "1") + title.Append(" iNTERNAL"); + if (torrent.Element("resolution") != null) + title.Append($" {torrent.FirstValue("resolution")}"); + if (torrent.Element("media") != null) + title.Append($" {torrent.FirstValue("media")}"); + if (torrent.Element("encoding") != null) + title.Append($" {torrent.FirstValue("encoding")}"); + if (torrent.Element("audioformat") != null) + title.Append($" {torrent.FirstValue("audioformat")}"); + if (torrent.Element("releasegroup") != null) + title.Append($"-{torrent.FirstValue("releasegroup")}"); + + var torrentId = torrent.FirstValue("id"); + var groupId = torrent.FirstValue("groupid"); + var comments = new Uri($"{TorrentUrl}?id={groupId}&torrentid={torrentId}"); + var link = new Uri($"{TorrentUrl}?action=download&id={torrentId}&authkey={authkey}&torrent_pass={passkey}"); + + var publishDate = DateTime.Parse(torrent.FirstValue("time")); + var size = long.Parse(torrent.FirstValue("size")); + var grabs = int.Parse(torrent.FirstValue("snatched")); + var seeders = int.Parse(torrent.FirstValue("seeders")); + var peers = seeders + int.Parse(torrent.FirstValue("leechers")); + var freeleech = double.Parse(torrent.FirstValue("freeleech")); + + Uri banner = null; + // small cover only for movies + if (!isSerie && !string.IsNullOrWhiteSpace(torrent.Element("smallcover")?.Value)) + banner = new Uri(torrent.FirstValue("smallcover")); + else if (!string.IsNullOrWhiteSpace(torrent.Element("cover")?.Value)) + banner = new Uri(torrent.FirstValue("cover")); + + var description = torrent.Element("encodestatus") != null ? + $"Encode status: {torrent.FirstValue("encodestatus")}" : null; + + var imdb = ParseUtil.GetImdbID(torrent.Element("imdb")?.Value); + + var release = new ReleaseInfo + { + Title = title.ToString(), + Comments = comments, + Link = link, + Guid = link, + PublishDate = publishDate, + Category = cat, + BannerUrl = banner, + Description = description, + Imdb = imdb, + Size = size, + Grabs = grabs, + Seeders = seeders, + Peers = peers, + MinimumRatio = 1, + MinimumSeedTime = 259200, // 72 hours + DownloadVolumeFactor = freeleech, + UploadVolumeFactor = 1 + }; + + releases.Add(release); + } + } + catch (Exception ex) + { + OnParseError(results.Content, ex); + } + + return releases; + } + } +} diff --git a/src/Jackett.Updater/Program.cs b/src/Jackett.Updater/Program.cs index f0aa491a6..f305ef8a9 100644 --- a/src/Jackett.Updater/Program.cs +++ b/src/Jackett.Updater/Program.cs @@ -282,6 +282,7 @@ namespace Jackett.Updater "Definitions/archetorrent.yml", "Definitions/asiandvdclub.yml", "Definitions/avg.yml", + "Definitions/awesomehd.yml", // migrated to C# "Definitions/b2s-share.yml", "Definitions/bithq.yml", "Definitions/bitme.yml",