diff --git a/src/Jackett.Common/Helpers/Fluent.cs b/src/Jackett.Common/Helpers/Fluent.cs new file mode 100644 index 000000000..34cfa3212 --- /dev/null +++ b/src/Jackett.Common/Helpers/Fluent.cs @@ -0,0 +1,32 @@ +using System; + +namespace Jackett.Common.Helpers +{ + public static class Fluent + { + public static long Megabytes(this int megabytes) + { + return Convert.ToInt64(megabytes * 1024L * 1024L); + } + + public static long Gigabytes(this int gigabytes) + { + return Convert.ToInt64(gigabytes * 1024L * 1024L * 1024L); + } + + public static long Megabytes(this double megabytes) + { + return Convert.ToInt64(megabytes * 1024L * 1024L); + } + + public static long Gigabytes(this double gigabytes) + { + return Convert.ToInt64(gigabytes * 1024L * 1024L * 1024L); + } + + public static long Round(this long number, long level) + { + return Convert.ToInt64(Math.Floor((decimal)number / level) * level); + } + } +} diff --git a/src/Jackett.Common/Indexers/SubsPlease.cs b/src/Jackett.Common/Indexers/SubsPlease.cs index 7350f7e3e..fef048d75 100644 --- a/src/Jackett.Common/Indexers/SubsPlease.cs +++ b/src/Jackett.Common/Indexers/SubsPlease.cs @@ -6,6 +6,8 @@ using System.Linq; using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Jackett.Common.Extensions; +using Jackett.Common.Helpers; using Jackett.Common.Models; using Jackett.Common.Models.IndexerConfig; using Jackett.Common.Services.Interfaces; @@ -49,6 +51,8 @@ namespace Jackett.Common.Indexers private string ApiEndpoint => SiteLink + "api/?"; + private static readonly Regex _RegexSize = new Regex(@"\&xl=(?\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + public SubsPlease(IIndexerConfigurationService configService, Utils.Clients.WebClient wc, Logger l, IProtectionService ps, ICacheService cs) : base(configService: configService, client: wc, @@ -99,12 +103,14 @@ namespace Jackett.Common.Indexers private async Task> PerformSearch(TorznabQuery query) { // If the search terms contain [SubsPlease] or SubsPlease, remove them from the query sent to the API - string searchTerm = Regex.Replace(query.SearchTerm, "\\[?SubsPlease\\]?\\s*", string.Empty, RegexOptions.IgnoreCase).Trim(); + var searchTerm = Regex.Replace(query.SearchTerm, "\\[?SubsPlease\\]?\\s*", string.Empty, RegexOptions.IgnoreCase).Trim(); // If the search terms contain a resolution, remove it from the query sent to the API - Match resMatch = Regex.Match(searchTerm, "\\d{3,4}[p|P]"); + var resMatch = Regex.Match(searchTerm, "\\d{3,4}[p|P]"); if (resMatch.Success) + { searchTerm = searchTerm.Replace(resMatch.Value, string.Empty); + } var queryParameters = new NameValueCollection { @@ -114,14 +120,18 @@ namespace Jackett.Common.Indexers }; var response = await RequestWithCookiesAndRetryAsync(ApiEndpoint + queryParameters.GetQueryString()); if (response.Status != HttpStatusCode.OK) + { throw new WebException($"SubsPlease search returned unexpected result. Expected 200 OK but got {response.Status}.", WebExceptionStatus.ProtocolError); + } var results = ParseApiResults(response.ContentString); var filteredResults = results.Where(release => query.MatchQueryStringAND(release.Title)); // If we detected a resolution in the search terms earlier, filter by it if (resMatch.Success) + { filteredResults = filteredResults.Where(release => release.Title.IndexOf(resMatch.Value, StringComparison.OrdinalIgnoreCase) >= 0); + } return filteredResults; } @@ -135,7 +145,9 @@ namespace Jackett.Common.Indexers }; var response = await RequestWithCookiesAndRetryAsync(ApiEndpoint + queryParameters.GetQueryString()); if (response.Status != HttpStatusCode.OK) + { throw new WebException($"SubsPlease search returned unexpected result. Expected 200 OK but got {response.Status}.", WebExceptionStatus.ProtocolError); + } return ParseApiResults(response.ContentString); } @@ -150,7 +162,7 @@ namespace Jackett.Common.Indexers return releaseInfo; } - var releases = JsonConvert.DeserializeObject>(json); + var releases = JsonConvert.DeserializeObject>(json); foreach (var keyValue in releases) { @@ -159,7 +171,7 @@ namespace Jackett.Common.Indexers var baseRelease = new ReleaseInfo { Details = new Uri(SiteLink + $"shows/{r.Page}/"), - PublishDate = r.Release_Date.DateTime, + PublishDate = r.ReleaseDate.DateTime, Files = 1, Category = new List { TorznabCatType.TVAnime.ID }, Seeders = 1, @@ -179,37 +191,11 @@ namespace Jackett.Common.Indexers { var release = (ReleaseInfo)baseRelease.Clone(); // Ex: [SubsPlease] Shingeki no Kyojin (The Final Season) - 64 (1080p) - release.Title += $"[SubsPlease] {r.Show} - {r.Episode} ({d.Res}p)"; + release.Title += $"[SubsPlease] {r.Show} - {r.Episode} ({d.Resolution}p)"; release.MagnetUri = new Uri(d.Magnet); release.Link = null; release.Guid = new Uri(d.Magnet); - - var sizeMatch = Regex.Match(d.Magnet, "&xl=\\d+"); - - if (sizeMatch.Success) - { - release.Size = ParseUtil.CoerceLong(sizeMatch.Value.Replace("&xl=", string.Empty)); - } - else - { - // The API doesn't tell us file size, so give an estimate based on resolution - if (string.Equals(d.Res, "1080")) - { - release.Size = 1395864371; // 1.3GB - } - else if (string.Equals(d.Res, "720")) - { - release.Size = 734003200; // 700MB - } - else if (string.Equals(d.Res, "480")) - { - release.Size = 367001600; // 350MB - } - else - { - release.Size = 1073741824; // 1GB - } - } + release.Size = GetReleaseSize(d); releaseInfo.Add(release); } @@ -218,21 +204,48 @@ namespace Jackett.Common.Indexers return releaseInfo; } - public class Release + private static long GetReleaseSize(SubsPleaseDownloadInfo info) + { + if (info.Magnet.IsNotNullOrWhiteSpace()) + { + var sizeMatch = _RegexSize.Match(info.Magnet); + + if (sizeMatch.Success && + long.TryParse(sizeMatch.Groups["size"].Value, out var releaseSize) + && releaseSize > 0) + { + return releaseSize; + } + } + + // The API doesn't tell us file size, so give an estimate based on resolution + return info.Resolution switch + { + "1080" => 1.3.Gigabytes(), + "720" => 700.Megabytes(), + "480" => 350.Megabytes(), + _ => 1.Gigabytes() + }; + } + + public class SubsPleaseRelease { public string Time { get; set; } - public DateTimeOffset Release_Date { get; set; } + + [JsonProperty("release_date")] + public DateTimeOffset ReleaseDate { get; set; } public string Show { get; set; } public string Episode { get; set; } - public DownloadInfo[] Downloads { get; set; } + public SubsPleaseDownloadInfo[] Downloads { get; set; } public string Xdcc { get; set; } public string ImageUrl { get; set; } public string Page { get; set; } } - public class DownloadInfo + public class SubsPleaseDownloadInfo { - public string Res { get; set; } + [JsonProperty("res")] + public string Resolution { get; set; } public string Magnet { get; set; } } }