1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-18 16:24:34 -04:00

Compare commits

...

13 Commits

Author SHA1 Message Date
Mark McDowall
0004502d01 Sliding expiration for auth cookie and a little clean up
(cherry picked from commit 05ee4e644907b7f1e84589465ac9ab1848f5a766)
2022-08-19 06:29:45 +00:00
Servarr
d730161800 Automated API Docs update 2022-08-18 22:45:47 -05:00
Qstick
66c1af0555 New: (API) Get Collection by TmdbId 2022-08-18 22:37:15 -05:00
Chris
dca00db317 Added: Ntfy provider for notifications. (#7455)
* Added: Ntfy provider for notifications.

* Changed: Clean up of validation and fields for ntfy

* Fixed: Unused title setting, and spelling issue.
2022-08-18 20:12:06 -05:00
Chris
812e5ac5a3 Fixed: Postgres secret regex now less greedy
[common]
2022-08-18 21:36:22 +01:00
Chris
d01bae92bf Fixed: Regex in log cleanser taking 10+ minutes on messages longer than 100k. (#7481) 2022-08-18 13:00:58 +01:00
Qstick
1a6bf51741 New: Add support for Plex Edition tags in naming 2022-08-16 23:04:46 -05:00
Qstick
f3e7843150 New: Make Plex imdb tags conditional 2022-08-16 23:04:46 -05:00
Qstick
886b9b1c05 Fixed: Correctly map movie by original title on import
Fixes #7348
2022-08-15 23:16:22 -05:00
Qstick
d8891ee4ea Fixed: UI Error on Collection Filter
Fixes #7474
2022-08-14 16:33:40 -05:00
Qstick
192dd9c137 Fixed: Allow 0 Min on Size CustomFormat Condition
Fixes #7476
2022-08-14 16:19:28 -05:00
Gylesie
b549fddf95 New: Add Slovak Language 2022-08-13 16:25:32 -05:00
Qstick
c1f538ed97 Bump version to 4.2.2 2022-08-13 15:48:22 -05:00
22 changed files with 464 additions and 36 deletions

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '4.2.1'
majorVersion: '4.2.2'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'

View File

@@ -116,7 +116,7 @@ export const filterPredicates = {
const predicate = filterTypePredicates[type];
const { collection } = item;
return predicate(collection ? collection.name : '', filterValue);
return predicate(collection ? collection.title : '', filterValue);
},
originalLanguage: function(item, filterValue, type) {

View File

@@ -341,8 +341,8 @@ export const defaultState = {
const collectionList = items.reduce((acc, movie) => {
if (movie.collection) {
acc.push({
id: movie.collection.name,
name: movie.collection.name
id: movie.collection.title,
name: movie.collection.title
});
}

View File

@@ -18,7 +18,7 @@ namespace NzbDrone.Common.Instrumentation
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=[?& ;])[^=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"\b[^;=]*?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),

View File

@@ -0,0 +1,67 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
{
[TestFixture]
public class EditionTagsFixture : CoreTest<FileNameBuilder>
{
private Movie _movie;
private MovieFile _movieFile;
private NamingConfig _namingConfig;
[SetUp]
public void Setup()
{
_movie = Builder<Movie>
.CreateNew()
.With(s => s.Title = "South Park")
.Build();
_movieFile = new MovieFile { Quality = new QualityModel(), ReleaseGroup = "SonarrTest" };
_namingConfig = NamingConfig.Default;
_namingConfig.RenameMovies = true;
Mocker.GetMock<INamingConfigService>()
.Setup(c => c.GetConfig()).Returns(_namingConfig);
Mocker.GetMock<IQualityDefinitionService>()
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
Mocker.GetMock<ICustomFormatService>()
.Setup(v => v.All())
.Returns(new List<CustomFormat>());
}
[Test]
public void should_add_edition_tag()
{
_movieFile.Edition = "Uncut";
_namingConfig.StandardMovieFormat = "{Movie Title} [{Edition Tags}]";
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("South Park [Uncut]");
}
[TestCase("{Movie Title} {edition-{Edition Tags}}")]
public void should_conditional_hide_edition_tags_in_plex_format(string movieFormat)
{
_movieFile.Edition = "";
_namingConfig.StandardMovieFormat = movieFormat;
Subject.BuildFileName(_movie, _movieFile)
.Should().Be("South Park");
}
}
}

View File

@@ -1,4 +1,4 @@
using FizzWare.NBuilder;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NUnit.Framework.Internal;
@@ -47,5 +47,25 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
Subject.GetMovieFolder(_movie)
.Should().Be($"Movie Title ({_movie.TmdbId})");
}
[Test]
public void should_add_imdb_tag()
{
_namingConfig.MovieFolderFormat = "{Movie Title} {imdb-{ImdbId}}";
Subject.GetMovieFolder(_movie)
.Should().Be($"Movie Title {{imdb-{_movie.ImdbId}}}");
}
[Test]
public void should_skip_imdb_tag_if_null()
{
_namingConfig.MovieFolderFormat = "{Movie Title} {imdb-{ImdbId}}";
_movie.ImdbId = null;
Subject.GetMovieFolder(_movie)
.Should().Be($"Movie Title");
}
}
}

View File

@@ -342,6 +342,16 @@ namespace NzbDrone.Core.Test.ParserTests
result.Languages.Should().BeEquivalentTo(Language.Bengali);
}
[TestCase("Movie.Title.1994.HDTV.x264.SK-iCZi")]
[TestCase("Movie.Title.2019.1080p.HDTV.x265.iNTERNAL.SK-iCZi")]
[TestCase("Movie.Title.2018.SLOVAK.DUAL.2160p.UHD.BluRay.x265-iCZi")]
[TestCase("Movie.Title.1990.SLOVAK.HDTV.x264-iCZi")]
public void should_parse_language_slovak(string postTitle)
{
var result = Parser.Parser.ParseMovieTitle(postTitle);
result.Languages.Should().BeEquivalentTo(Language.Slovak);
}
[TestCase("Movie.Title.en.sub")]
[TestCase("Movie Title.eng.sub")]
[TestCase("Movie.Title.eng.forced.sub")]

View File

@@ -10,7 +10,7 @@ namespace NzbDrone.Core.CustomFormats
{
public SizeSpecificationValidator()
{
RuleFor(c => c.Min).GreaterThan(0);
RuleFor(c => c.Min).GreaterThanOrEqualTo(0);
RuleFor(c => c.Max).GreaterThan(c => c.Min);
}
}

View File

@@ -105,6 +105,7 @@ namespace NzbDrone.Core.Languages
public static Language Ukrainian => new Language(32, "Ukrainian");
public static Language Persian => new Language(33, "Persian");
public static Language Bengali => new Language(34, "Bengali");
public static Language Slovak => new Language(35, "Slovak");
public static Language Any => new Language(-1, "Any");
public static Language Original => new Language(-2, "Original");
@@ -149,6 +150,7 @@ namespace NzbDrone.Core.Languages
Ukrainian,
Persian,
Bengali,
Slovak,
Any,
Original
};

View File

@@ -119,10 +119,18 @@ namespace NzbDrone.Core.Movies
return FindByTitle(new List<string> { title }, year, otherTitles, candidates);
}
public Movie FindByTitle(List<string> cleanTitles, int? year, List<string> otherTitles, List<Movie> candidates)
public Movie FindByTitle(List<string> titles, int? year, List<string> otherTitles, List<Movie> candidates)
{
var cleanTitles = titles.Select(t => t.CleanMovieTitle().ToLowerInvariant());
var result = candidates.Where(x => cleanTitles.Contains(x.MovieMetadata.Value.CleanTitle)).FirstWithYear(year);
if (result == null)
{
result =
candidates.Where(movie => cleanTitles.Contains(movie.MovieMetadata.Value.CleanOriginalTitle)).FirstWithYear(year);
}
if (result == null)
{
result =

View File

@@ -0,0 +1,66 @@
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Movies;
namespace NzbDrone.Core.Notifications.Ntfy
{
public class Ntfy : NotificationBase<NtfySettings>
{
private readonly INtfyProxy _proxy;
public Ntfy(INtfyProxy proxy)
{
_proxy = proxy;
}
public override string Name => "ntfy.sh";
public override string Link => "https://ntfy.sh/";
public override void OnGrab(GrabMessage grabMessage)
{
_proxy.SendNotification(MOVIE_GRABBED_TITLE_BRANDED, grabMessage.Message, Settings);
}
public override void OnDownload(DownloadMessage message)
{
_proxy.SendNotification(MOVIE_DOWNLOADED_TITLE_BRANDED, message.Message, Settings);
}
public override void OnMovieAdded(Movie movie)
{
_proxy.SendNotification(MOVIE_ADDED_TITLE_BRANDED, $"{movie.Title} added to library", Settings);
}
public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage)
{
_proxy.SendNotification(MOVIE_FILE_DELETED_TITLE, deleteMessage.Message, Settings);
}
public override void OnMovieDelete(MovieDeleteMessage deleteMessage)
{
_proxy.SendNotification(MOVIE_DELETED_TITLE, deleteMessage.Message, Settings);
}
public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck)
{
_proxy.SendNotification(HEALTH_ISSUE_TITLE_BRANDED, healthCheck.Message, Settings);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
_proxy.SendNotification(APPLICATION_UPDATE_TITLE_BRANDED, updateMessage.Message, Settings);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_proxy.Test(Settings));
return new ValidationResult(failures);
}
}
}

View File

@@ -0,0 +1,18 @@
using System;
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Notifications.Ntfy
{
public class NtfyException : NzbDroneException
{
public NtfyException(string message)
: base(message)
{
}
public NtfyException(string message, Exception innerException, params object[] args)
: base(message, innerException, args)
{
}
}
}

View File

@@ -0,0 +1,11 @@
namespace NzbDrone.Core.Notifications.Ntfy
{
public enum NtfyPriority
{
Min = 1,
Low = 2,
Default = 3,
High = 4,
Max = 5
}
}

View File

@@ -0,0 +1,138 @@
using System;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
namespace NzbDrone.Core.Notifications.Ntfy
{
public interface INtfyProxy
{
void SendNotification(string title, string message, NtfySettings settings);
ValidationFailure Test(NtfySettings settings);
}
public class NtfyProxy : INtfyProxy
{
private const string DEFAULT_PUSH_URL = "https://ntfy.sh";
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
public NtfyProxy(IHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
}
public void SendNotification(string title, string message, NtfySettings settings)
{
var error = false;
var serverUrl = settings.ServerUrl.IsNullOrWhiteSpace() ? NtfyProxy.DEFAULT_PUSH_URL : settings.ServerUrl;
foreach (var topic in settings.Topics)
{
var request = BuildTopicRequest(serverUrl, topic);
try
{
SendNotification(title, message, request, settings);
}
catch (NtfyException ex)
{
_logger.Error(ex, "Unable to send test message to {0}", topic);
error = true;
}
}
if (error)
{
throw new NtfyException("Unable to send Ntfy notifications to all topics");
}
}
private HttpRequestBuilder BuildTopicRequest(string serverUrl, string topic)
{
var trimServerUrl = serverUrl.TrimEnd('/');
var requestBuilder = new HttpRequestBuilder($"{trimServerUrl}/{topic}").Post();
return requestBuilder;
}
public ValidationFailure Test(NtfySettings settings)
{
try
{
const string title = "Radarr - Test Notification";
const string body = "This is a test message from Radarr";
SendNotification(title, body, settings);
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized || ex.Response.StatusCode == HttpStatusCode.Forbidden)
{
_logger.Error(ex, "Authorization is required");
return new ValidationFailure("UserName", "Authorization is required");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("ServerUrl", "Unable to send test message");
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("", "Unable to send test message");
}
return null;
}
private void SendNotification(string title, string message, HttpRequestBuilder requestBuilder, NtfySettings settings)
{
try
{
requestBuilder.Headers.Add("X-Title", title);
requestBuilder.Headers.Add("X-Message", message);
requestBuilder.Headers.Add("X-Priority", settings.Priority.ToString());
if (settings.Tags.Any())
{
requestBuilder.Headers.Add("X-Tags", settings.Tags.Join(","));
}
if (!settings.ClickUrl.IsNullOrWhiteSpace())
{
requestBuilder.Headers.Add("X-Click", settings.ClickUrl);
}
var request = requestBuilder.Build();
if (!settings.UserName.IsNullOrWhiteSpace() && !settings.Password.IsNullOrWhiteSpace())
{
request.Credentials = new BasicNetworkCredential(settings.UserName, settings.Password);
}
_httpClient.Execute(request);
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized || ex.Response.StatusCode == HttpStatusCode.Forbidden)
{
_logger.Error(ex, "Authorization is required");
throw;
}
throw new NtfyException("Unable to send text message: {0}", ex, ex.Message);
}
}
}
}

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Notifications.Ntfy
{
public class NtfySettingsValidator : AbstractValidator<NtfySettings>
{
public NtfySettingsValidator()
{
RuleFor(c => c.Topics).NotEmpty();
RuleFor(c => c.Priority).InclusiveBetween(1, 5);
RuleFor(c => c.ServerUrl).IsValidUrl().When(c => !c.ServerUrl.IsNullOrWhiteSpace());
RuleFor(c => c.ClickUrl).IsValidUrl().When(c => !c.ClickUrl.IsNullOrWhiteSpace());
RuleFor(c => c.UserName).NotEmpty().When(c => !c.Password.IsNullOrWhiteSpace());
RuleFor(c => c.Password).NotEmpty().When(c => !c.UserName.IsNullOrWhiteSpace());
RuleForEach(c => c.Topics).NotEmpty().Matches("[a-zA-Z0-9_-]+").Must(c => !InvalidTopics.Contains(c)).WithMessage("Invalid topic");
}
private static List<string> InvalidTopics => new List<string> { "announcements", "app", "docs", "settings", "stats", "mytopic-rw", "mytopic-ro", "mytopic-wo" };
}
public class NtfySettings : IProviderConfig
{
private static readonly NtfySettingsValidator Validator = new NtfySettingsValidator();
public NtfySettings()
{
Topics = Array.Empty<string>();
Priority = 3;
}
[FieldDefinition(0, Label = "Server Url", Type = FieldType.Url, HelpLink = "https://ntfy.sh/docs/install/", HelpText = "Leave blank to use public server (https://ntfy.sh)", Placeholder = "https://ntfy.sh")]
public string ServerUrl { get; set; }
[FieldDefinition(1, Label = "User Name", HelpText = "Optional Authorization", Privacy = PrivacyLevel.UserName)]
public string UserName { get; set; }
[FieldDefinition(2, Label = "Password", Type = FieldType.Password, HelpText = "Optional Password", Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(3, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NtfyPriority))]
public int Priority { get; set; }
[FieldDefinition(4, Label = "Topics", HelpText = "List of Topics to send notifications to", Type = FieldType.Tag, Placeholder = "Topic1234,Topic4321")]
public IEnumerable<string> Topics { get; set; }
[FieldDefinition(5, Label = "Ntfy Tags and Emojis", Type = FieldType.Tag, HelpText = "Optional list of tags or emojis to use", Placeholder = "warning,skull", HelpLink = "https://ntfy.sh/docs/emojis/")]
public IEnumerable<string> Tags { get; set; }
[FieldDefinition(6, Label = "Click Url", Type = FieldType.Url, HelpText = "Optional link when user clicks notification", Placeholder = "https://myserver.example.com/radarr")]
public string ClickUrl { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -38,20 +38,9 @@ namespace NzbDrone.Core.Organizer
private readonly ICustomFormatService _formatService;
private readonly Logger _logger;
private static readonly Regex TitleRegex = new Regex(@"\{(?<prefix>[- ._\[(]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9|]+))?(?<suffix>[- ._)\]]*)\}",
private static readonly Regex TitleRegex = new Regex(@"(?<tag>\{(?:imdb-|edition-))?\{(?<prefix>[- ._\[(]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9|]+))?(?<suffix>[-} ._)\]]*)\}",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
private static readonly Regex TagsRegex = new Regex(@"(?<tags>\{tags(?:\:0+)?})",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static readonly Regex SeasonEpisodePatternRegex = new Regex(@"(?<separator>(?<=})[- ._]+?)?(?<seasonEpisode>s?{season(?:\:0+)?}(?<episodeSeparator>[- ._]?[ex])(?<episode>{episode(?:\:0+)?}))(?<separator>[- ._]+?(?={))?",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static readonly Regex AbsoluteEpisodePatternRegex = new Regex(@"(?<separator>(?<=})[- ._]+?)?(?<absolute>{absolute(?:\:0+)?})(?<separator>[- ._]+?(?={))?",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static readonly Regex AirDateRegex = new Regex(@"\{Air(\s|\W|_)Date\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static readonly Regex MovieTitleRegex = new Regex(@"(?<token>\{((?:(Movie|Original))(?<separator>[- ._])(Clean|Original)?(Title|Filename)(The)?)(?::(?<customFormat>[a-z0-9|]+))?\})",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
@@ -61,11 +50,6 @@ namespace NzbDrone.Core.Organizer
private static readonly Regex ScenifyRemoveChars = new Regex(@"(?<=\s)(,|<|>|\/|\\|;|:|'|""|\||`|~|!|\?|@|$|%|^|\*|-|_|=){1}(?=\s)|('|:|\?|,)(?=(?:(?:s|m)\s)|\s|$)|(\(|\)|\[|\]|\{|\})", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ScenifyReplaceChars = new Regex(@"[\/]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
//TODO: Support Written numbers (One, Two, etc) and Roman Numerals (I, II, III etc)
private static readonly Regex MultiPartCleanupRegex = new Regex(@"(?:\(\d+\)|(Part|Pt\.?)\s?\d+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' };
private static readonly Regex TitlePrefixRegex = new Regex(@"^(The|An|A) (.*?)((?: *\([^)]+\))*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ReservedDeviceNamesRegex = new Regex(@"^(?:aux|com[1-9]|con|lpt[1-9]|nul|prn)\.", RegexOptions.Compiled | RegexOptions.IgnoreCase);
@@ -133,7 +117,7 @@ namespace NzbDrone.Core.Organizer
AddQualityTokens(tokenHandlers, movie, movieFile);
AddMediaInfoTokens(tokenHandlers, movieFile);
AddMovieFileTokens(tokenHandlers, movieFile);
AddTagsTokens(tokenHandlers, movieFile);
AddEditionTagsTokens(tokenHandlers, movieFile);
AddCustomFormats(tokenHandlers, movie, movieFile, customFormats);
var splitPatterns = pattern.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
@@ -193,7 +177,7 @@ namespace NzbDrone.Core.Organizer
AddQualityTokens(tokenHandlers, movie, movieFile);
AddMediaInfoTokens(tokenHandlers, movieFile);
AddMovieFileTokens(tokenHandlers, movieFile);
AddTagsTokens(tokenHandlers, movieFile);
AddEditionTagsTokens(tokenHandlers, movieFile);
}
else
{
@@ -297,7 +281,7 @@ namespace NzbDrone.Core.Organizer
return movie.Title;
}
private void AddTagsTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, MovieFile movieFile)
private void AddEditionTagsTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, MovieFile movieFile)
{
if (movieFile.Edition.IsNotNullOrWhiteSpace())
{
@@ -497,6 +481,7 @@ namespace NzbDrone.Core.Organizer
var tokenMatch = new TokenMatch
{
RegexMatch = match,
Tag = match.Groups["tag"].Value,
Prefix = match.Groups["prefix"].Value,
Separator = match.Groups["separator"].Value,
Suffix = match.Groups["suffix"].Value,
@@ -531,7 +516,7 @@ namespace NzbDrone.Core.Organizer
if (!replacementText.IsNullOrWhiteSpace())
{
replacementText = tokenMatch.Prefix + replacementText + tokenMatch.Suffix;
replacementText = tokenMatch.Tag + tokenMatch.Prefix + replacementText + tokenMatch.Suffix;
}
return replacementText;
@@ -598,6 +583,7 @@ namespace NzbDrone.Core.Organizer
internal sealed class TokenMatch
{
public Match RegexMatch { get; set; }
public string Tag { get; set; }
public string Prefix { get; set; }
public string Separator { get; set; }
public string Suffix { get; set; }

View File

@@ -42,7 +42,8 @@ namespace NzbDrone.Core.Parser
new IsoLanguage("uk", "", "ukr", "Ukrainian", Language.Ukrainian),
new IsoLanguage("fa", "", "fas", "Persian", Language.Persian),
new IsoLanguage("be", "", "ben", "Bengali", Language.Bengali),
new IsoLanguage("lt", "", "lit", "Lithuanian", Language.Lithuanian)
new IsoLanguage("lt", "", "lit", "Lithuanian", Language.Lithuanian),
new IsoLanguage("sk", "", "slk", "Slovak", Language.Slovak)
};
public static IsoLanguage Find(string isoCode)

View File

@@ -32,7 +32,8 @@ namespace NzbDrone.Core.Parser
private static readonly Regex CaseSensitiveLanguageRegex = new Regex(@"(?:(?i)(?<!SUB[\W|_|^]))(?:(?<lithuanian>\bLT\b)|
(?<czech>\bCZ\b)|
(?<polish>\bPL\b)|
(?<bulgarian>\bBG\b))(?:(?i)(?![\W|_|^]SUB))",
(?<bulgarian>\bBG\b))(?:(?i)(?![\W|_|^]SUB))|
(?<slovak>\bSK\b)",
RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
private static readonly Regex SubtitleLanguageRegex = new Regex(".+?[-_. ](?<iso_code>[a-z]{2,3})(?:[-_. ]forced)?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
@@ -182,6 +183,11 @@ namespace NzbDrone.Core.Parser
languages.Add(Language.Bengali);
}
if (lowerTitle.Contains("slovak"))
{
languages.Add(Language.Slovak);
}
// Case sensitive
var caseSensitiveMatch = CaseSensitiveLanguageRegex.Match(title);
@@ -205,6 +211,11 @@ namespace NzbDrone.Core.Parser
languages.Add(Language.Bulgarian);
}
if (caseSensitiveMatch.Groups["slovak"].Captures.Cast<Capture>().Any())
{
languages.Add(Language.Slovak);
}
var matches = LanguageRegex.Matches(title);
foreach (Match match in matches)

View File

@@ -53,9 +53,25 @@ namespace Radarr.Api.V3.Collections
}
[HttpGet]
public List<CollectionResource> GetCollections()
public List<CollectionResource> GetCollections(int? tmdbId)
{
return MapToResource(_collectionService.GetAllCollections()).ToList();
var collectionResources = new List<CollectionResource>();
if (tmdbId.HasValue)
{
var collection = _collectionService.FindByTmdbId(tmdbId.Value);
if (collection != null)
{
collectionResources.AddIfNotNull(MapToResource(collection));
}
}
else
{
collectionResources = MapToResource(_collectionService.GetAllCollections()).ToList();
}
return collectionResources;
}
[RestPutById]

View File

@@ -589,6 +589,16 @@
"tags": [
"Collection"
],
"parameters": [
{
"name": "tmdbId",
"in": "query",
"schema": {
"type": "integer",
"format": "int32"
}
}
],
"responses": {
"200": {
"description": "Success",

View File

@@ -33,6 +33,7 @@ namespace Radarr.Http.Authentication
options.AccessDeniedPath = "/login?loginFailed=true";
options.LoginPath = "/login";
options.ExpireTimeSpan = TimeSpan.FromDays(7);
options.SlidingExpiration = true;
})
.AddApiKey("API", options =>
{

View File

@@ -1,5 +1,8 @@
using System;
using System.Net;
using Microsoft.AspNetCore.Http;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
using Radarr.Http.Extensions;
@@ -15,17 +18,14 @@ namespace Radarr.Http.Authentication
public class AuthenticationService : IAuthenticationService
{
private const string AnonymousUser = "Anonymous";
private static readonly Logger _authLogger = LogManager.GetLogger("Auth");
private readonly IUserService _userService;
private static string API_KEY;
private static AuthenticationType AUTH_METHOD;
public AuthenticationService(IConfigFileProvider configFileProvider, IUserService userService)
{
_userService = userService;
API_KEY = configFileProvider.ApiKey;
AUTH_METHOD = configFileProvider.AuthenticationMethod;
}