Compare commits

...

9 Commits

Author SHA1 Message Date
Bogdan
45d8a8a4e6 Minor fixes and cover link for SubsPlease 2024-07-06 22:12:36 +03:00
Bogdan
a4546c77ce Avoid invalid requests for Nebulance 2024-07-06 11:33:21 +03:00
Bogdan
d69bf6360a Fixed: (Nebulance) Improve searching by release names 2024-07-06 09:21:55 +03:00
Bogdan
da9ce5b5c3 New: Enable "Sync Anime Standard Format Search" by default for new Sonarr apps 2024-07-05 22:26:34 +03:00
Bogdan
e092098101 Minor improvements to season parsing from titles for AnimeBytes 2024-07-05 16:39:04 +03:00
Bogdan
1a89a79b74 Avoid NullRef for missing filelist and tags fields 2024-07-05 16:13:32 +03:00
Bogdan
cb6bf49922 New: (Nebulance) Improvements for season and episode searching 2024-07-05 12:42:51 +03:00
Bogdan
4bcaba0be0 Fixed: Trimming disabled logs database
(cherry picked from commit d5dff8e8d6301b661a713702e1c476705423fc4f)
2024-07-01 05:41:37 +03:00
Bogdan
220ef723c7 Bump version to 1.20.1 2024-06-30 07:22:49 +03:00
9 changed files with 125 additions and 85 deletions

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '1.20.0'
majorVersion: '1.20.1'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'

View File

@@ -44,7 +44,7 @@ namespace NzbDrone.Core.Applications.Sonarr
public IEnumerable<int> AnimeSyncCategories { get; set; }
[FieldDefinition(5, Label = "Sync Anime Standard Format Search", Type = FieldType.Checkbox, HelpText = "Sync also searching for anime using the standard numbering", Advanced = true)]
public bool SyncAnimeStandardFormatSearch { get; set; }
public bool SyncAnimeStandardFormatSearch { get; set; } = true;
[FieldDefinition(6, Type = FieldType.Checkbox, Label = "ApplicationSettingsSyncRejectBlocklistedTorrentHashes", HelpText = "ApplicationSettingsSyncRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool SyncRejectBlocklistedTorrentHashesWhileGrabbing { get; set; }

View File

@@ -1,18 +1,26 @@
using NzbDrone.Core.Instrumentation;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Instrumentation;
namespace NzbDrone.Core.Housekeeping.Housekeepers
{
public class TrimLogDatabase : IHousekeepingTask
{
private readonly ILogRepository _logRepo;
private readonly IConfigFileProvider _configFileProvider;
public TrimLogDatabase(ILogRepository logRepo)
public TrimLogDatabase(ILogRepository logRepo, IConfigFileProvider configFileProvider)
{
_logRepo = logRepo;
_configFileProvider = configFileProvider;
}
public void Clean()
{
if (!_configFileProvider.LogDbEnabled)
{
return;
}
_logRepo.Trim();
}
}

View File

@@ -114,7 +114,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
string episodeString;
if (DateTime.TryParseExact($"{Season} {Episode}", "yyyy MM/dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var showDate))
{
episodeString = showDate.ToString("yyyy.MM.dd");
episodeString = showDate.ToString("yyyy.MM.dd", CultureInfo.InvariantCulture);
}
else if (Episode.IsNullOrWhiteSpace())
{

View File

@@ -644,16 +644,16 @@ namespace NzbDrone.Core.Indexers.Definitions
private static int? ParseSeasonFromTitles(IReadOnlyCollection<string> titles)
{
var advancedSeasonRegex = new Regex(@"(\d+)(st|nd|rd|th) Season", RegexOptions.Compiled | RegexOptions.IgnoreCase);
var advancedSeasonRegex = new Regex(@"\b(?:(?<season>\d+)(?:st|nd|rd|th) Season|Season (?<season>\d+))\b", RegexOptions.Compiled | RegexOptions.IgnoreCase);
var seasonCharactersRegex = new Regex(@"(I{2,})$", RegexOptions.Compiled);
var seasonNumberRegex = new Regex(@"\b(?:S)?([2-9])$", RegexOptions.Compiled);
var seasonNumberRegex = new Regex(@"\b(?<!Part[- ._])(?:S)?(?<season>[2-9])$", RegexOptions.Compiled);
foreach (var title in titles)
{
var advancedSeasonRegexMatch = advancedSeasonRegex.Match(title);
if (advancedSeasonRegexMatch.Success)
{
return ParseUtil.CoerceInt(advancedSeasonRegexMatch.Groups[1].Value);
return ParseUtil.CoerceInt(advancedSeasonRegexMatch.Groups["season"].Value);
}
var seasonCharactersRegexMatch = seasonCharactersRegex.Match(title);
@@ -665,7 +665,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var seasonNumberRegexMatch = seasonNumberRegex.Match(title);
if (seasonNumberRegexMatch.Success)
{
return ParseUtil.CoerceInt(seasonNumberRegexMatch.Groups[1].Value);
return ParseUtil.CoerceInt(seasonNumberRegexMatch.Groups["season"].Value);
}
}

View File

@@ -74,7 +74,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
else if (DateTime.TryParseExact($"{searchCriteria.Season} {searchCriteria.Episode}", "yyyy MM/dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var showDate))
{
// Daily Episode
parameters.Name = showDate.ToString("yyyy.MM.dd");
parameters.Name = showDate.ToString("yyyy.MM.dd", CultureInfo.InvariantCulture);
parameters.Category = "Episode";
pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset));
}

View File

@@ -66,7 +66,7 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
if (DateTime.TryParseExact($"{searchCriteria.Season} {searchCriteria.Episode}", "yyyy MM/dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var showDate))
{
query.Search = showDate.ToString("yyyy-MM-dd");
query.Search = showDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
}
else
{

View File

@@ -5,7 +5,6 @@ using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Newtonsoft.Json;
using NLog;
@@ -42,7 +41,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new NebulanceRequestGenerator(Settings);
return new NebulanceRequestGenerator(Settings, _logger);
}
public override IParseIndexerResponse GetParser()
@@ -68,26 +67,6 @@ namespace NzbDrone.Core.Indexers.Definitions
return Task.FromResult(request);
}
protected override IList<ReleaseInfo> CleanupReleases(IEnumerable<ReleaseInfo> releases, SearchCriteriaBase searchCriteria)
{
var cleanReleases = base.CleanupReleases(releases, searchCriteria);
return FilterReleasesByQuery(cleanReleases, searchCriteria).ToList();
}
protected override IEnumerable<ReleaseInfo> FilterReleasesByQuery(IEnumerable<ReleaseInfo> releases, SearchCriteriaBase searchCriteria)
{
if (!searchCriteria.IsRssSearch &&
searchCriteria.IsIdSearch &&
searchCriteria is TvSearchCriteria tvSearchCriteria &&
tvSearchCriteria.EpisodeSearchString.IsNotNullOrWhiteSpace())
{
releases = releases.Where(r => r.Title.IsNotNullOrWhiteSpace() && r.Title.ContainsIgnoreCase(tvSearchCriteria.EpisodeSearchString)).ToList();
}
return releases;
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
@@ -111,10 +90,12 @@ namespace NzbDrone.Core.Indexers.Definitions
public class NebulanceRequestGenerator : IIndexerRequestGenerator
{
private readonly NebulanceSettings _settings;
private readonly Logger _logger;
public NebulanceRequestGenerator(NebulanceSettings settings)
public NebulanceRequestGenerator(NebulanceSettings settings, Logger logger)
{
_settings = settings;
_logger = logger;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
@@ -136,40 +117,53 @@ namespace NzbDrone.Core.Indexers.Definitions
Age = ">0"
};
if (searchCriteria.SanitizedTvSearchString.IsNotNullOrWhiteSpace())
if (searchCriteria.TvMazeId is > 0)
{
queryParams.Name = "%" + Regex.Replace(searchCriteria.SanitizedTvSearchString, "[\\W]+", "%").Trim() + "%";
queryParams.TvMaze = searchCriteria.TvMazeId.Value;
}
else if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
{
queryParams.Imdb = searchCriteria.FullImdbId;
}
if (searchCriteria.TvMazeId.HasValue)
{
queryParams.Tvmaze = searchCriteria.TvMazeId.Value;
var searchQuery = searchCriteria.SanitizedSearchTerm.Trim();
if (searchCriteria.EpisodeSearchString.IsNotNullOrWhiteSpace())
if (searchQuery.IsNotNullOrWhiteSpace())
{
queryParams.Release = searchQuery;
}
if (DateTime.TryParseExact($"{searchCriteria.Season} {searchCriteria.Episode}", "yyyy MM/dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var showDate))
{
queryParams.Name = searchQuery;
queryParams.Release = showDate.ToString("yyyy.MM.dd", CultureInfo.InvariantCulture);
}
else
{
if (searchCriteria.Season.HasValue)
{
queryParams.Name = "%" + Regex.Replace(searchCriteria.EpisodeSearchString, "[\\W]+", "%").Trim() + "%";
queryParams.Season = searchCriteria.Season.Value;
}
if (searchCriteria.Episode.IsNotNullOrWhiteSpace() && int.TryParse(searchCriteria.Episode, out var episodeNumber))
{
queryParams.Episode = episodeNumber;
}
}
else if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace() && int.TryParse(searchCriteria.ImdbId, out var intImdb))
{
queryParams.Imdb = intImdb;
if (searchCriteria.EpisodeSearchString.IsNotNullOrWhiteSpace())
{
queryParams.Name = "%" + Regex.Replace(searchCriteria.EpisodeSearchString, "[\\W]+", "%").Trim() + "%";
}
if ((queryParams.Season.HasValue || queryParams.Episode.HasValue) &&
queryParams.Name.IsNullOrWhiteSpace() &&
queryParams.Release.IsNullOrWhiteSpace() &&
!queryParams.TvMaze.HasValue &&
queryParams.Imdb.IsNullOrWhiteSpace())
{
_logger.Debug("NBL API does not support season calls without name, series, id, imdb, tvmaze, or time keys.");
return new IndexerPageableRequestChain();
}
pageableRequests.Add(GetPagedRequests(queryParams, searchCriteria.Limit, searchCriteria.Offset));
if (queryParams.Name.IsNotNullOrWhiteSpace() && (queryParams.Tvmaze is > 0 || queryParams.Imdb is > 0))
{
queryParams = queryParams.Clone();
queryParams.Name = null;
pageableRequests.Add(GetPagedRequests(queryParams, searchCriteria.Limit, searchCriteria.Offset));
}
return pageableRequests;
}
@@ -187,9 +181,11 @@ namespace NzbDrone.Core.Indexers.Definitions
Age = ">0"
};
if (searchCriteria.SanitizedSearchTerm.IsNotNullOrWhiteSpace())
var searchQuery = searchCriteria.SanitizedSearchTerm.Trim();
if (searchQuery.IsNotNullOrWhiteSpace())
{
queryParams.Name = "%" + Regex.Replace(searchCriteria.SanitizedSearchTerm, "[\\W]+", "%").Trim() + "%";
queryParams.Release = searchQuery;
}
pageableRequests.Add(GetPagedRequests(queryParams, searchCriteria.Limit, searchCriteria.Offset));
@@ -231,11 +227,11 @@ namespace NzbDrone.Core.Indexers.Definitions
throw new IndexerException(indexerResponse, "Unexpected response status '{0}' code from indexer request", indexerResponse.HttpResponse.StatusCode);
}
JsonRpcResponse<NebulanceTorrents> jsonResponse;
JsonRpcResponse<NebulanceResponse> jsonResponse;
try
{
jsonResponse = STJson.Deserialize<JsonRpcResponse<NebulanceTorrents>>(indexerResponse.HttpResponse.Content);
jsonResponse = STJson.Deserialize<JsonRpcResponse<NebulanceResponse>>(indexerResponse.HttpResponse.Content);
}
catch (Exception ex)
{
@@ -249,7 +245,7 @@ namespace NzbDrone.Core.Indexers.Definitions
throw new IndexerException(indexerResponse, "Indexer API call returned an error [{0}]", jsonResponse.Error);
}
if (jsonResponse.Result.Items.Count == 0)
if (jsonResponse.Result?.Items == null || jsonResponse.Result.Items.Count == 0)
{
return torrentInfos;
}
@@ -264,14 +260,13 @@ namespace NzbDrone.Core.Indexers.Definitions
var release = new TorrentInfo
{
Title = title,
Guid = details,
InfoUrl = details,
PosterUrl = row.Banner,
DownloadUrl = row.Download,
Title = title.Trim(),
Categories = new List<IndexerCategory> { TvCategoryFromQualityParser.ParseTvShowQuality(row.ReleaseTitle) },
Size = ParseUtil.CoerceLong(row.Size),
Files = row.FileList.Length,
Files = row.FileList.Count(),
PublishDate = DateTime.Parse(row.PublishDateUtc, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal),
Grabs = ParseUtil.CoerceInt(row.Snatch),
Seeders = ParseUtil.CoerceInt(row.Seed),
@@ -280,7 +275,8 @@ namespace NzbDrone.Core.Indexers.Definitions
MinimumRatio = 0, // ratioless
MinimumSeedTime = row.Category.ToLower() == "season" ? 432000 : 86400, // 120 hours for seasons and 24 hours for episodes
DownloadVolumeFactor = 0, // ratioless tracker
UploadVolumeFactor = 1
UploadVolumeFactor = 1,
PosterUrl = row.Banner
};
if (row.TvMazeId.IsNotNullOrWhiteSpace())
@@ -312,60 +308,86 @@ namespace NzbDrone.Core.Indexers.Definitions
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Id { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Time { get; set; }
[JsonProperty(PropertyName="age", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Age { get; set; }
[JsonProperty(PropertyName="tvmaze", DefaultValueHandling = DefaultValueHandling.Ignore)]
public int? Tvmaze { get; set; }
public int? TvMaze { get; set; }
[JsonProperty(PropertyName="imdb", DefaultValueHandling = DefaultValueHandling.Ignore)]
public int? Imdb { get; set; }
public string Imdb { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Hash { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string[] Tags { get; set; }
[JsonProperty(PropertyName="name", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Name { get; set; }
[JsonProperty(PropertyName="release", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Release { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Category { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Series { get; set; }
[JsonProperty(PropertyName="season", DefaultValueHandling = DefaultValueHandling.Ignore)]
public int? Season { get; set; }
[JsonProperty(PropertyName="episode", DefaultValueHandling = DefaultValueHandling.Ignore)]
public int? Episode { get; set; }
public NebulanceQuery Clone()
{
return MemberwiseClone() as NebulanceQuery;
}
}
public class NebulanceResponse
{
public List<NebulanceTorrent> Items { get; set; }
}
public class NebulanceTorrent
{
[JsonPropertyName("rls_name")]
public string ReleaseTitle { get; set; }
[JsonPropertyName("cat")]
public string Category { get; set; }
public string Size { get; set; }
public string Seed { get; set; }
public string Leech { get; set; }
public string Snatch { get; set; }
public string Download { get; set; }
[JsonPropertyName("file_list")]
public string[] FileList { get; set; }
public IEnumerable<string> FileList { get; set; } = Array.Empty<string>();
[JsonPropertyName("group_name")]
public string GroupName { get; set; }
[JsonPropertyName("series_banner")]
public string Banner { get; set; }
[JsonPropertyName("group_id")]
public string TorrentId { get; set; }
[JsonPropertyName("series_id")]
public string TvMazeId { get; set; }
[JsonPropertyName("rls_utc")]
public string PublishDateUtc { get; set; }
public IEnumerable<string> Tags { get; set; }
}
public class NebulanceTorrents
{
public List<NebulanceTorrent> Items { get; set; }
public int Results { get; set; }
public IEnumerable<string> Tags { get; set; } = Array.Empty<string>();
}
}

View File

@@ -75,6 +75,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public class SubsPleaseRequestGenerator : IIndexerRequestGenerator
{
private static readonly Regex ResolutionRegex = new (@"\d{3,4}p", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly NoAuthTorrentBaseSettings _settings;
public SubsPleaseRequestGenerator(NoAuthTorrentBaseSettings settings)
@@ -134,15 +136,6 @@ namespace NzbDrone.Core.Indexers.Definitions
private IEnumerable<IndexerRequest> GetSearchRequests(string term, SearchCriteriaBase searchCriteria)
{
var searchTerm = Regex.Replace(term, "\\[?SubsPlease\\]?\\s*", string.Empty, RegexOptions.IgnoreCase).Trim();
// If the search terms contain a resolution, remove it from the query sent to the API
var resMatch = Regex.Match(searchTerm, "\\d{3,4}[p|P]");
if (resMatch.Success)
{
searchTerm = searchTerm.Replace(resMatch.Value, string.Empty).Trim();
}
var queryParameters = new NameValueCollection
{
{ "tz", "UTC" }
@@ -154,6 +147,16 @@ namespace NzbDrone.Core.Indexers.Definitions
}
else
{
var searchTerm = Regex.Replace(term, "\\[?SubsPlease\\]?\\s*", string.Empty, RegexOptions.IgnoreCase).Trim();
// If the search terms contain a resolution, remove it from the query sent to the API
var resolutionMatch = ResolutionRegex.Match(searchTerm);
if (resolutionMatch.Success)
{
searchTerm = searchTerm.Replace(resolutionMatch.Value, string.Empty).Trim();
}
queryParameters.Set("f", "search");
queryParameters.Set("s", searchTerm);
}
@@ -201,7 +204,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var release = new TorrentInfo
{
InfoUrl = _settings.BaseUrl + $"shows/{value.Page}/",
InfoUrl = $"{_settings.BaseUrl}shows/{value.Page}/",
PublishDate = value.ReleaseDate.LocalDateTime,
Files = 1,
Categories = new List<IndexerCategory> { NewznabStandardCategory.TVAnime },
@@ -213,13 +216,18 @@ namespace NzbDrone.Core.Indexers.Definitions
UploadVolumeFactor = 1
};
if (value.ImageUrl.IsNotNullOrWhiteSpace())
{
release.PosterUrl = _settings.BaseUrl + value.ImageUrl.TrimStart('/');
}
if (value.Episode.ToLowerInvariant() == "movie")
{
release.Categories.Add(NewznabStandardCategory.MoviesOther);
}
// Ex: [SubsPlease] Shingeki no Kyojin (The Final Season) - 64 (1080p)
release.Title += $"[SubsPlease] {value.Show} - {value.Episode} ({d.Resolution}p)";
release.Title = $"[SubsPlease] {value.Show} - {value.Episode} ({d.Resolution}p)";
release.MagnetUrl = d.Magnet;
release.DownloadUrl = null;
release.Guid = d.Magnet;
@@ -269,6 +277,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public string Episode { get; set; }
public SubPleaseDownloadInfo[] Downloads { get; set; }
public string Xdcc { get; set; }
[JsonProperty("image_url")]
public string ImageUrl { get; set; }
public string Page { get; set; }
}