mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-13 15:34:56 -04:00
Compare commits
41 Commits
zeus-old
...
v4.5.0.710
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
204a8bab79 | ||
|
|
0158c84a98 | ||
|
|
ac51a943fb | ||
|
|
78edc9aa43 | ||
|
|
f2bf494cef | ||
|
|
5727e7c43b | ||
|
|
a7ba1a6454 | ||
|
|
cc285fab45 | ||
|
|
e0ad573e7f | ||
|
|
9a23b7f0fc | ||
|
|
85aecbe67e | ||
|
|
bbb20e95af | ||
|
|
3fb337e20b | ||
|
|
5e63da418e | ||
|
|
890f9d6fe4 | ||
|
|
8a496cbdae | ||
|
|
f54a5388a0 | ||
|
|
6961633cc9 | ||
|
|
1be450a9d0 | ||
|
|
2b4d6464e2 | ||
|
|
ee4c34bd6c | ||
|
|
07d41f7902 | ||
|
|
bb573594d9 | ||
|
|
12e360ab4f | ||
|
|
bae555f63e | ||
|
|
31abb93d8c | ||
|
|
75035035e1 | ||
|
|
c1e5990a58 | ||
|
|
4a42ebe44c | ||
|
|
c82aa5c2c7 | ||
|
|
d1bae32e1c | ||
|
|
c514c7cac0 | ||
|
|
3d244057b5 | ||
|
|
323510300c | ||
|
|
669d87b7ef | ||
|
|
ec7f7b085a | ||
|
|
ecc906a754 | ||
|
|
d0fcac389c | ||
|
|
621acbef9a | ||
|
|
7fb1163b23 | ||
|
|
b48eda95dd |
@@ -9,13 +9,13 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '4.4.3'
|
||||
majorVersion: '4.5.0'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '6.0.400'
|
||||
dotnetVersion: '6.0.408'
|
||||
nodeVersion: '16.X'
|
||||
innoVersion: '6.2.0'
|
||||
windowsImage: 'windows-2022'
|
||||
|
||||
@@ -113,6 +113,7 @@ class AddNewMovieSearchResult extends Component {
|
||||
images={images}
|
||||
size={250}
|
||||
overflow={true}
|
||||
lazy={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -112,6 +112,12 @@ class TextInput extends Component {
|
||||
this._isMouseTarget = false;
|
||||
};
|
||||
|
||||
onWheel = () => {
|
||||
if (this.props.type === 'number') {
|
||||
this._input.blur();
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -161,6 +167,7 @@ class TextInput extends Component {
|
||||
onKeyUp={this.onKeyUp}
|
||||
onMouseDown={this.onMouseDown}
|
||||
onMouseUp={this.onMouseUp}
|
||||
onWheel={this.onWheel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,11 +8,15 @@
|
||||
}
|
||||
|
||||
.host {
|
||||
flex: 0 0 300px;
|
||||
@add-mixin truncate;
|
||||
|
||||
flex: 0 1 300px;
|
||||
}
|
||||
|
||||
.path {
|
||||
flex: 0 0 400px;
|
||||
@add-mixin truncate;
|
||||
|
||||
flex: 0 1 400px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
.remotePathMappingsHeader {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
padding-right: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.host {
|
||||
flex: 0 0 300px;
|
||||
@add-mixin truncate;
|
||||
|
||||
flex: 0 1 300px;
|
||||
}
|
||||
|
||||
.path {
|
||||
flex: 0 0 400px;
|
||||
@add-mixin truncate;
|
||||
|
||||
flex: 0 1 400px;
|
||||
}
|
||||
|
||||
.addRemotePathMapping {
|
||||
|
||||
@@ -191,6 +191,7 @@ const delayProfileShape = {
|
||||
enableTorrent: PropTypes.shape(boolSettingShape).isRequired,
|
||||
usenetDelay: PropTypes.shape(numberSettingShape).isRequired,
|
||||
torrentDelay: PropTypes.shape(numberSettingShape).isRequired,
|
||||
bypassIfHighestQuality: PropTypes.shape(boolSettingShape).isRequired,
|
||||
order: PropTypes.shape(numberSettingShape),
|
||||
tags: PropTypes.shape(tagSettingShape).isRequired
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ const newDelayProfile = {
|
||||
preferredProtocol: 'usenet',
|
||||
usenetDelay: 0,
|
||||
torrentDelay: 0,
|
||||
bypassIfHighestQuality: false,
|
||||
tags: []
|
||||
};
|
||||
|
||||
|
||||
@@ -94,27 +94,27 @@ module.exports = {
|
||||
defaultBackgroundColor: '#333',
|
||||
defaultBorderColor: '#eaeaea',
|
||||
defaultHoverBackgroundColor: '#444',
|
||||
defaultHoverBorderColor: '#d6d6d6;',
|
||||
defaultHoverBorderColor: '#d6d6d6',
|
||||
|
||||
primaryBackgroundColor: '#5d9cec',
|
||||
primaryBorderColor: '#5899eb',
|
||||
primaryHoverBackgroundColor: '#4b91ea',
|
||||
primaryHoverBorderColor: '#3483e7;',
|
||||
primaryHoverBorderColor: '#3483e7',
|
||||
|
||||
successBackgroundColor: '#27c24c',
|
||||
successBorderColor: '#26be4a',
|
||||
successHoverBackgroundColor: '#24b145',
|
||||
successHoverBorderColor: '#1f9c3d;',
|
||||
successHoverBorderColor: '#1f9c3d',
|
||||
|
||||
warningBackgroundColor: '#ff902b',
|
||||
warningBorderColor: '#ff8d26',
|
||||
warningHoverBackgroundColor: '#ff8517',
|
||||
warningHoverBorderColor: '#fc7800;',
|
||||
warningHoverBorderColor: '#fc7800',
|
||||
|
||||
dangerBackgroundColor: '#f05050',
|
||||
dangerBorderColor: '#f04b4b',
|
||||
dangerHoverBackgroundColor: '#ee3d3d',
|
||||
dangerHoverBorderColor: '#ec2626;',
|
||||
dangerHoverBorderColor: '#ec2626',
|
||||
|
||||
iconButtonDisabledColor: '#7a7a7a',
|
||||
iconButtonHoverColor: '#666',
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"@fortawesome/free-regular-svg-icons": "6.1.0",
|
||||
"@fortawesome/free-solid-svg-icons": "6.1.0",
|
||||
"@fortawesome/react-fontawesome": "0.1.18",
|
||||
"@microsoft/signalr": "6.0.8",
|
||||
"@microsoft/signalr": "6.0.16",
|
||||
"@sentry/browser": "6.18.2",
|
||||
"@sentry/integrations": "6.18.2",
|
||||
"classnames": "2.3.1",
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
|
||||
<!-- Standard testing packages -->
|
||||
<ItemGroup Condition="'$(TestProject)'=='true'">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||
|
||||
@@ -483,12 +483,11 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
return mounts.Where(drive => drive.RootDirectory.PathEquals(path) ||
|
||||
drive.RootDirectory.IsParentPath(path))
|
||||
.OrderByDescending(drive => drive.RootDirectory.Length)
|
||||
.FirstOrDefault();
|
||||
.MaxBy(drive => drive.RootDirectory.Length);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug(ex, string.Format("Failed to get mount for path {0}", path));
|
||||
Logger.Debug(ex, $"Failed to get mount for path {path}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@ namespace NzbDrone.Common.Processes
|
||||
processInfo = new ProcessInfo();
|
||||
processInfo.Id = process.Id;
|
||||
processInfo.Name = process.ProcessName;
|
||||
processInfo.StartPath = GetExeFileName(process);
|
||||
processInfo.StartPath = process.MainModule.FileName;
|
||||
|
||||
if (process.Id != GetCurrentProcessId() && process.HasExited)
|
||||
{
|
||||
@@ -335,16 +335,6 @@ namespace NzbDrone.Common.Processes
|
||||
return processInfo;
|
||||
}
|
||||
|
||||
private static string GetExeFileName(Process process)
|
||||
{
|
||||
if (process.MainModule.FileName != "mono.exe")
|
||||
{
|
||||
return process.MainModule.FileName;
|
||||
}
|
||||
|
||||
return process.Modules.Cast<ProcessModule>().FirstOrDefault(module => module.ModuleName.ToLower().EndsWith(".exe")).FileName;
|
||||
}
|
||||
|
||||
private List<Process> GetProcessesByName(string name)
|
||||
{
|
||||
var processes = Process.GetProcessesByName(name).ToList();
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" Version="5.3.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.3.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
|
||||
@@ -14,10 +14,10 @@
|
||||
<PackageReference Include="Sentry" Version="3.23.1" />
|
||||
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.5" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.7" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="6.0.0" />
|
||||
|
||||
@@ -81,8 +81,9 @@ namespace NzbDrone.Core.Test.Blocklisting
|
||||
|
||||
Subject.DeleteForMovies(new List<int> { _movie1.Id });
|
||||
|
||||
var removedMovieBlocklists = Subject.BlocklistedByMovie(_movie1.Id);
|
||||
var nonRemovedMovieBlocklists = Subject.BlocklistedByMovie(_movie2.Id);
|
||||
var blocklist = Subject.All();
|
||||
var removedMovieBlocklists = blocklist.Where(b => b.MovieId == _movie1.Id);
|
||||
var nonRemovedMovieBlocklists = blocklist.Where(b => b.MovieId == _movie2.Id);
|
||||
|
||||
removedMovieBlocklists.Should().HaveCount(0);
|
||||
nonRemovedMovieBlocklists.Should().HaveCount(1);
|
||||
|
||||
@@ -4,9 +4,11 @@ using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.CustomFormats;
|
||||
using NzbDrone.Core.DecisionEngine.Specifications;
|
||||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Profiles;
|
||||
@@ -204,5 +206,31 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
|
||||
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_false_if_same_quality_non_proper_in_queue_and_download_propers_is_do_not_upgrade()
|
||||
{
|
||||
_remoteMovie.ParsedMovieInfo.Quality = new QualityModel(Quality.HDTV720p, new Revision(2));
|
||||
_movie.Profile.Cutoff = _remoteMovie.ParsedMovieInfo.Quality.Quality.Id;
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.Setup(s => s.DownloadPropersAndRepacks)
|
||||
.Returns(ProperDownloadTypes.DoNotUpgrade);
|
||||
|
||||
var remoteMovie = Builder<RemoteMovie>.CreateNew()
|
||||
.With(r => r.Movie = _movie)
|
||||
.With(r => r.ParsedMovieInfo = new ParsedMovieInfo
|
||||
{
|
||||
Quality = new QualityModel(Quality.HDTV720p),
|
||||
Languages = new List<Language> { Language.English }
|
||||
})
|
||||
.With(r => r.Release = _releaseInfo)
|
||||
.With(r => r.CustomFormats = new List<CustomFormat>())
|
||||
.Build();
|
||||
|
||||
GivenQueue(new List<RemoteMovie> { remoteMovie });
|
||||
|
||||
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||
[TestCase("msmpeg4, DIV3", "Movie the Title (1976) 360p MPEG Audio.avi", "DivX")]
|
||||
[TestCase("msmpeg4v2, DIV3", "Movie the Title (1976) 360p MPEG Audio.avi", "DivX")]
|
||||
[TestCase("msmpeg4v3, DIV3", "Movie the Title (1976) 360p MPEG Audio.avi", "DivX")]
|
||||
[TestCase("vp6f, 4", "Movie the Title (1976) 360p MPEG Audio.flv", "VP6")]
|
||||
[TestCase("vp6, 4", "Top Gear - S12E01 - Lorries - SD TV.flv", "VP6")]
|
||||
[TestCase("vp7, VP70", "Movie the Title.avi", "VP7")]
|
||||
[TestCase("vp8, V_VP8", "Movie the Title.mkv", "VP8")]
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Test.NotificationTests.EmailTests
|
||||
[TestCase("radarr.video")]
|
||||
public void should_not_be_valid_if_cc_is_invalid(string email)
|
||||
{
|
||||
_emailSettings.CC = new string[] { email };
|
||||
_emailSettings.Cc = new string[] { email };
|
||||
|
||||
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
|
||||
}
|
||||
@@ -94,7 +94,7 @@ namespace NzbDrone.Core.Test.NotificationTests.EmailTests
|
||||
public void should_not_be_valid_if_to_bcc_cc_are_all_empty()
|
||||
{
|
||||
_emailSettings.To = Array.Empty<string>();
|
||||
_emailSettings.CC = Array.Empty<string>();
|
||||
_emailSettings.Cc = Array.Empty<string>();
|
||||
_emailSettings.Bcc = Array.Empty<string>();
|
||||
|
||||
_validator.Validate(_emailSettings).IsValid.Should().BeFalse();
|
||||
|
||||
@@ -69,7 +69,8 @@ namespace NzbDrone.Core.Backup
|
||||
_diskProvider.EnsureFolder(_backupTempFolder);
|
||||
_diskProvider.EnsureFolder(GetBackupFolder(backupType));
|
||||
|
||||
var backupFilename = string.Format("radarr_backup_v{0}_{1:yyyy.MM.dd_HH.mm.ss}.zip", BuildInfo.Version, DateTime.Now);
|
||||
var dateNow = DateTime.Now;
|
||||
var backupFilename = $"radarr_backup_v{BuildInfo.Version}_{dateNow:yyyy.MM.dd_HH.mm.ss}.zip";
|
||||
var backupPath = Path.Combine(GetBackupFolder(backupType), backupFilename);
|
||||
|
||||
Cleanup();
|
||||
@@ -81,7 +82,7 @@ namespace NzbDrone.Core.Backup
|
||||
|
||||
BackupConfigFile();
|
||||
BackupDatabase();
|
||||
CreateVersionInfo();
|
||||
CreateVersionInfo(dateNow);
|
||||
|
||||
_logger.ProgressDebug("Creating backup zip");
|
||||
|
||||
@@ -208,11 +209,15 @@ namespace NzbDrone.Core.Backup
|
||||
_diskTransferService.TransferFile(configFile, tempConfigFile, TransferMode.Copy);
|
||||
}
|
||||
|
||||
private void CreateVersionInfo()
|
||||
private void CreateVersionInfo(DateTime dateNow)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var tempFile = Path.Combine(_backupTempFolder, "INFO");
|
||||
|
||||
builder.AppendLine(BuildInfo.Version.ToString());
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine($"v{BuildInfo.Version}");
|
||||
builder.AppendLine($"{dateNow:yyyy-MM-dd HH:mm:ss}");
|
||||
|
||||
_diskProvider.WriteAllText(tempFile, builder.ToString());
|
||||
}
|
||||
|
||||
private void CleanupOldBackups(BackupType backupType)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Movies;
|
||||
@@ -32,7 +33,14 @@ namespace NzbDrone.Core.Blocklisting
|
||||
|
||||
public List<Blocklist> BlocklistedByMovie(int movieId)
|
||||
{
|
||||
return Query(x => x.MovieId == movieId);
|
||||
var builder = Builder().Join<Blocklist, Movie>((h, a) => h.MovieId == a.Id)
|
||||
.Where<Blocklist>(h => h.MovieId == movieId);
|
||||
|
||||
return _database.QueryJoined<Blocklist, Movie>(builder, (blocklist, movie) =>
|
||||
{
|
||||
blocklist.Movie = movie;
|
||||
return blocklist;
|
||||
}).OrderByDescending(h => h.Date).ToList();
|
||||
}
|
||||
|
||||
public void DeleteForMovies(List<int> movieIds)
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace NzbDrone.Core.Configuration
|
||||
XDocument LoadConfigFile();
|
||||
Dictionary<string, object> GetConfigDictionary();
|
||||
void SaveConfigDictionary(Dictionary<string, object> configValues);
|
||||
void EnsureDefaultConfigFile();
|
||||
|
||||
string BindAddress { get; }
|
||||
int Port { get; }
|
||||
@@ -253,7 +254,7 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
public T GetValueEnum<T>(string key, T defaultValue, bool persist = true)
|
||||
{
|
||||
return (T)Enum.Parse(typeof(T), GetValue(key, defaultValue), persist);
|
||||
return (T)Enum.Parse(typeof(T), GetValue(key, defaultValue, persist), true);
|
||||
}
|
||||
|
||||
public string GetValue(string key, object defaultValue, bool persist = true)
|
||||
@@ -312,7 +313,7 @@ namespace NzbDrone.Core.Configuration
|
||||
SetValue(key, value.ToString().ToLower());
|
||||
}
|
||||
|
||||
private void EnsureDefaultConfigFile()
|
||||
public void EnsureDefaultConfigFile()
|
||||
{
|
||||
if (!File.Exists(_configFile))
|
||||
{
|
||||
|
||||
@@ -141,11 +141,11 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
if (decision.Rejections.Any())
|
||||
{
|
||||
_logger.Debug("Release rejected for the following reasons: {0}", string.Join(", ", decision.Rejections));
|
||||
_logger.Debug("Release '{0}' from '{1}' rejected for the following reasons: {2}", report.Title, report.Indexer, string.Join(", ", decision.Rejections));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("Release accepted");
|
||||
_logger.Debug("Release '{0}' from '{1}' accepted", report.Title, report.Indexer);
|
||||
}
|
||||
|
||||
yield return decision;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.CustomFormats;
|
||||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Queue;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
@@ -14,16 +16,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
private readonly IQueueService _queueService;
|
||||
private readonly UpgradableSpecification _upgradableSpecification;
|
||||
private readonly ICustomFormatCalculationService _formatService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public QueueSpecification(IQueueService queueService,
|
||||
UpgradableSpecification upgradableSpecification,
|
||||
ICustomFormatCalculationService formatService,
|
||||
IConfigService configService,
|
||||
Logger logger)
|
||||
{
|
||||
_queueService = queueService;
|
||||
_upgradableSpecification = upgradableSpecification;
|
||||
_formatService = formatService;
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -85,6 +90,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
return Decision.Reject("Another release is queued and the Quality profile does not allow upgrades");
|
||||
}
|
||||
|
||||
if (_upgradableSpecification.IsRevisionUpgrade(remoteMovie.ParsedMovieInfo.Quality, subject.ParsedMovieInfo.Quality))
|
||||
{
|
||||
if (_configService.DownloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade)
|
||||
{
|
||||
_logger.Debug("Auto downloading of propers is disabled");
|
||||
return Decision.Reject("Proper downloading is disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
|
||||
@@ -36,13 +36,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
if (qualityCompare > 0)
|
||||
{
|
||||
_logger.Debug("New item has a better quality");
|
||||
_logger.Debug("New item has a better quality. Existing: {0}. New: {1}", currentQuality, newQuality);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (qualityCompare < 0)
|
||||
{
|
||||
_logger.Debug("Existing item has better quality, skipping");
|
||||
_logger.Debug("Existing item has better quality, skipping. Existing: {0}. New: {1}", currentQuality, newQuality);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (downloadPropersAndRepacks != ProperDownloadTypes.DoNotPrefer &&
|
||||
qualityRevisionCompare > 0)
|
||||
{
|
||||
_logger.Debug("New item has a better quality revision, skipping. Existing: {0}. New: {1}", currentQuality, newQuality);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -63,19 +64,21 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
if (downloadPropersAndRepacks != ProperDownloadTypes.DoNotPrefer &&
|
||||
qualityRevisionCompare < 0)
|
||||
{
|
||||
_logger.Debug("Existing item has a better quality revision, skipping");
|
||||
_logger.Debug("Existing item has a better quality revision, skipping. Existing: {0}. New: {1}", currentQuality, newQuality);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newFormatScore <= currentFormatScore)
|
||||
{
|
||||
_logger.Debug("New item's custom formats [{0}] do not improve on [{1}], skipping",
|
||||
_logger.Debug("New item's custom formats [{0}] ({1}) do not improve on [{2}] ({3}), skipping",
|
||||
newCustomFormats.ConcatToString(),
|
||||
currentCustomFormats.ConcatToString());
|
||||
newFormatScore,
|
||||
currentCustomFormats.ConcatToString(),
|
||||
currentFormatScore);
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger.Debug("New item has a custom format upgrade");
|
||||
_logger.Debug("New item has a better custom format score");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -115,7 +118,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
return true;
|
||||
}
|
||||
|
||||
_logger.Debug("Existing item meets cut-off. skipping.");
|
||||
_logger.Debug("Existing item meets cut-off. skipping. Existing: {0}", currentQuality);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -124,9 +127,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
var compare = newQuality.Revision.CompareTo(currentQuality.Revision);
|
||||
|
||||
// Comparing the quality directly because we don't want to upgrade to a proper for a webrip from a webdl or vice versa
|
||||
if (currentQuality.Quality == newQuality.Quality && compare > 0)
|
||||
{
|
||||
_logger.Debug("New quality is a better revision for existing quality");
|
||||
_logger.Debug("New quality is a better revision for existing quality. Existing: {0}. New: {1}", currentQuality, newQuality);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,8 +88,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
if (!addHasSetShareLimits && setShareLimits)
|
||||
{
|
||||
Proxy.SetTorrentSeedingConfiguration(hash.ToLower(), remoteMovie.SeedConfiguration, Settings);
|
||||
|
||||
try
|
||||
{
|
||||
Proxy.SetTorrentSeedingConfiguration(hash.ToLower(), remoteMovie.SeedConfiguration, Settings);
|
||||
|
||||
@@ -239,8 +239,7 @@ namespace NzbDrone.Core.Download.Pending
|
||||
var movieReleases = GetPendingReleases(movieId);
|
||||
|
||||
return movieReleases.Select(r => r.RemoteMovie)
|
||||
.OrderByDescending(p => p.Release.AgeHours)
|
||||
.FirstOrDefault();
|
||||
.MaxBy(p => p.Release.AgeHours);
|
||||
}
|
||||
|
||||
private List<PendingRelease> GetPendingReleases()
|
||||
|
||||
@@ -37,9 +37,7 @@ namespace NzbDrone.Core.History
|
||||
|
||||
public MovieHistory MostRecentForDownloadId(string downloadId)
|
||||
{
|
||||
return FindByDownloadId(downloadId)
|
||||
.OrderByDescending(h => h.Date)
|
||||
.FirstOrDefault();
|
||||
return FindByDownloadId(downloadId).MaxBy(h => h.Date);
|
||||
}
|
||||
|
||||
public List<MovieHistory> FindByDownloadId(string downloadId)
|
||||
@@ -90,9 +88,7 @@ namespace NzbDrone.Core.History
|
||||
|
||||
public MovieHistory MostRecentForMovie(int movieId)
|
||||
{
|
||||
return Query(x => x.MovieId == movieId)
|
||||
.OrderByDescending(h => h.Date)
|
||||
.FirstOrDefault();
|
||||
return Query(x => x.MovieId == movieId).MaxBy(h => h.Date);
|
||||
}
|
||||
|
||||
public List<MovieHistory> Since(DateTime date, MovieHistoryEventType? eventType)
|
||||
|
||||
@@ -71,25 +71,25 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||
|
||||
public enum FileListCategories
|
||||
{
|
||||
[FieldOption]
|
||||
[FieldOption("Movies SD")]
|
||||
Movie_SD = 1,
|
||||
[FieldOption]
|
||||
[FieldOption("Movies DVD")]
|
||||
Movie_DVD = 2,
|
||||
[FieldOption]
|
||||
[FieldOption("Movies DVD-RO")]
|
||||
Movie_DVDRO = 3,
|
||||
[FieldOption]
|
||||
[FieldOption("Movies HD")]
|
||||
Movie_HD = 4,
|
||||
[FieldOption]
|
||||
[FieldOption("Movies HD-RO")]
|
||||
Movie_HDRO = 19,
|
||||
[FieldOption]
|
||||
Movie_BluRay = 20,
|
||||
[FieldOption]
|
||||
Movie_BluRay4K = 26,
|
||||
[FieldOption]
|
||||
Movie_3D = 25,
|
||||
[FieldOption]
|
||||
[FieldOption("Movies 4K")]
|
||||
Movie_4K = 6,
|
||||
[FieldOption]
|
||||
[FieldOption("Movies Blu-Ray")]
|
||||
Movie_BluRay = 20,
|
||||
[FieldOption("Movies 4K Blu-Ray")]
|
||||
Movie_BluRay4K = 26,
|
||||
[FieldOption("Movies 3D")]
|
||||
Movie_3D = 25,
|
||||
[FieldOption("XXX")]
|
||||
Xxx = 7
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
get
|
||||
{
|
||||
yield return GetDefinition("DOGnzb", GetSettings("https://api.dognzb.cr"));
|
||||
yield return GetDefinition("DrunkenSlug", GetSettings("https://api.drunkenslug.com"));
|
||||
yield return GetDefinition("DrunkenSlug", GetSettings("https://drunkenslug.com"));
|
||||
yield return GetDefinition("Nzb-Tortuga", GetSettings("https://www.nzb-tortuga.com"));
|
||||
yield return GetDefinition("Nzb.su", GetSettings("https://api.nzb.su"));
|
||||
yield return GetDefinition("NZBCat", GetSettings("https://nzb.cat"));
|
||||
|
||||
@@ -1064,5 +1064,6 @@
|
||||
"RssSyncHelpText": "الفاصل بالدقائق. اضبط على صفر للتعطيل (سيؤدي هذا إلى إيقاف كل عمليات الاستيلاء على التحرير التلقائي)",
|
||||
"AllCollectionsHiddenDueToFilter": "جميع الأفلام مخفية بسبب الفلتر المطبق.",
|
||||
"Collections": "مجموعة",
|
||||
"File": "الملفات"
|
||||
"File": "الملفات",
|
||||
"AnnouncedMsg": "تم اعلان الفيلم"
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@
|
||||
"ImportHeader": "Chcete-li přidat filmy do Radarru, importujte existující organizovanou knihovnu",
|
||||
"ImportListStatusCheckSingleClientMessage": "Seznamy nejsou k dispozici z důvodu selhání: {0}",
|
||||
"ChmodFolderHelpText": "Octal, aplikováno během importu / přejmenování na mediální složky a soubory (bez provádění bitů)",
|
||||
"AgeWhenGrabbed": "Věk (po uchopení)",
|
||||
"AgeWhenGrabbed": "Stáří (při zachycení)",
|
||||
"AreYouSureYouWantToDeleteThisImportListExclusion": "Opravdu chcete toto vyloučení importního seznamu smazat?",
|
||||
"ChmodFolderHelpTextWarning": "Funguje to pouze v případě, že je uživatel souboru Radarr vlastníkem souboru. Je lepší zajistit, aby stahovací klient správně nastavil oprávnění.",
|
||||
"ChmodGroup": "chmod Group",
|
||||
@@ -441,7 +441,7 @@
|
||||
"AnalyseVideoFiles": "Analyzujte video soubory",
|
||||
"Age": "Stáří",
|
||||
"Agenda": "Denní program",
|
||||
"All": "Všechno",
|
||||
"All": "Vše",
|
||||
"Analytics": "Analytics",
|
||||
"PackageVersion": "Verze balíčku",
|
||||
"AddMovie": "Přidat film",
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
"IndexerRssHealthCheckNoIndexers": "Da keine Indexer mit aktivierter RSS-Synchronisierung verfügbar sind, erfasst Radarr nicht automatisch neue Releases auf",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "Keine Indexer mit aktivierter automatischer Suche verfügbar. Radarr liefert keine automatischen Suchergebnisse",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "Alle suchfähigen Indexer sind aufgrund der kürzlichen Indexerfehler vorübergehend nicht verfügbar",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Keine Indexer mit aktivierter interaktiver Suche verfügbar, Radarr liefert keine interaktiven Suchergebnisse",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Keine Indexer mit aktivierter interaktiver Suche verfügbar, Radarr wird keine interaktiven Suchergebnisse liefern",
|
||||
"IndexerStatusCheckAllClientMessage": "Alle Indexer sind aufgrund von Fehlern nicht verfügbar",
|
||||
"IndexerStatusCheckSingleClientMessage": "Indexer aufgrund von Fehlern nicht verfügbar: {0}",
|
||||
"Indexers": "Indexer",
|
||||
@@ -101,7 +101,7 @@
|
||||
"QualityProfile": "Qualitätsprofil",
|
||||
"QualityProfiles": "Qualitätsprofile",
|
||||
"Queue": "Warteschlange",
|
||||
"RSSSync": "RSS Sync",
|
||||
"RSSSync": "RSS-Sync",
|
||||
"Refresh": "Aktualisieren",
|
||||
"RefreshAndScan": "Aktualisieren",
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "Zweig {0} ist kein gültiger Radarr-Release-Zweig. Sie erhalten keine Updates",
|
||||
@@ -223,9 +223,9 @@
|
||||
"RejectionCount": "Anzahl der Ablehnungen",
|
||||
"Peers": "Peers",
|
||||
"PageSize": "Einträge pro Seite",
|
||||
"OrganizeModalSuccess": "Fertig! Keine weiteren Dateien zum umbennenen.",
|
||||
"OrganizeModalSuccess": "Fertig! Keine weiteren Dateien zum Umbenennen.",
|
||||
"OrganizeModalNamingPattern": "Benennungsmuster:",
|
||||
"OrganizeModalDisabled": "Umbenennen ist deaktiviert, nichts zum umbennenen",
|
||||
"OrganizeModalDisabled": "Umbenennen ist deaktiviert, nichts zum Umbenennen",
|
||||
"OrganizeModalAllPathsRelative": "Alle Pfade sind relativ zu:",
|
||||
"OrganizeAndRename": "Organisieren & Umbenennen",
|
||||
"Organize": "Organisieren",
|
||||
@@ -327,14 +327,14 @@
|
||||
"AvailabilityDelayHelpText": "Zeit vor( - ) oder nach( + ) dem Verfügbarkeitsdatum für die Suche nach einem Film",
|
||||
"BackupIntervalHelpText": "Intervall zum sichern der Datenbank und Einstellungen",
|
||||
"BackupRetentionHelpText": "Automatische Backups, die älter als die Aufbewahrungsfrist sind, werden automatisch gelöscht",
|
||||
"Backups": "Backups",
|
||||
"Backups": "Sicherungen",
|
||||
"BindAddress": "Adresse binden",
|
||||
"BindAddressHelpText": "Gültige IP Adresse oder \"*\" für alle Netzwerke",
|
||||
"Branch": "Git-Branch",
|
||||
"BypassProxyForLocalAddresses": "Proxy für lokale Adressen umgehen",
|
||||
"CertificateValidation": "Zertifikat Validierung",
|
||||
"CertificateValidationHelpText": "Ändere wie streng die Validierung der HTTPS-Zertifizierung ist. Ändern Sie nicht wenn Ihnen die Risiken nicht bewusst sind.",
|
||||
"CertificationCountry": "Zertifizierungs Land",
|
||||
"CertificationCountry": "Zertifizierungsland",
|
||||
"ChangeFileDate": "Erstelldatum der Datei anpassen",
|
||||
"ChangeHasNotBeenSavedYet": "Änderung wurde noch nicht gespeichert",
|
||||
"CheckForFinishedDownloadsInterval": "Intervall zum prüfen von fertigen Downloads",
|
||||
@@ -415,7 +415,7 @@
|
||||
"LanguageHelpText": "Sprache für Releases",
|
||||
"Links": "Links",
|
||||
"ListSettings": "Listen Einstellungen",
|
||||
"ListSyncLevelHelpText": "Filme in deiner Mediathek werden gelöscht oder nicht weiter beobachtet wenn sie nicht in der Liste sind",
|
||||
"ListSyncLevelHelpText": "Die Filme in der Bibliothek werden auf der Grundlage Ihrer Auswahl behandelt, wenn sie nicht auf Ihrer Liste auftauchen",
|
||||
"ListUpdateInterval": "Aktualisierungs Intervall der Listen",
|
||||
"Local": "Lokal",
|
||||
"LogLevel": "Log Level",
|
||||
@@ -531,7 +531,7 @@
|
||||
"TestAllIndexers": "Alle testen",
|
||||
"TestAllLists": "Alle testen",
|
||||
"TimeFormat": "Zeitformat",
|
||||
"UpdateMechanismHelpText": "Benutze Radarr's Build-In Updater oder ein Script",
|
||||
"UpdateMechanismHelpText": "Benutze den in Radarr eingebauten Updater oder ein Script",
|
||||
"UpdateScriptPathHelpText": "Pfad zu einem benutzerdefinierten Skript, das ein extrahiertes Update-Paket übernimmt und den Rest des Update-Prozesses abwickelt",
|
||||
"UpgradeAllowedHelpText": "Wenn deaktiviert wird die Qualität nicht verbessert",
|
||||
"Uptime": "Laufzeit",
|
||||
@@ -587,7 +587,7 @@
|
||||
"ImportExtraFilesHelpText": "Importiere zutreffende Extra Dateien (Untertitel, nfo, etc.) nach dem Importieren einer Filmdatei",
|
||||
"PreferIndexerFlags": "Bevorzugte Indexer Flags",
|
||||
"AreYouSureYouWantToResetYourAPIKey": "Bist du sicher, dass du den API-Schlüssel zurücksetzen willst?",
|
||||
"CopyUsingHardlinksHelpText": "Hardlinks erstellen wenn Torrents die noch geseeded werden kopiert werden sollen",
|
||||
"CopyUsingHardlinksHelpText": "Hardlinks erlauben es Radarr, Torrents zu importieren die derzeit geseeded werden, ohne dabei weiteren Speicherplatz zu belegen oder die Datei vollständig zu kopieren. Hardlinks funktionieren nur, wenn sich die Quelle und das Ziel auf dem selben Volume befinden",
|
||||
"IncludeUnknownMovieItemsHelpText": "Einträge ohne eine Zuordnung in der Warteschlange anzeigen. Dies könnten gelöschte Filme oder alles andere mit Radarrs Downloadkategorie sein",
|
||||
"PriorityHelpText": "Priorisiere mehrere Downloader. Rundlauf-Verfahren wird für Downloader mit der gleichen Priorität verwendet.",
|
||||
"SearchForMovie": "Film suchen",
|
||||
@@ -830,7 +830,7 @@
|
||||
"ChmodFolderHelpText": "Oktal, wird beim Importieren/Umbenennen von Medienordnern und Dateien angewendet (ohne Ausführungsbits)",
|
||||
"ChmodFolder": "chmod Ordner",
|
||||
"FileNameTokens": "Dateinamen Teile",
|
||||
"YesMoveFiles": "Ja, Dateien verchieben",
|
||||
"YesMoveFiles": "Ja, Dateien verschieben",
|
||||
"WouldYouLikeToRestoreBackup": "Willst du das Backup {0} wiederherstellen?",
|
||||
"Wiki": "Wiki",
|
||||
"WhatsNew": "Was gibt's Neues?",
|
||||
@@ -904,7 +904,7 @@
|
||||
"PhysicalReleaseDate": "Disc Veröffentlichungsdatum",
|
||||
"OrganizeSelectedMovies": "Ausgewählte Filme organisieren",
|
||||
"OrganizeConfirm": "Bist du sicher, dass du alle Datien der {0} ausgewählten Filme organisieren willst?",
|
||||
"OnRename": "Bei Umbennenung",
|
||||
"OnRename": "Bei Umbenennen",
|
||||
"OnlyUsenet": "Nur Usenet",
|
||||
"OnlyTorrent": "Nur Torrents",
|
||||
"OnLatestVersion": "Die aktuellste Version ist bereits installiert",
|
||||
@@ -1024,7 +1024,7 @@
|
||||
"Add": "Hinzufügen",
|
||||
"RequiredRestrictionHelpText": "Das Release mus mindesten eines der Begriffe beinhalten ( Groß-/Kleinschreibung wird nicht beachtet )",
|
||||
"MoveFolders2": "Bist du sicher, dass du die Filmdateien von '{0}' nach '{1}' verschieben willst ?",
|
||||
"ImportLibrary": "Mediathek Import",
|
||||
"ImportLibrary": "Importieren",
|
||||
"ImportNotForDownloads": "Benutze dies NICHT um abgeschlossene Downloads deines Download Clients hinzuzufügen. Dies ist NUR für vorhandene organisierte Mediatheken und nicht für unsortierte Dateien.",
|
||||
"SqliteVersionCheckUpgradeRequiredMessage": "Die derzeit installierte SQLite-Version {0} wird nicht mehr unterstützt. Bitte aktualisieren Sie SQLite auf mindestens Version {1}.",
|
||||
"ShowCinemaRelease": "Erscheinungsdatum des Kinos anzeigen",
|
||||
@@ -1156,5 +1156,6 @@
|
||||
"SettingsTheme": "Design",
|
||||
"RSSHelpText": "Wird benutzt, wenn Radarr mittels RSS-Sync regelmäßig nach Releases schaut",
|
||||
"DownloadClientSortingCheckMessage": "Downloader {0} hat die {1} Sortierung für Radarrs Kategorie aktiviert. Dies sollte deaktiviert werden, um Import-Probleme zu vermeiden.",
|
||||
"File": "Datei"
|
||||
"File": "Datei",
|
||||
"UMask": "UMask"
|
||||
}
|
||||
|
||||
@@ -1156,5 +1156,6 @@
|
||||
"Waiting": "Αναμονή",
|
||||
"UpdateAvailable": "Νέα ενημέρωση είναι διαθέσιμη",
|
||||
"SizeLimit": "Όριο μεγέθους",
|
||||
"File": "Αρχεία"
|
||||
"File": "Αρχεία",
|
||||
"UMask": "UMask"
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@
|
||||
"ConnectSettingsSummary": "Notifications, connections to media servers/players, and custom scripts",
|
||||
"ConsideredAvailable": "Considered Available",
|
||||
"CopyToClipboard": "Copy to Clipboard",
|
||||
"CopyUsingHardlinksHelpText": "Use Hardlinks when trying to copy files from torrents that are still being seeded",
|
||||
"CopyUsingHardlinksHelpText": "Hardlinks allow Radarr to import seeding torrents to the movie folder without taking extra disk space or copying the entire contents of the file. Hardlinks will only work if the source and destination are on the same volume",
|
||||
"CopyUsingHardlinksHelpTextWarning": "Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use Radarr's rename function as a work around.",
|
||||
"CouldNotConnectSignalR": "Could not connect to SignalR, UI won't update",
|
||||
"CouldNotFindResults": "Couldn't find any results for '{0}'",
|
||||
@@ -483,7 +483,7 @@
|
||||
"Lists": "Lists",
|
||||
"ListSettings": "List Settings",
|
||||
"ListsSettingsSummary": "Import Lists, list exclusions",
|
||||
"ListSyncLevelHelpText": "Movies in library will be removed or unmonitored if not in your list",
|
||||
"ListSyncLevelHelpText": "Movies in library will handled based on your selection if they fall off or do not appear on your list(s)",
|
||||
"ListSyncLevelHelpTextWarning": "Movie files will be permanently deleted, this can result in wiping your library if your lists are empty",
|
||||
"ListTagsHelpText": "Tags list items will be added with",
|
||||
"ListUpdateInterval": "List Update Interval",
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
"ListsSettingsSummary": "Importer listes, listes d'exclusions",
|
||||
"ListExclusions": "Liste des exclusions",
|
||||
"IndexersSettingsSummary": "Indexeurs et restrictions de version",
|
||||
"Grabbed": "Attrapé",
|
||||
"Grabbed": "Récupéré",
|
||||
"Genres": "Genres",
|
||||
"Forecast": "Prévision",
|
||||
"DownloadClientsSettingsSummary": "Clients de Téléchargement, la gestion des téléchargements et les mappages de chemins d'accès à distance",
|
||||
@@ -582,7 +582,7 @@
|
||||
"UsenetDelayHelpText": "Délai en minutes avant de récupérer une version Usenet",
|
||||
"Uptime": "Durée de fonctionnent",
|
||||
"UpgradeUntilThisQualityIsMetOrExceeded": "Mettre à niveau jusqu'à ce que cette qualité soit atteinte ou dépassée",
|
||||
"UpgradeAllowedHelpText": "Ne sera pas mis à jour si la qualité est désactivée",
|
||||
"UpgradeAllowedHelpText": "Si désactivé, la qualité ne sera pas améliorée",
|
||||
"UpdateScriptPathHelpText": "Chemin vers un script personnalisé qui prend un package de mise à jour extraite et gère le reste du processus de mise à jour",
|
||||
"UpdateMechanismHelpText": "Utiliser le programme de mise à jour intégré de Radarr ou un script",
|
||||
"UpdateAutomaticallyHelpText": "Télécharger et installer automatiquement les mises à jour. Vous pourrez toujours installer à partir de System : Updates",
|
||||
@@ -671,7 +671,7 @@
|
||||
"ShowCutoffUnmetIconHelpText": "Afficher l'icône des fichiers lorsque la limite n'a pas été atteinte",
|
||||
"ShowCertification": "Afficher la certification",
|
||||
"ShowAsAllDayEvents": "Afficher comme événements d'une journée entière",
|
||||
"ShouldMonitorHelpText": "Les films ou les collections ajoutés par cette liste doivent-ils être ajoutés en tant que surveillés",
|
||||
"ShouldMonitorHelpText": "Les films ou les collections ajoutés par cette liste devraient-ils être ajoutés en tant que surveillés",
|
||||
"SettingsRuntimeFormat": "Format de durée",
|
||||
"SetPermissionsLinuxHelpTextWarning": "Si vous ne savez pas ce que font ces paramètres, ne les modifiez pas.",
|
||||
"SetPermissionsLinuxHelpText": "Chmod doit-il être exécuté lorsque les fichiers sont importés/renommés ?",
|
||||
@@ -1117,7 +1117,7 @@
|
||||
"Collections": "Collections",
|
||||
"MonitorMovies": "Surveiller les films",
|
||||
"NoCollections": "Aucune collection n'a été trouvée. Pour commencer, vous devez ajouter un nouveau film ou importer des films existants",
|
||||
"RssSyncHelpText": "Intervalle en minutes. Mettez la valeur zéro pour désactiver (cela arrêtera tous les téléchargements automatiques de version)",
|
||||
"RssSyncHelpText": "Intervalle en minutes. Régler à zéro pour désactiver (cela arrêtera tous les déclenchements automatiques).",
|
||||
"CollectionsSelectedInterp": "{0} Collections(s) Sélectionnée(s)",
|
||||
"ChooseImportMode": "Mode d'importation",
|
||||
"CollectionOptions": "Options de collection",
|
||||
@@ -1156,5 +1156,6 @@
|
||||
"ResetTitlesHelpText": "Réinitialiser les titres des définitions ainsi que les valeurs",
|
||||
"SettingsThemeHelpText": "Changez le thème de l'interface de l'application. Le thème \"Auto\" utilisera celui de votre système d'exploitation pour définir le mode clair ou foncé. Inspiré par Theme.Park",
|
||||
"ShowPosters": "Montre les affiches",
|
||||
"File": "Fichier"
|
||||
"File": "Fichier",
|
||||
"UMask": "UMask"
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
"BackupIntervalHelpText": "מרווח בין גיבויים אוטומטיים",
|
||||
"BackupRetentionHelpText": "גיבויים אוטומטיים ישנים יותר מתקופת השמירה ינוקו אוטומטית",
|
||||
"BindAddress": "כתובת איגוד",
|
||||
"BindAddressHelpText": "כתובת IP4 תקפה או '*' לכל הממשקים",
|
||||
"BindAddressHelpText": "יש להזין localhost, כתובת IP או '*' כדי לאפשר הכל",
|
||||
"Branch": "ענף",
|
||||
"BuiltIn": "נִבנָה בְּ",
|
||||
"CalendarOptions": "אפשרויות לוח שנה",
|
||||
@@ -530,7 +530,7 @@
|
||||
"DestinationRelativePath": "מסלול יחסי יעד",
|
||||
"DetailedProgressBar": "סרגל התקדמות מפורט",
|
||||
"Details": "פרטים",
|
||||
"Discord": "מַחֲלוֹקֶת",
|
||||
"Discord": "דיסקורד",
|
||||
"Discover": "לְגַלוֹת",
|
||||
"DiskSpace": "שטח דיסק",
|
||||
"Docker": "דוקר",
|
||||
|
||||
@@ -227,5 +227,26 @@
|
||||
"ChmodGroup": "chmod Grupa",
|
||||
"ChmodGroupHelpText": "Naziv grupe ili gid. Koristite gid za udaljene sustave datoteka.",
|
||||
"ChooseAnotherFolder": "Odaberite Drugu Mapu",
|
||||
"ChooseImportMode": "Odaberite Način Uvoza"
|
||||
"ChooseImportMode": "Odaberite Način Uvoza",
|
||||
"AppDataLocationHealthCheckMessage": "Ažuriranje ne može spriječiti brisanje AppData pri ažuriranju",
|
||||
"CleanLibraryLevel": "Očisti Razinu Biblioteke",
|
||||
"ClickToChangeLanguage": "Klikni za promjenu jezika",
|
||||
"ClickToChangeMovie": "Klikni za promjenu filma",
|
||||
"ClickToChangeQuality": "Klikni za promjenu kvalitete",
|
||||
"ClickToChangeReleaseGroup": "Klikni za promjenu grupe verzije",
|
||||
"ClientPriority": "Prioritet Klijenata",
|
||||
"CloneCustomFormat": "Kloniraj Prilagođeni Format",
|
||||
"CloneFormatTag": "Kloniraj Oznaku Formata",
|
||||
"CloneIndexer": "Kloniraj Indekser",
|
||||
"CloneProfile": "Kloniraj Profil",
|
||||
"Close": "Zatvori",
|
||||
"CloseCurrentModal": "Zatvori Trenutni Modal",
|
||||
"CollectionOptions": "Opcije Kolekcije",
|
||||
"CollectionShowDetailsHelpText": "Prikaži status i svojstva kolekcije",
|
||||
"CollectionShowOverviewsHelpText": "Prikaži pregled kolekcije",
|
||||
"CollectionShowPostersHelpText": "Prikaži Kolekciju postera",
|
||||
"CollectionsSelectedInterp": "{0} Kolekcija odabrano",
|
||||
"ColonReplacement": "Zamjena Zareza",
|
||||
"ColonReplacementFormatHelpText": "izmijeni kako Radar upravlja zamjenama zareza",
|
||||
"Columns": "Stupci"
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@
|
||||
"Cancel": "Annulla",
|
||||
"BypassProxyForLocalAddresses": "Evita il Proxy per gli Indirizzi Locali",
|
||||
"Branch": "Ramo",
|
||||
"BindAddressHelpText": "Indirizzo IPV4 valido o '*' per tutte le interfacce di rete",
|
||||
"BindAddressHelpText": "Indirizzo IP valido, localhost o '*' per tutte le interfacce di rete",
|
||||
"BindAddress": "Indirizzo di Bind",
|
||||
"Backups": "I Backup",
|
||||
"BackupRetentionHelpText": "I backup automatici più vecchi del periodo di conservazione verranno eliminati automaticamente",
|
||||
@@ -845,7 +845,7 @@
|
||||
"AddDownloadClient": "Aggiungi Downloader",
|
||||
"AddCustomFormat": "Aggiungi Formato Personalizzato",
|
||||
"Add": "Aggiungi",
|
||||
"ImportLibrary": "Importazione della libreria",
|
||||
"ImportLibrary": "Importazione libreria",
|
||||
"CustomFormatHelpText": "Radarr valuta ogni release usando la somma dei punteggi dei corrispondenti formati personalizzati. Se una nuova versione migliorasse il punteggio, con una qualità uguale o migliore, Radarr lo prenderà.",
|
||||
"DefaultDelayProfile": "Questo è il profilo predefinito. Si applica a tutti i film che non hanno un profilo esplicito.",
|
||||
"EditCustomFormat": "Modifica Formato Personalizzato",
|
||||
@@ -948,9 +948,9 @@
|
||||
"LookingForReleaseProfiles1": "Cerchi profili di rilascio? Prova",
|
||||
"LookingForReleaseProfiles2": "piuttosto.",
|
||||
"LowerCase": "Minuscolo",
|
||||
"ManualImportSelectLanguage": "Importazione manuale: seleziona Lingua",
|
||||
"ManualImportSelectLanguage": "Importazione Manuale: Seleziona Lingua",
|
||||
"ManualImportSelectMovie": "Importazione manuale: seleziona Film",
|
||||
"ManualImportSelectQuality": " Importazione manuale: seleziona Qualità",
|
||||
"ManualImportSelectQuality": " Importazione Manuale: Seleziona Qualità",
|
||||
"MegabytesPerMinute": "Megabyte al minuto",
|
||||
"Min": "Min",
|
||||
"MinimumCustomFormatScore": "Punteggio formato personalizzato minimo",
|
||||
@@ -1108,5 +1108,13 @@
|
||||
"EditCollection": "Modifica Connessione",
|
||||
"SettingsThemeHelpText": "Cambia il Tema dell'interfaccia dell’applicazione, il Tema 'Auto' userà il suo Tema di Sistema per impostare la modalità Chiara o Scura. Ispirato da {0}",
|
||||
"PreferredProtocol": "Protocollo Preferito",
|
||||
"File": "File"
|
||||
"File": "File",
|
||||
"SettingsTheme": "Tema",
|
||||
"ShowCollectionDetails": "Mostra stato collezione",
|
||||
"Waiting": "In attesa",
|
||||
"CollectionsSelectedInterp": "{0} Collezione(i) Selezionate",
|
||||
"CollectionShowOverviewsHelpText": "Mostra panoramica della collezione",
|
||||
"ImdbVotes": "Voti IMDb",
|
||||
"ImdbRating": "Valutazione IMDb",
|
||||
"DownloadClientSortingCheckMessage": "Il client di download {0} ha l'ordinamento {1} abilitato per la categoria di Radarr. Dovresti disabilitare l'ordinamento nel tuo client di download per evitare problemi di importazione."
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
"AreYouSureYouWantToDeleteThisRemotePathMapping": "Er du sikker på at du vil slette denne eksterne banetilordningen?",
|
||||
"AutoRedownloadFailedHelpText": "Søk etter eller prøv å laste ned en annen versjon automatisk",
|
||||
"BackupFolderHelpText": "Relative stier vil være under Radarr's AppData -katalog",
|
||||
"BindAddressHelpText": "Gyldig IPv4 -adresse eller \"*\" for alle grensesnitt",
|
||||
"BindAddressHelpText": "Gyldig IPv4 -adresse, localhost eller \"*\" for alle grensesnitt",
|
||||
"BypassDelayIfHighestQuality": "Omgå hvis høyeste kvalitet",
|
||||
"BypassDelayIfHighestQualityHelpText": "Omgåelsesforsinkelse når utgivelsen har den høyeste aktiverte kvaliteten i kvalitetsprofilen med den foretrukne protokollen",
|
||||
"ChangeHasNotBeenSavedYet": "Endringen er ikke lagret ennå",
|
||||
@@ -256,5 +256,9 @@
|
||||
"New": "Ny",
|
||||
"RemotePathMappings": "Ekstern portmapping",
|
||||
"Languages": "språk",
|
||||
"File": "Fil"
|
||||
"File": "Fil",
|
||||
"ApplicationURL": "Applikasjon URL",
|
||||
"ApplicationUrlHelpText": "Denne applikasjonens eksterne URL inkludert http(s)://, port og URL base",
|
||||
"CollectionOptions": "Innstillinger for Samling",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Er du sikker på at du vil nullstille kvalitetsdefinisjonene?"
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@
|
||||
"IndexerStatusCheckSingleClientMessage": "Indexadores indisponíveis devido a falhas: {0}",
|
||||
"IndexerStatusCheckAllClientMessage": "Todos os indexadores estão indisponíveis devido a falhas",
|
||||
"IndexersSettingsSummary": "Restrições de versões e de indexadores",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa Interativa ativada, o Radarr não fornecerá nenhum resultado nas Pesquisas Interativas",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa Interativa ativada. O Radarr não fornecerá nenhum resultado nas Pesquisas Interativas",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "Não há indexadores disponíveis com Pesquisa automática ativada, o Radarr não fornecerá nenhum resultado de pesquisa automática",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "Todos os indexadores com funcionalidade de pesquisa estão indisponíveis devido a erros recentes do indexador",
|
||||
"Indexers": "Indexadores",
|
||||
@@ -486,7 +486,7 @@
|
||||
"CertificateValidation": "Validação de certificado",
|
||||
"BypassProxyForLocalAddresses": "Ignorar proxy para endereços locais",
|
||||
"Branch": "Ramificação",
|
||||
"BindAddressHelpText": "Endereço IPv4 válido ou \"*\" para todas as interfaces",
|
||||
"BindAddressHelpText": "Endereço de IP válido, localhost ou \"*\" para todas as interfaces",
|
||||
"BindAddress": "Endereço de vínculo",
|
||||
"Backups": "Cópias de segurança",
|
||||
"BackupFolderHelpText": "Caminhos relativos estarão na pasta AppData do Radarr",
|
||||
@@ -635,7 +635,7 @@
|
||||
"Disabled": "Desativado",
|
||||
"DeleteTagMessageText": "Tem a certeza que quer eliminar a etiqueta \"{0}\"?",
|
||||
"DeleteSelectedMovieFilesMessage": "Tem a certeza que quer eliminar os ficheiros do filme selecionado?",
|
||||
"DeleteRestrictionHelpText": "Tem a certeza que quer eliminar esta restrição?",
|
||||
"DeleteRestrictionHelpText": "Tem certeza de que quer eliminar esta restrição?",
|
||||
"DeleteNotificationMessageText": "Tem a certeza que quer eliminar a notificação \"{0}\"?",
|
||||
"DeleteListMessageText": "Tem a certeza que quer eliminar a lista \"{0}\"?",
|
||||
"DeleteDownloadClientMessageText": "Tem a certeza que quer eliminar o cliente de transferências \"{0}\"?",
|
||||
@@ -897,7 +897,7 @@
|
||||
"OnRename": "Ao renomear",
|
||||
"PreferTorrent": "Preferir torrent",
|
||||
"PreferUsenet": "Preferir Usenet",
|
||||
"QualitiesHelpText": "Qualidades mais acima na lista têm maior prioridade, as que estão no mesmo grupo têm prioridade igual. Apenas qualidades verificadas são desejadas",
|
||||
"QualitiesHelpText": "Qualidades mais acima na lista têm maior prioridade, mesmo que não sejam verificadas. As que estão no mesmo grupo têm prioridade igual. Apenas qualidades verificadas são desejadas",
|
||||
"QualityProfileInUse": "Não é possível eliminar um perfil de qualidade quando associado a um filme, lista ou coleção",
|
||||
"QueueIsEmpty": "A fila está vazia",
|
||||
"RadarrCalendarFeed": "Feed de calendário do Radarr",
|
||||
@@ -1146,5 +1146,16 @@
|
||||
"MovieCollectionMultipleMissingRoots": "Faltam várias pastas base para as coleções de filmes: {0}",
|
||||
"MovieOnly": "Apenas filme",
|
||||
"ShowCollectionDetails": "Mostrar estado da coleção",
|
||||
"File": "Ficheiro"
|
||||
"File": "Ficheiro",
|
||||
"ResetDefinitions": "Redefinir Definições",
|
||||
"ResetQualityDefinitions": "Redefinir Qualidade de Definições",
|
||||
"ResetTitles": "Redefinir Títulos",
|
||||
"ResetTitlesHelpText": "Redefinir títulos de definição, bem como valores",
|
||||
"SettingsThemeHelpText": "Alterar o tema da interface do usuário. O tema 'Auto' usará o tema do sistema operacional para definir o modo Claro ou Escuro. Inspirado por Theme.Park",
|
||||
"UMask": "UMask",
|
||||
"RSSHelpText": "Será usado quando o Radarr procurar periodicamente releases via RSS Sync",
|
||||
"SettingsTheme": "Tema",
|
||||
"AreYouSureYouWantToResetQualityDefinitions": "Tem certeza de que deseja redefinir as definições de qualidade?",
|
||||
"DownloadClientSortingCheckMessage": "O cliente de download {0} tem {1} classificação habilitada para a categoria do Radarr. Você deve desativar a classificação em seu cliente de download para evitar problemas de importação.",
|
||||
"PreferredProtocol": "Protocolo Preferido"
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"IndexerStatusCheckAllClientMessage": "Todos os indexadores estão indisponíveis devido a falhas",
|
||||
"IndexersSettingsSummary": "Indexadores e restrições de lançamento",
|
||||
"IndexerSettings": "Configurações do indexador",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa Interativa habilitada, o Radarr não fornecerá nenhum resultado para a pesquisa interativa",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "Nenhum indexador disponível com a Pesquisa interativa habilitada, o Radarr não fornecerá nenhum resultado de pesquisa interativa",
|
||||
"IndexerSearchCheckNoAvailableIndexersMessage": "Todos os indexadores com capacidade de pesquisa estão temporariamente indisponíveis devido a erros recentes do indexador",
|
||||
"IndexerSearchCheckNoAutomaticMessage": "Nenhum indexador disponível com a Pesquisa automática habilitada, o Radarr não fornecerá nenhum resultado de pesquisa automática",
|
||||
"Indexers": "Indexadores",
|
||||
@@ -84,7 +84,7 @@
|
||||
"HealthNoIssues": "Nenhum problema com sua configuração",
|
||||
"Health": "Integridade",
|
||||
"HaveNotAddedMovies": "Você ainda não adicionou nenhum filme, deseja importar alguns ou todos os seus filmes primeiro?",
|
||||
"HardlinkCopyFiles": "Hardlink/Copiar arquivos",
|
||||
"HardlinkCopyFiles": "Vínculo real/Copiar arquivos",
|
||||
"Group": "Grupo",
|
||||
"GrabSelected": "Obter selecionado",
|
||||
"GrabReleaseMessageText": "O Radarr não conseguiu determinar a qual filme este lançamento está relacionado. O Radarr pode não conseguir importar automaticamente este lançamento. Quer obter \"{0}\"?",
|
||||
@@ -282,7 +282,7 @@
|
||||
"CouldNotFindResults": "Não foi possível encontrar resultados para \"{0}\"",
|
||||
"CouldNotConnectSignalR": "Não é possível conectar ao SignalR, a interface não atualizará",
|
||||
"CopyUsingHardlinksHelpTextWarning": "Ocasionalmente, os bloqueios de arquivo podem impedir a renomeação de arquivos que estão sendo semeados. Você pode desabilitar temporariamente a semeadura e usar a função de renomeação do Radarr como uma solução alternativa.",
|
||||
"CopyUsingHardlinksHelpText": "Usar vínculos reais ao tentar copiar arquivos de torrents que ainda estão sendo semeados",
|
||||
"CopyUsingHardlinksHelpText": "Os hardlinks permitem que o Radarr importe torrents de propagação para a pasta do filme sem ocupar espaço extra em disco ou copiar todo o conteúdo do arquivo. Hardlinks só funcionarão se a origem e o destino estiverem no mesmo volume",
|
||||
"CopyToClipboard": "Copiar para área de transferência",
|
||||
"ConsideredAvailable": "Considerado disponível",
|
||||
"ConnectSettingsSummary": "Notificações, conexões com servidores/reprodutores de mídia, e scripts personalizados",
|
||||
@@ -449,7 +449,7 @@
|
||||
"ListUpdateInterval": "Intervalo de atualização da lista",
|
||||
"ListTagsHelpText": "Os itens na lista de tags serão adicionados com",
|
||||
"ListSyncLevelHelpTextWarning": "Os arquivos de filme serão excluídos permanentemente, o que pode resultar na limpeza de sua biblioteca se suas listas estiverem vazias",
|
||||
"ListSyncLevelHelpText": "Os filmes na biblioteca serão removidos ou deixarão de ser monitorados se não estiverem na sua lista",
|
||||
"ListSyncLevelHelpText": "Os filmes na biblioteca serão tratados com base na sua seleção se eles caírem ou não aparecerem na(s) sua(s) lista(s)",
|
||||
"ListsSettingsSummary": "Listas de importação, exclusões de lista",
|
||||
"ListSettings": "Configurações de listas",
|
||||
"Lists": "Listas",
|
||||
@@ -693,7 +693,7 @@
|
||||
"ShowDateAdded": "Mostrar data de adição",
|
||||
"ShowCutoffUnmetIconHelpText": "Mostrar ícone para arquivos quando o limite não foi atingindo",
|
||||
"ShowCertification": "Mostrar certificação",
|
||||
"ShowAdvanced": "Mostrar avançado",
|
||||
"ShowAdvanced": "Mostrar opções avançadas",
|
||||
"ShouldMonitorHelpText": "Os filmes ou coleções adicionados por esta lista devem ser adicionados como monitorados",
|
||||
"SettingsWeekColumnHeader": "Cabeçalho da coluna da semana",
|
||||
"SettingsTimeFormat": "Formato de hora",
|
||||
@@ -1083,8 +1083,8 @@
|
||||
"RemoveFailed": "Falha na Remoção",
|
||||
"RemoveCompleted": "Remover Completos",
|
||||
"RemoveDownloadsAlert": "As configurações de remoção foram movidas para configurações individuais do cliente na tabela acima.",
|
||||
"OnApplicationUpdate": "Na Atualização do Aplicativo",
|
||||
"OnApplicationUpdateHelpText": "Na Atualização do Aplicativo",
|
||||
"OnApplicationUpdate": "Ao atualizar o aplicativo",
|
||||
"OnApplicationUpdateHelpText": "Ao atualizar o aplicativo",
|
||||
"DiscordUrlInSlackNotification": "Você tem uma notificação do Discord configurado como uma notificação do Slack. Definir isso como uma notificação do Discord para melhor funcionalidade. Com efeito, notificações são: {0}",
|
||||
"AnnouncedMsg": "Filme foi anunciado",
|
||||
"IndexerDownloadClientHelpText": "Especificar qual cliente de download é utilizado para baixar a partir deste indexador",
|
||||
@@ -1096,7 +1096,7 @@
|
||||
"ClickToChangeReleaseGroup": "Clique para alterar o grupo do lançamento",
|
||||
"Filters": "Filtros",
|
||||
"RemotePath": "Caminho Remoto",
|
||||
"SizeLimit": "Tamanho Limite",
|
||||
"SizeLimit": "Tamanho limite",
|
||||
"ImdbRating": "Avaliação no IMDb",
|
||||
"TmdbRating": "Avaliação no TMDb",
|
||||
"TmdbVotes": "Votos no TMDb",
|
||||
@@ -1115,7 +1115,7 @@
|
||||
"RefreshMonitoredIntervalHelpText": "Com que frequência atualizar downloads monitorados de clientes de download, no mínimo 1 minuto",
|
||||
"RssSyncHelpText": "Intervalo em minutos. Defina como zero para desabilitar (isso interromperá todas as capturas de liberação automática)",
|
||||
"InstanceName": "Nome da instância",
|
||||
"InstanceNameHelpText": "Nome da instância na guia e para o nome do aplicativo Syslog",
|
||||
"InstanceNameHelpText": "Nome da instância na guia e para o nome do aplicativo do Syslog",
|
||||
"AllCollectionsHiddenDueToFilter": "Todos os filmes estão ocultos devido ao filtro aplicado.",
|
||||
"Collections": "Coleções",
|
||||
"MonitorMovies": "Monitorar Filmes",
|
||||
|
||||
@@ -420,7 +420,7 @@
|
||||
"EnableInteractiveSearchHelpText": "当手动搜索启用时使用",
|
||||
"EnableInteractiveSearch": "启用手动搜索",
|
||||
"EnabledHelpText": "在 Radarr 中启用该列表",
|
||||
"EnableCompletedDownloadHandlingHelpText": "自动从下载客户端导入已下载的歌曲",
|
||||
"EnableCompletedDownloadHandlingHelpText": "自动从下载客户端导入已完成的下载内容",
|
||||
"Enabled": "已启用",
|
||||
"EnableColorImpairedMode": "启用色障模式",
|
||||
"EnableAutomaticSearchHelpTextWarning": "当手动搜索启用时使用",
|
||||
@@ -452,7 +452,7 @@
|
||||
"CreateGroup": "创建组",
|
||||
"CreateEmptyMovieFolders": "为影片创建空文件夹",
|
||||
"CouldNotConnectSignalR": "无法连接至SignalR,不会升级UI",
|
||||
"CopyUsingHardlinksHelpText": "拷贝文件时torrents文件还在做种中则使用硬链接",
|
||||
"CopyUsingHardlinksHelpText": "硬链接 (Hardlinks) 允许 Radarr 将还在做种中的文件导入到影片文件夹中而不占用额外的存储空间或者复制文件的全部内容。硬链接 (Hardlinks) 仅能在源文件和目标文件在同一磁盘卷中使用",
|
||||
"ConnectSettingsSummary": "通知、与媒体服务器/播放器的链接、自定义脚本",
|
||||
"DownloadClientSettings": "下载客户端设置",
|
||||
"DownloadClients": "下载客户端",
|
||||
@@ -569,7 +569,7 @@
|
||||
"AddingTag": "添加标签",
|
||||
"AddImportExclusionHelpText": "防止列表中的电影被添加到 Radarr 中",
|
||||
"AddExclusion": "添加例外",
|
||||
"AddedToDownloadQueue": "已添加到已下载队列",
|
||||
"AddedToDownloadQueue": "已添加到下载队列",
|
||||
"Added": "已添加",
|
||||
"Actions": "操作",
|
||||
"ImportListSyncIntervalHelpText": "多久Radarr同步一次您的影片列表,最小值为6小时",
|
||||
@@ -628,7 +628,7 @@
|
||||
"LoadingMovieExtraFilesFailed": "读取影片附加文件失败",
|
||||
"LoadingMovieCreditsFailed": "读取影片评分失败",
|
||||
"ListSyncLevelHelpTextWarning": "影片文件会被永久删除,如果你的列表是空的,这可能会导致您的媒体库被清除",
|
||||
"ListSyncLevelHelpText": "媒体库中的影片如果不在您的列表中则会被移除或者不再监控",
|
||||
"ListSyncLevelHelpText": "媒体库中的影片如果没有出现或者不在您的列表中,则将会按照您的选择进行处理",
|
||||
"LastWriteTime": "最后写入时间",
|
||||
"LastDuration": "上一次用时",
|
||||
"InteractiveImportErrMovie": "必须为每个已选择文件选择影片",
|
||||
@@ -636,7 +636,7 @@
|
||||
"InstallLatest": "安装最新版",
|
||||
"IndexerStatusCheckSingleClientMessage": "搜刮器因错误不可用:{0}",
|
||||
"IndexerStatusCheckAllClientMessage": "所有搜刮器都因错误不可用",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "没有任何索引器开启了手动搜索,因此 Radarr不会提供任何手动搜索的结果",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "没有可用的交互式搜索的索引器,因此 Radarr 不会提供任何交互式搜索的结果",
|
||||
"IndexerRssHealthCheckNoIndexers": "没有任何索引器开启了RSS同步,Radarr不会自动抓取新发布的影片",
|
||||
"IndexerLongTermStatusCheckSingleClientMessage": "由于故障6小时,下列索引器都已不可用:{0}",
|
||||
"IndexerLongTermStatusCheckAllClientMessage": "由于故障超过6小时,所有索引器均不可用",
|
||||
@@ -745,7 +745,7 @@
|
||||
"SearchCutoffUnmet": "搜索未满足终止条件的",
|
||||
"RadarrSupportsAnyDownloadClient": "Radarr支持许多常用的的torrent和usenet下载客户端。",
|
||||
"VideoCodec": "视频编码",
|
||||
"QualitySettings": "歌曲质量配置设置",
|
||||
"QualitySettings": "媒体质量设置",
|
||||
"UseHardlinksInsteadOfCopy": "使用硬链接代替复制",
|
||||
"RecycleBinHelpText": "影片文件会被移动到回收站以替代永久删除",
|
||||
"Redownload": "重新下载",
|
||||
@@ -766,7 +766,7 @@
|
||||
"Table": "表格",
|
||||
"WaitingToImport": "等待导入",
|
||||
"QualityOrLangCutoffHasNotBeenMet": "影片质量和语言未满足终止监控条件",
|
||||
"QualityProfiles": "歌曲质量配置",
|
||||
"QualityProfiles": "媒体质量配置",
|
||||
"ProfilesSettingsSummary": "影片质量,语言和延时配置",
|
||||
"WeekColumnHeader": "日期格式",
|
||||
"YesCancel": "是,取消",
|
||||
@@ -783,10 +783,10 @@
|
||||
"RemovedMovieCheckSingleMessage": "影片“{0}”已从TMDb移除",
|
||||
"UpgradeUntilThisQualityIsMetOrExceeded": "升级直到影片质量超出或者满足",
|
||||
"SettingsWeekColumnHeader": "日期格式",
|
||||
"ShowQualityProfileHelpText": "在海报下显示歌曲质量",
|
||||
"ShowQualityProfileHelpText": "在海报下方显示媒体质量配置",
|
||||
"ReleaseRejected": "版本被拒绝",
|
||||
"UnmappedFilesOnly": "未映射的文件",
|
||||
"Quality": "歌曲质量",
|
||||
"Quality": "媒体质量",
|
||||
"TheLogLevelDefault": "默认的日志等级为“Info”,可以被修改在",
|
||||
"RestartReloadNote": "注意:Radarr将在恢复过程中自动重启并重新加载UI。",
|
||||
"RequiredRestrictionPlaceHolder": "添加新限制",
|
||||
@@ -818,7 +818,7 @@
|
||||
"QualityCutoffHasNotBeenMet": "影片还未满足质量配置",
|
||||
"UISettingsSummary": "日历、日期和色弱模式选项",
|
||||
"Scheduled": "计划中",
|
||||
"ShowQualityProfile": "显示歌曲质量配置",
|
||||
"ShowQualityProfile": "显示媒体质量配置",
|
||||
"IndexerRssHealthCheckNoAvailableIndexers": "由于索引器错误,所有支持rss的索引器暂时不可用",
|
||||
"KeepAndUnmonitorMovie": "保持不监控影片",
|
||||
"RadarrSupportsAnyIndexer": "Radarr支持任何使用Newznab标准的搜刮器,以及下面列出的其他搜刮器。",
|
||||
@@ -875,7 +875,7 @@
|
||||
"NoAltTitle": "没有其他标题。",
|
||||
"PreviewRenameHelpText": "小提示:要预览重命名,选择“取消”并点击任何影片标题并且使用",
|
||||
"PtpOldSettingsCheckMessage": "下列PassThePopcorn搜刮器有已启用的设置,请更新:{0}",
|
||||
"QualityDefinitions": "歌曲质量定义",
|
||||
"QualityDefinitions": "媒体质量定义",
|
||||
"RegularExpressionsCanBeTested": "正则表达式可供测试 ",
|
||||
"ReleaseTitle": "歌曲发布标题",
|
||||
"RemoveHelpTextWarning": "移除操作会从下载客户端中删除任务和已下载文件。",
|
||||
@@ -987,7 +987,7 @@
|
||||
"MovieExcludedFromAutomaticAdd": "影片已被排除在自动添加之外",
|
||||
"MovieDetailsPreviousMovie": "影片详细:前一个",
|
||||
"MovieDetailsNextMovie": "影片详细:下一个",
|
||||
"MovieChat": "Movie Chat",
|
||||
"MovieChat": "MovieChat.org",
|
||||
"MovieAlreadyExcluded": "影片已排除",
|
||||
"Mode": "模式",
|
||||
"MinimumLimits": "最小限制",
|
||||
@@ -1010,7 +1010,7 @@
|
||||
"GrabReleaseMessageText": "Radarr无法确定这个发布版本是哪部电影,Radarr可能无法自动导入此版本,你想要获取“{0}”吗?",
|
||||
"FeatureRequests": "功能建议",
|
||||
"Extension": "扩展",
|
||||
"Discord": "Discord",
|
||||
"Discord": "冲突",
|
||||
"CustomFormatUnknownConditionOption": "未知的条件“{1}”的选项“{0}”",
|
||||
"Retention": "保留",
|
||||
"ChmodGroupHelpText": "组名称或GID。对于远程文件系统请使用GID。",
|
||||
|
||||
@@ -42,11 +42,9 @@ namespace NzbDrone.Core.MediaCover
|
||||
|
||||
try
|
||||
{
|
||||
using (var image = Image.Load(source))
|
||||
{
|
||||
image.Mutate(x => x.Resize(0, height));
|
||||
image.Save(destination);
|
||||
}
|
||||
using var image = Image.Load(source);
|
||||
image.Mutate(x => x.Resize(0, height));
|
||||
image.Save(destination);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -72,6 +72,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
}
|
||||
|
||||
public static HashSet<string> Extensions => new HashSet<string>(_fileExtensions.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
public static HashSet<string> DiskExtensions => new HashSet<string>(new[] { ".img", ".iso", ".vob" }, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public static Quality GetQualityForExtension(string extension)
|
||||
{
|
||||
|
||||
@@ -154,7 +154,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||
}
|
||||
|
||||
Logger.ForDebugEvent()
|
||||
.Message("Unknown audio format: '{0}' in '{1}'.", mediaInfo.RawStreamData, sceneName)
|
||||
.Message("Unknown audio format: '{0}' in '{1}'. Streams: {2}", audioFormat, sceneName, mediaInfo.RawStreamData)
|
||||
.WriteSentryWarn("UnknownAudioFormatFFProbe", mediaInfo.ContainerFormat, mediaInfo.AudioFormat, audioCodecID)
|
||||
.Log();
|
||||
|
||||
@@ -236,8 +236,12 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||
return "AV1";
|
||||
}
|
||||
|
||||
if (videoFormat == "vp6" ||
|
||||
videoFormat == "vp7" ||
|
||||
if (videoFormat.Contains("vp6"))
|
||||
{
|
||||
return "VP6";
|
||||
}
|
||||
|
||||
if (videoFormat == "vp7" ||
|
||||
videoFormat == "vp8" ||
|
||||
videoFormat == "vp9")
|
||||
{
|
||||
@@ -257,13 +261,15 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||
videoFormat == "rv20" ||
|
||||
videoFormat == "rv30" ||
|
||||
videoFormat == "rv40" ||
|
||||
videoFormat == "cinepak")
|
||||
videoFormat == "cinepak" ||
|
||||
videoFormat == "rawvideo" ||
|
||||
videoFormat == "msvideo1")
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
Logger.ForDebugEvent()
|
||||
.Message("Unknown video format: '{0}' in '{1}'.", mediaInfo.RawStreamData, sceneName)
|
||||
.Message("Unknown video format: '{0}' in '{1}'. Streams: {2}", videoFormat, sceneName, mediaInfo.RawStreamData)
|
||||
.WriteSentryWarn("UnknownVideoFormatFFProbe", mediaInfo.ContainerFormat, videoFormat, videoCodecID)
|
||||
.Log();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FFMpegCore;
|
||||
using FFMpegCore.Enums;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -55,6 +56,11 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||
throw new FileNotFoundException("Media file does not exist: " + filename);
|
||||
}
|
||||
|
||||
if (MediaFileExtensions.DiskExtensions.Contains(Path.GetExtension(filename)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Cache media info by path, mtime and length so we don't need to read files multiple times
|
||||
try
|
||||
{
|
||||
@@ -62,6 +68,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||
var ffprobeOutput = FFProbe.GetStreamJson(filename, ffOptions: new FFOptions { ExtraArguments = "-probesize 50000000" });
|
||||
|
||||
var analysis = FFProbe.AnalyseStreamJson(ffprobeOutput);
|
||||
var primaryVideoStream = GetPrimaryVideoStream(analysis);
|
||||
|
||||
if (analysis.PrimaryAudioStream?.ChannelLayout.IsNullOrWhiteSpace() ?? true)
|
||||
{
|
||||
@@ -71,26 +78,26 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||
|
||||
var mediaInfoModel = new MediaInfoModel();
|
||||
mediaInfoModel.ContainerFormat = analysis.Format.FormatName;
|
||||
mediaInfoModel.VideoFormat = analysis.PrimaryVideoStream?.CodecName;
|
||||
mediaInfoModel.VideoCodecID = analysis.PrimaryVideoStream?.CodecTagString;
|
||||
mediaInfoModel.VideoProfile = analysis.PrimaryVideoStream?.Profile;
|
||||
mediaInfoModel.VideoBitrate = analysis.PrimaryVideoStream?.BitRate ?? 0;
|
||||
mediaInfoModel.VideoMultiViewCount = analysis.PrimaryVideoStream?.Tags?.ContainsKey("stereo_mode") ?? false ? 2 : 1;
|
||||
mediaInfoModel.VideoBitDepth = GetPixelFormat(analysis.PrimaryVideoStream?.PixelFormat)?.Components.Min(x => x.BitDepth) ?? 8;
|
||||
mediaInfoModel.VideoColourPrimaries = analysis.PrimaryVideoStream?.ColorPrimaries;
|
||||
mediaInfoModel.VideoTransferCharacteristics = analysis.PrimaryVideoStream?.ColorTransfer;
|
||||
mediaInfoModel.DoviConfigurationRecord = analysis.PrimaryVideoStream?.SideDataList?.Find(x => x.GetType().Name == nameof(DoviConfigurationRecordSideData)) as DoviConfigurationRecordSideData;
|
||||
mediaInfoModel.Height = analysis.PrimaryVideoStream?.Height ?? 0;
|
||||
mediaInfoModel.Width = analysis.PrimaryVideoStream?.Width ?? 0;
|
||||
mediaInfoModel.VideoFormat = primaryVideoStream?.CodecName;
|
||||
mediaInfoModel.VideoCodecID = primaryVideoStream?.CodecTagString;
|
||||
mediaInfoModel.VideoProfile = primaryVideoStream?.Profile;
|
||||
mediaInfoModel.VideoBitrate = primaryVideoStream?.BitRate ?? 0;
|
||||
mediaInfoModel.VideoMultiViewCount = primaryVideoStream?.Tags?.ContainsKey("stereo_mode") ?? false ? 2 : 1;
|
||||
mediaInfoModel.VideoBitDepth = GetPixelFormat(primaryVideoStream?.PixelFormat)?.Components.Min(x => x.BitDepth) ?? 8;
|
||||
mediaInfoModel.VideoColourPrimaries = primaryVideoStream?.ColorPrimaries;
|
||||
mediaInfoModel.VideoTransferCharacteristics = primaryVideoStream?.ColorTransfer;
|
||||
mediaInfoModel.DoviConfigurationRecord = primaryVideoStream?.SideDataList?.Find(x => x.GetType().Name == nameof(DoviConfigurationRecordSideData)) as DoviConfigurationRecordSideData;
|
||||
mediaInfoModel.Height = primaryVideoStream?.Height ?? 0;
|
||||
mediaInfoModel.Width = primaryVideoStream?.Width ?? 0;
|
||||
mediaInfoModel.AudioFormat = analysis.PrimaryAudioStream?.CodecName;
|
||||
mediaInfoModel.AudioCodecID = analysis.PrimaryAudioStream?.CodecTagString;
|
||||
mediaInfoModel.AudioProfile = analysis.PrimaryAudioStream?.Profile;
|
||||
mediaInfoModel.AudioBitrate = analysis.PrimaryAudioStream?.BitRate ?? 0;
|
||||
mediaInfoModel.RunTime = GetBestRuntime(analysis.PrimaryAudioStream?.Duration, analysis.PrimaryVideoStream?.Duration, analysis.Format.Duration);
|
||||
mediaInfoModel.RunTime = GetBestRuntime(analysis.PrimaryAudioStream?.Duration, primaryVideoStream?.Duration, analysis.Format.Duration);
|
||||
mediaInfoModel.AudioStreamCount = analysis.AudioStreams.Count;
|
||||
mediaInfoModel.AudioChannels = analysis.PrimaryAudioStream?.Channels ?? 0;
|
||||
mediaInfoModel.AudioChannelPositions = analysis.PrimaryAudioStream?.ChannelLayout;
|
||||
mediaInfoModel.VideoFps = analysis.PrimaryVideoStream?.FrameRate ?? 0;
|
||||
mediaInfoModel.VideoFps = primaryVideoStream?.FrameRate ?? 0;
|
||||
mediaInfoModel.AudioLanguages = analysis.AudioStreams?.Select(x => x.Language)
|
||||
.Where(l => l.IsNotNullOrWhiteSpace())
|
||||
.ToList();
|
||||
@@ -112,7 +119,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||
frames = FFProbe.AnalyseFrameJson(frameOutput);
|
||||
}
|
||||
|
||||
var streamSideData = analysis.PrimaryVideoStream?.SideDataList ?? new ();
|
||||
var streamSideData = primaryVideoStream?.SideDataList ?? new ();
|
||||
var framesSideData = frames?.Frames?.Count > 0 ? frames?.Frames[0]?.SideDataList ?? new () : new ();
|
||||
|
||||
var sideData = streamSideData.Concat(framesSideData).ToList();
|
||||
@@ -150,6 +157,19 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||
return video.Value;
|
||||
}
|
||||
|
||||
private VideoStream GetPrimaryVideoStream(IMediaAnalysis mediaAnalysis)
|
||||
{
|
||||
if (mediaAnalysis.VideoStreams.Count <= 1)
|
||||
{
|
||||
return mediaAnalysis.PrimaryVideoStream;
|
||||
}
|
||||
|
||||
// motion image codec streams are often in front of the main video stream
|
||||
var codecFilter = new[] { "mjpeg", "png" };
|
||||
|
||||
return mediaAnalysis.VideoStreams.FirstOrDefault(s => !codecFilter.Contains(s.CodecName)) ?? mediaAnalysis.PrimaryVideoStream;
|
||||
}
|
||||
|
||||
private FFProbePixelFormat GetPixelFormat(string format)
|
||||
{
|
||||
return _pixelFormats.Find(x => x.Name == format);
|
||||
|
||||
@@ -8,9 +8,10 @@ using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Extras;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.MediaFiles.Commands;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
||||
@@ -29,6 +30,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IManageCommandQueue _commandQueueManager;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ImportApprovedMovie(IUpgradeMediaFiles movieFileUpgrader,
|
||||
@@ -37,6 +39,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
|
||||
IDiskProvider diskProvider,
|
||||
IHistoryService historyService,
|
||||
IEventAggregator eventAggregator,
|
||||
IManageCommandQueue commandQueueManager,
|
||||
Logger logger)
|
||||
{
|
||||
_movieFileUpgrader = movieFileUpgrader;
|
||||
@@ -45,6 +48,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
|
||||
_diskProvider = diskProvider;
|
||||
_historyService = historyService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_commandQueueManager = commandQueueManager;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -157,6 +161,8 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
|
||||
{
|
||||
_logger.Warn(e, "Couldn't import movie " + localMovie);
|
||||
importResults.Add(new ImportResult(importDecision, "Failed to import movie, Destination already exists."));
|
||||
|
||||
_commandQueueManager.Push(new RescanMovieCommand(localMovie.Movie.Id));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -121,8 +121,8 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
|
||||
SceneSource = SceneSource(movie, rootFolder),
|
||||
ExistingFile = movie.Path.IsParentPath(path),
|
||||
Size = _diskProvider.GetFileSize(path),
|
||||
Languages = (languages?.SingleOrDefault() ?? Language.Unknown) == Language.Unknown ? languageParse : languages,
|
||||
Quality = quality.Quality == Quality.Unknown ? QualityParser.ParseQuality(path) : quality,
|
||||
Languages = languages?.Count <= 1 && (languages?.SingleOrDefault() ?? Language.Unknown) == Language.Unknown ? languageParse : languages,
|
||||
Quality = (quality?.Quality ?? Quality.Unknown) == Quality.Unknown ? QualityParser.ParseQuality(path) : quality,
|
||||
ReleaseGroup = releaseGroup.IsNullOrWhiteSpace() ? Parser.Parser.ParseReleaseGroup(path) : releaseGroup,
|
||||
};
|
||||
|
||||
|
||||
@@ -61,17 +61,14 @@ namespace NzbDrone.Core.Movies
|
||||
{
|
||||
get
|
||||
{
|
||||
if (PhysicalRelease.HasValue)
|
||||
if ((PhysicalRelease.HasValue && PhysicalRelease.Value >= DateTime.UtcNow.AddDays(-21)) ||
|
||||
(DigitalRelease.HasValue && DigitalRelease.Value >= DateTime.UtcNow.AddDays(-21)) ||
|
||||
(InCinemas.HasValue && InCinemas.Value >= DateTime.UtcNow.AddDays(-120)))
|
||||
{
|
||||
return PhysicalRelease.Value >= DateTime.UtcNow.AddDays(-21);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (InCinemas.HasValue)
|
||||
{
|
||||
return InCinemas.Value >= DateTime.UtcNow.AddDays(-120);
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
65
src/NzbDrone.Core/Notifications/Apprise/Apprise.cs
Normal file
65
src/NzbDrone.Core/Notifications/Apprise/Apprise.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation.Results;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Movies;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Apprise
|
||||
{
|
||||
public class Apprise : NotificationBase<AppriseSettings>
|
||||
{
|
||||
public override string Name => "Apprise";
|
||||
|
||||
public override string Link => "https://github.com/caronc/apprise";
|
||||
|
||||
private readonly IAppriseProxy _proxy;
|
||||
|
||||
public Apprise(IAppriseProxy proxy)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
public override void OnGrab(GrabMessage grabMessage)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_GRABBED_TITLE, grabMessage.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnDownload(DownloadMessage message)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_DOWNLOADED_TITLE, message.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnMovieAdded(Movie movie)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_ADDED_TITLE, $"{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, healthCheck.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
|
||||
{
|
||||
_proxy.SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, Settings);
|
||||
}
|
||||
|
||||
public override ValidationResult Test()
|
||||
{
|
||||
var failures = new List<ValidationFailure>();
|
||||
|
||||
failures.AddIfNotNull(_proxy.Test(Settings));
|
||||
|
||||
return new ValidationResult(failures);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/NzbDrone.Core/Notifications/Apprise/AppriseError.cs
Normal file
7
src/NzbDrone.Core/Notifications/Apprise/AppriseError.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace NzbDrone.Core.Notifications.Apprise
|
||||
{
|
||||
public class AppriseError
|
||||
{
|
||||
public string Error { get; set; }
|
||||
}
|
||||
}
|
||||
18
src/NzbDrone.Core/Notifications/Apprise/AppriseException.cs
Normal file
18
src/NzbDrone.Core/Notifications/Apprise/AppriseException.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using NzbDrone.Common.Exceptions;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Apprise
|
||||
{
|
||||
public class AppriseException : NzbDroneException
|
||||
{
|
||||
public AppriseException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AppriseException(string message, Exception innerException, params object[] args)
|
||||
: base(message, innerException, args)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Apprise
|
||||
{
|
||||
public enum AppriseNotificationType
|
||||
{
|
||||
[EnumMember(Value = "info")]
|
||||
Info,
|
||||
|
||||
[EnumMember(Value = "success")]
|
||||
Success,
|
||||
|
||||
[EnumMember(Value = "warning")]
|
||||
Warning,
|
||||
|
||||
[EnumMember(Value = "failure")]
|
||||
Failure,
|
||||
}
|
||||
}
|
||||
15
src/NzbDrone.Core/Notifications/Apprise/ApprisePayload.cs
Normal file
15
src/NzbDrone.Core/Notifications/Apprise/ApprisePayload.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace NzbDrone.Core.Notifications.Apprise
|
||||
{
|
||||
public class ApprisePayload
|
||||
{
|
||||
public string Urls { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public string Body { get; set; }
|
||||
|
||||
public AppriseNotificationType Type { get; set; }
|
||||
|
||||
public string Tag { get; set; }
|
||||
}
|
||||
}
|
||||
118
src/NzbDrone.Core/Notifications/Apprise/AppriseProxy.cs
Normal file
118
src/NzbDrone.Core/Notifications/Apprise/AppriseProxy.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Apprise
|
||||
{
|
||||
public interface IAppriseProxy
|
||||
{
|
||||
void SendNotification(string title, string message, AppriseSettings settings);
|
||||
ValidationFailure Test(AppriseSettings settings);
|
||||
}
|
||||
|
||||
public class AppriseProxy : IAppriseProxy
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public AppriseProxy(IHttpClient httpClient, Logger logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void SendNotification(string title, string message, AppriseSettings settings)
|
||||
{
|
||||
var payload = new ApprisePayload
|
||||
{
|
||||
Title = title,
|
||||
Body = message,
|
||||
Type = (AppriseNotificationType)settings.NotificationType
|
||||
};
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder(settings.ServerUrl.TrimEnd('/', ' '))
|
||||
.Post()
|
||||
.Accept(HttpAccept.Json);
|
||||
|
||||
if (settings.ConfigurationKey.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
requestBuilder
|
||||
.Resource("/notify/{configurationKey}")
|
||||
.SetSegment("configurationKey", settings.ConfigurationKey);
|
||||
}
|
||||
else if (settings.StatelessUrls.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
requestBuilder.Resource("/notify");
|
||||
|
||||
payload.Urls = settings.StatelessUrls;
|
||||
}
|
||||
|
||||
if (settings.Tags.Any())
|
||||
{
|
||||
payload.Tag = settings.Tags.Join(",");
|
||||
}
|
||||
|
||||
if (settings.AuthUsername.IsNotNullOrWhiteSpace() || settings.AuthPassword.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
requestBuilder.NetworkCredential = new BasicNetworkCredential(settings.AuthUsername, settings.AuthPassword);
|
||||
}
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
|
||||
request.Headers.ContentType = "application/json";
|
||||
request.SetContent(payload.ToJson());
|
||||
|
||||
try
|
||||
{
|
||||
_httpClient.Execute(request);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send message");
|
||||
throw new AppriseException("Unable to send Apprise notifications: {0}", ex, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public ValidationFailure Test(AppriseSettings settings)
|
||||
{
|
||||
const string title = "Radarr - Test Notification";
|
||||
const string body = "Success! You have properly configured your apprise notification settings.";
|
||||
|
||||
try
|
||||
{
|
||||
SendNotification(title, body, settings);
|
||||
}
|
||||
catch (AppriseException ex) when (ex.InnerException is HttpException httpException)
|
||||
{
|
||||
if (httpException.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
_logger.Error(ex, $"HTTP Auth credentials are invalid: {0}", ex.Message);
|
||||
return new ValidationFailure("AuthUsername", $"HTTP Auth credentials are invalid: {ex.Message}");
|
||||
}
|
||||
|
||||
if (httpException.Response.Content.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var error = Json.Deserialize<AppriseError>(httpException.Response.Content);
|
||||
|
||||
_logger.Error(ex, $"Unable to send test message. Response from API: {0}", error.Error);
|
||||
return new ValidationFailure(string.Empty, $"Unable to send test message. Response from API: {error.Error}");
|
||||
}
|
||||
|
||||
_logger.Error(ex, "Unable to send test message. Server connection failed: ({0}) {1}", httpException.Response.StatusCode, ex.Message);
|
||||
return new ValidationFailure("Url", $"Unable to connect to Apprise API. Server connection failed: ({httpException.Response.StatusCode}) {ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test message: {0}", ex.Message);
|
||||
return new ValidationFailure("Url", $"Unable to send test message: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
74
src/NzbDrone.Core/Notifications/Apprise/AppriseSettings.cs
Normal file
74
src/NzbDrone.Core/Notifications/Apprise/AppriseSettings.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
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.Apprise
|
||||
{
|
||||
public class AppriseSettingsValidator : AbstractValidator<AppriseSettings>
|
||||
{
|
||||
public AppriseSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.ServerUrl).IsValidUrl();
|
||||
|
||||
RuleFor(c => c.ConfigurationKey).NotEmpty()
|
||||
.When(c => c.StatelessUrls.IsNullOrWhiteSpace())
|
||||
.WithMessage("Use either Configuration Key or Stateless URLs");
|
||||
|
||||
RuleFor(c => c.ConfigurationKey).Matches("^[a-z0-9-]*$")
|
||||
.WithMessage("Allowed characters a-z, 0-9 and -");
|
||||
|
||||
RuleFor(c => c.StatelessUrls).NotEmpty()
|
||||
.When(c => c.ConfigurationKey.IsNullOrWhiteSpace())
|
||||
.WithMessage("Use either Configuration Key or Stateless URLs");
|
||||
|
||||
RuleFor(c => c.StatelessUrls).Empty()
|
||||
.When(c => c.ConfigurationKey.IsNotNullOrWhiteSpace())
|
||||
.WithMessage("Use either Configuration Key or Stateless URLs");
|
||||
|
||||
RuleFor(c => c.Tags).Empty()
|
||||
.When(c => c.StatelessUrls.IsNotNullOrWhiteSpace())
|
||||
.WithMessage("Stateless URLs do not support tags");
|
||||
}
|
||||
}
|
||||
|
||||
public class AppriseSettings : IProviderConfig
|
||||
{
|
||||
private static readonly AppriseSettingsValidator Validator = new ();
|
||||
|
||||
public AppriseSettings()
|
||||
{
|
||||
NotificationType = (int)AppriseNotificationType.Info;
|
||||
Tags = Array.Empty<string>();
|
||||
}
|
||||
|
||||
[FieldDefinition(1, Label = "Apprise Server URL", Type = FieldType.Url, Placeholder = "http://localhost:8000", HelpText = "Apprise server URL, including http(s):// and port if needed", HelpLink = "https://github.com/caronc/apprise-api")]
|
||||
public string ServerUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Apprise Configuration Key", Type = FieldType.Textbox, HelpText = "Configuration Key for the Persistent Storage Solution. Leave empty if Stateless URLs is used.", HelpLink = "https://github.com/caronc/apprise-api#persistent-storage-solution")]
|
||||
public string ConfigurationKey { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Apprise Stateless URLs", Type = FieldType.Textbox, HelpText = "One or more URLs separated by commas identifying where the notification should be sent to. Leave empty if Persistent Storage is used.", HelpLink = "https://github.com/caronc/apprise#productivity-based-notifications")]
|
||||
public string StatelessUrls { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Apprise Notification Type", Type = FieldType.Select, SelectOptions = typeof(AppriseNotificationType))]
|
||||
public int NotificationType { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Apprise Tags", Type = FieldType.Tag, HelpText = "Optionally notify only those tagged accordingly.")]
|
||||
public IEnumerable<string> Tags { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Username", Type = FieldType.Textbox, HelpText = "HTTP Basic Auth Username", Privacy = PrivacyLevel.UserName)]
|
||||
public string AuthUsername { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "Password", Type = FieldType.Password, HelpText = "HTTP Basic Auth Password", Privacy = PrivacyLevel.Password)]
|
||||
public string AuthPassword { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ namespace NzbDrone.Core.Notifications.Boxcar
|
||||
|
||||
public override void OnMovieAdded(Movie movie)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_ADDED_TITLE_BRANDED, $"{movie.Title} added to library", Settings);
|
||||
_proxy.SendNotification(MOVIE_ADDED_TITLE, $"{movie.Title} added to library", Settings);
|
||||
}
|
||||
|
||||
public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage)
|
||||
|
||||
@@ -83,30 +83,13 @@ namespace NzbDrone.Core.Notifications.Email
|
||||
return new ValidationResult(failures);
|
||||
}
|
||||
|
||||
public ValidationFailure Test(EmailSettings settings)
|
||||
{
|
||||
const string body = "Success! You have properly configured your email notification settings";
|
||||
|
||||
try
|
||||
{
|
||||
SendEmail(settings, "Radarr - Test Notification", body);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test email");
|
||||
return new ValidationFailure("Server", "Unable to send test email");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void SendEmail(EmailSettings settings, string subject, string body, bool htmlBody = false)
|
||||
{
|
||||
var email = new MimeMessage();
|
||||
|
||||
email.From.Add(ParseAddress("From", settings.From));
|
||||
email.To.AddRange(settings.To.Select(x => ParseAddress("To", x)));
|
||||
email.Cc.AddRange(settings.CC.Select(x => ParseAddress("CC", x)));
|
||||
email.Cc.AddRange(settings.Cc.Select(x => ParseAddress("CC", x)));
|
||||
email.Bcc.AddRange(settings.Bcc.Select(x => ParseAddress("BCC", x)));
|
||||
|
||||
email.Subject = subject;
|
||||
@@ -129,52 +112,67 @@ namespace NzbDrone.Core.Notifications.Email
|
||||
throw;
|
||||
}
|
||||
|
||||
_logger.Debug("Finished sending email. Subject: {0}", email.Subject);
|
||||
_logger.Debug("Finished sending email. Subject: {0}", subject);
|
||||
}
|
||||
|
||||
private void Send(MimeMessage email, EmailSettings settings)
|
||||
{
|
||||
using (var client = new SmtpClient())
|
||||
using var client = new SmtpClient();
|
||||
client.Timeout = 10000;
|
||||
|
||||
var serverOption = SecureSocketOptions.Auto;
|
||||
|
||||
if (settings.RequireEncryption)
|
||||
{
|
||||
client.Timeout = 10000;
|
||||
|
||||
var serverOption = SecureSocketOptions.Auto;
|
||||
|
||||
if (settings.RequireEncryption)
|
||||
if (settings.Port == 465)
|
||||
{
|
||||
if (settings.Port == 465)
|
||||
{
|
||||
serverOption = SecureSocketOptions.SslOnConnect;
|
||||
}
|
||||
else
|
||||
{
|
||||
serverOption = SecureSocketOptions.StartTls;
|
||||
}
|
||||
serverOption = SecureSocketOptions.SslOnConnect;
|
||||
}
|
||||
|
||||
client.ServerCertificateValidationCallback = _certificateValidationService.ShouldByPassValidationError;
|
||||
|
||||
_logger.Debug("Connecting to mail server");
|
||||
|
||||
client.Connect(settings.Server, settings.Port, serverOption);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(settings.Username))
|
||||
else
|
||||
{
|
||||
_logger.Debug("Authenticating to mail server");
|
||||
|
||||
client.Authenticate(settings.Username, settings.Password);
|
||||
serverOption = SecureSocketOptions.StartTls;
|
||||
}
|
||||
|
||||
_logger.Debug("Sending to mail server");
|
||||
|
||||
client.Send(email);
|
||||
|
||||
_logger.Debug("Sent to mail server, disconnecting");
|
||||
|
||||
client.Disconnect(true);
|
||||
|
||||
_logger.Debug("Disconnecting from mail server");
|
||||
}
|
||||
|
||||
client.ServerCertificateValidationCallback = _certificateValidationService.ShouldByPassValidationError;
|
||||
|
||||
_logger.Debug("Connecting to mail server");
|
||||
|
||||
client.Connect(settings.Server, settings.Port, serverOption);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(settings.Username))
|
||||
{
|
||||
_logger.Debug("Authenticating to mail server");
|
||||
|
||||
client.Authenticate(settings.Username, settings.Password);
|
||||
}
|
||||
|
||||
_logger.Debug("Sending to mail server");
|
||||
|
||||
client.Send(email);
|
||||
|
||||
_logger.Debug("Sent to mail server, disconnecting");
|
||||
|
||||
client.Disconnect(true);
|
||||
|
||||
_logger.Debug("Disconnecting from mail server");
|
||||
}
|
||||
|
||||
public ValidationFailure Test(EmailSettings settings)
|
||||
{
|
||||
const string body = "Success! You have properly configured your email notification settings";
|
||||
|
||||
try
|
||||
{
|
||||
SendEmail(settings, "Radarr - Test Notification", body);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unable to send test email");
|
||||
return new ValidationFailure("Server", "Unable to send test email");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private MailboxAddress ParseAddress(string type, string address)
|
||||
|
||||
@@ -16,13 +16,13 @@ namespace NzbDrone.Core.Notifications.Email
|
||||
RuleFor(c => c.Port).InclusiveBetween(1, 65535);
|
||||
RuleFor(c => c.From).NotEmpty();
|
||||
RuleForEach(c => c.To).EmailAddress();
|
||||
RuleForEach(c => c.CC).EmailAddress();
|
||||
RuleForEach(c => c.Cc).EmailAddress();
|
||||
RuleForEach(c => c.Bcc).EmailAddress();
|
||||
|
||||
// Only require one of three send fields to be set
|
||||
RuleFor(c => c.To).NotEmpty().Unless(c => c.Bcc.Any() || c.CC.Any());
|
||||
RuleFor(c => c.CC).NotEmpty().Unless(c => c.To.Any() || c.Bcc.Any());
|
||||
RuleFor(c => c.Bcc).NotEmpty().Unless(c => c.To.Any() || c.CC.Any());
|
||||
RuleFor(c => c.To).NotEmpty().Unless(c => c.Bcc.Any() || c.Cc.Any());
|
||||
RuleFor(c => c.Cc).NotEmpty().Unless(c => c.To.Any() || c.Bcc.Any());
|
||||
RuleFor(c => c.Bcc).NotEmpty().Unless(c => c.To.Any() || c.Cc.Any());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,10 @@ namespace NzbDrone.Core.Notifications.Email
|
||||
|
||||
public EmailSettings()
|
||||
{
|
||||
Port = 567;
|
||||
Port = 587;
|
||||
|
||||
To = Array.Empty<string>();
|
||||
CC = Array.Empty<string>();
|
||||
Cc = Array.Empty<string>();
|
||||
Bcc = Array.Empty<string>();
|
||||
}
|
||||
|
||||
@@ -60,7 +61,7 @@ namespace NzbDrone.Core.Notifications.Email
|
||||
public IEnumerable<string> To { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "CC Address(es)", HelpText = "Comma separated list of email cc recipients", Placeholder = "example@email.com,example1@email.com", Advanced = true)]
|
||||
public IEnumerable<string> CC { get; set; }
|
||||
public IEnumerable<string> Cc { get; set; }
|
||||
|
||||
[FieldDefinition(8, Label = "BCC Address(es)", HelpText = "Comma separated list of email bcc recipients", Placeholder = "example@email.com,example1@email.com", Advanced = true)]
|
||||
public IEnumerable<string> Bcc { get; set; }
|
||||
|
||||
@@ -36,12 +36,12 @@ namespace NzbDrone.Core.Notifications.Ntfy
|
||||
|
||||
public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_FILE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
_proxy.SendNotification(MOVIE_FILE_DELETED_TITLE_BRANDED, deleteMessage.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnMovieDelete(MovieDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
_proxy.SendNotification(MOVIE_DELETED_TITLE_BRANDED, deleteMessage.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck)
|
||||
|
||||
@@ -37,12 +37,12 @@ namespace NzbDrone.Core.Notifications.PushBullet
|
||||
|
||||
public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_FILE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
_proxy.SendNotification(MOVIE_FILE_DELETED_TITLE_BRANDED, deleteMessage.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnMovieDelete(MovieDeleteMessage deleteMessage)
|
||||
{
|
||||
_proxy.SendNotification(MOVIE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||
_proxy.SendNotification(MOVIE_DELETED_TITLE_BRANDED, deleteMessage.Message, Settings);
|
||||
}
|
||||
|
||||
public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck)
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace NzbDrone.Core.Notifications.Telegram
|
||||
.AddFormParameter("parse_mode", "HTML")
|
||||
.AddFormParameter("text", text)
|
||||
.AddFormParameter("disable_notification", settings.SendSilently)
|
||||
.AddFormParameter("message_thread_id", settings.TopicId)
|
||||
.Build();
|
||||
|
||||
_httpClient.Post(request);
|
||||
@@ -64,7 +65,16 @@ namespace NzbDrone.Core.Notifications.Telegram
|
||||
else if (ex is Common.Http.HttpException restException && restException.Response.StatusCode == HttpStatusCode.BadRequest)
|
||||
{
|
||||
var error = Json.Deserialize<TelegramError>(restException.Response.Content);
|
||||
var property = error.Description.ContainsIgnoreCase("chat not found") ? "ChatId" : "BotToken";
|
||||
var property = "BotToken";
|
||||
|
||||
if (error.Description.ContainsIgnoreCase("chat not found") || error.Description.ContainsIgnoreCase("group chat was upgraded to a supergroup chat"))
|
||||
{
|
||||
property = "ChatId";
|
||||
}
|
||||
else if (error.Description.ContainsIgnoreCase("message thread not found"))
|
||||
{
|
||||
property = "TopicId";
|
||||
}
|
||||
|
||||
return new ValidationFailure(property, error.Description);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace NzbDrone.Core.Notifications.Telegram
|
||||
{
|
||||
RuleFor(c => c.BotToken).NotEmpty();
|
||||
RuleFor(c => c.ChatId).NotEmpty();
|
||||
RuleFor(c => c.TopicId).Must(topicId => !topicId.HasValue || topicId > 1)
|
||||
.WithMessage("Topic ID must be greater than 1 or empty");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +26,10 @@ namespace NzbDrone.Core.Notifications.Telegram
|
||||
[FieldDefinition(1, Label = "Chat ID", HelpLink = "http://stackoverflow.com/a/37396871/882971", HelpText = "You must start a conversation with the bot or add it to your group to receive messages")]
|
||||
public string ChatId { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Send Silently", Type = FieldType.Checkbox, HelpText = "Sends the message silently. Users will receive a notification with no sound")]
|
||||
[FieldDefinition(2, Label = "Topic ID", HelpLink = "https://stackoverflow.com/a/75178418", HelpText = "Specify a Topic ID to send notifications to that topic. Leave blank to use the general topic (Supergroups only)")]
|
||||
public int? TopicId { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Send Silently", Type = FieldType.Checkbox, HelpText = "Sends the message silently. Users will receive a notification with no sound")]
|
||||
public bool SendSilently { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Organizer
|
||||
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);
|
||||
|
||||
public static readonly Regex MovieTitleRegex = new Regex(@"(?<token>\{((?:(Movie|Original))(?<separator>[- ._])(Clean|Original)?(Title|Filename)(The)?)(?::(?<customFormat>[a-z0-9|]+))?\})",
|
||||
public static readonly Regex MovieTitleRegex = new Regex(@"(?<token>\{((?:(Movie|Original))(?<separator>[- ._])(Clean)?(Original)?(Title|Filename)(The)?)(?::(?<customFormat>[a-z0-9|]+))?\})",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled);
|
||||
@@ -247,7 +247,7 @@ namespace NzbDrone.Core.Organizer
|
||||
{
|
||||
tokenHandlers["{Movie Title}"] = m => GetLanguageTitle(movie, m.CustomFormat);
|
||||
tokenHandlers["{Movie CleanTitle}"] = m => CleanTitle(GetLanguageTitle(movie, m.CustomFormat));
|
||||
tokenHandlers["{Movie Title The}"] = m => TitleThe(movie.Title);
|
||||
tokenHandlers["{Movie TitleThe}"] = m => TitleThe(movie.Title);
|
||||
tokenHandlers["{Movie TitleFirstCharacter}"] = m => TitleThe(movie.Title).Substring(0, 1).FirstCharToUpper();
|
||||
tokenHandlers["{Movie OriginalTitle}"] = m => movie.MovieMetadata.Value.OriginalTitle ?? string.Empty;
|
||||
tokenHandlers["{Movie CleanOriginalTitle}"] = m => CleanTitle(movie.MovieMetadata.Value.OriginalTitle) ?? string.Empty;
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Parser.RomanNumerals;
|
||||
@@ -214,31 +213,15 @@ namespace NzbDrone.Core.Parser
|
||||
possibleTitles.AddRange(searchCriteria.Movie.MovieMetadata.Value.AlternativeTitles.Select(t => t.CleanTitle));
|
||||
possibleTitles.AddRange(searchCriteria.Movie.MovieMetadata.Value.Translations.Select(t => t.CleanTitle));
|
||||
|
||||
var cleanTitle = parsedMovieInfo.PrimaryMovieTitle.CleanMovieTitle();
|
||||
var cleanTitles = parsedMovieInfo.MovieTitles.Select(t => t.CleanMovieTitle()).ToArray();
|
||||
|
||||
foreach (var title in possibleTitles)
|
||||
if (possibleTitles.Any(pt =>
|
||||
cleanTitles.Contains(pt)
|
||||
|| _arabicRomanNumeralMappings.Any(mn =>
|
||||
cleanTitles.Contains(pt.Replace(mn.ArabicNumeralAsString, mn.RomanNumeralLowerCase))
|
||||
|| cleanTitles.Any(t => t.Replace(mn.ArabicNumeralAsString, mn.RomanNumeralLowerCase) == pt))))
|
||||
{
|
||||
if (title == cleanTitle)
|
||||
{
|
||||
possibleMovie = searchCriteria.Movie;
|
||||
}
|
||||
|
||||
foreach (var numeralMapping in _arabicRomanNumeralMappings)
|
||||
{
|
||||
var arabicNumeral = numeralMapping.ArabicNumeralAsString;
|
||||
var romanNumeral = numeralMapping.RomanNumeralLowerCase;
|
||||
|
||||
// _logger.Debug(cleanTitle);
|
||||
if (title.Replace(arabicNumeral, romanNumeral) == cleanTitle)
|
||||
{
|
||||
possibleMovie = searchCriteria.Movie;
|
||||
}
|
||||
|
||||
if (title == cleanTitle.Replace(arabicNumeral, romanNumeral))
|
||||
{
|
||||
possibleMovie = searchCriteria.Movie;
|
||||
}
|
||||
}
|
||||
possibleMovie = searchCriteria.Movie;
|
||||
}
|
||||
|
||||
if (possibleMovie != null)
|
||||
|
||||
@@ -5,24 +5,24 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||
<PackageReference Include="Equ" Version="2.3.0" />
|
||||
<PackageReference Include="MailKit" Version="2.15.0" />
|
||||
<PackageReference Include="MailKit" Version="3.6.0" />
|
||||
<PackageReference Include="Npgsql" Version="6.0.3" />
|
||||
<PackageReference Include="Servarr.FFMpegCore" Version="4.7.0-26" />
|
||||
<PackageReference Include="Servarr.FFprobe" Version="5.1.2.106" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||
<PackageReference Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
|
||||
<PackageReference Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
|
||||
<PackageReference Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
|
||||
<PackageReference Include="FluentValidation" Version="8.6.2" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||
<PackageReference Include="MonoTorrent" Version="2.0.7" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.5" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.7" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Common\Radarr.Common.csproj" />
|
||||
|
||||
@@ -182,9 +182,7 @@ namespace NzbDrone.Core.RootFolders
|
||||
|
||||
public string GetBestRootFolderPath(string path)
|
||||
{
|
||||
var possibleRootFolder = All().Where(r => r.Path.IsParentPath(path))
|
||||
.OrderByDescending(r => r.Path.Length)
|
||||
.FirstOrDefault();
|
||||
var possibleRootFolder = All().Where(r => r.Path.IsParentPath(path)).MaxBy(r => r.Path.Length);
|
||||
|
||||
if (possibleRootFolder == null)
|
||||
{
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.3.0" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.3.0" />
|
||||
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="6.1.0" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.3.4" />
|
||||
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="6.1.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Common\Radarr.Common.csproj" />
|
||||
|
||||
@@ -224,6 +224,8 @@ namespace NzbDrone.Host
|
||||
appFolderFactory.Register();
|
||||
pidFileProvider.Write();
|
||||
|
||||
configFileProvider.EnsureDefaultConfigFile();
|
||||
|
||||
reconfigureLogging.Reconfigure();
|
||||
|
||||
EnsureSingleInstance(false, startupContext, singleInstancePolicy);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.16" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" />
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" Version="5.3.0" />
|
||||
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="6.1.0" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.3.4" />
|
||||
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="6.1.1" />
|
||||
<PackageReference Include="NLog" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -26,15 +26,15 @@ namespace Radarr.Api.V3.Calendar
|
||||
}
|
||||
|
||||
[HttpGet("Radarr.ics")]
|
||||
public IActionResult GetCalendarFeed(int pastDays = 7, int futureDays = 28, string tagList = "", bool unmonitored = false)
|
||||
public IActionResult GetCalendarFeed(int pastDays = 7, int futureDays = 28, string tags = "", bool unmonitored = false)
|
||||
{
|
||||
var start = DateTime.Today.AddDays(-pastDays);
|
||||
var end = DateTime.Today.AddDays(futureDays);
|
||||
var tags = new List<int>();
|
||||
var parsedTags = new List<int>();
|
||||
|
||||
if (tagList.IsNotNullOrWhiteSpace())
|
||||
if (tags.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
tags.AddRange(tagList.Split(',').Select(_tagService.GetTag).Select(t => t.Id));
|
||||
parsedTags.AddRange(tags.Split(',').Select(_tagService.GetTag).Select(t => t.Id));
|
||||
}
|
||||
|
||||
var movies = _movieService.GetMoviesBetweenDates(start, end, unmonitored);
|
||||
@@ -49,7 +49,7 @@ namespace Radarr.Api.V3.Calendar
|
||||
|
||||
foreach (var movie in movies.OrderBy(v => v.Added))
|
||||
{
|
||||
if (tags.Any() && tags.None(movie.Tags.Contains))
|
||||
if (parsedTags.Any() && parsedTags.None(movie.Tags.Contains))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,9 @@ namespace Radarr.Api.V3.ManualImport
|
||||
|
||||
item.Movie = processedItem.Movie.ToResource(0);
|
||||
item.Rejections = processedItem.Rejections;
|
||||
if (item.Languages.Single() == Language.Unknown)
|
||||
|
||||
if (item.Languages?.Count <= 1 && (item.Languages?.SingleOrDefault() ?? Language.Unknown) == Language.Unknown &&
|
||||
processedItem.Languages.Any())
|
||||
{
|
||||
item.Languages = processedItem.Languages;
|
||||
}
|
||||
@@ -45,7 +47,7 @@ namespace Radarr.Api.V3.ManualImport
|
||||
item.Quality = processedItem.Quality;
|
||||
}
|
||||
|
||||
if (item.ReleaseGroup.IsNotNullOrWhiteSpace())
|
||||
if (item.ReleaseGroup.IsNullOrWhiteSpace())
|
||||
{
|
||||
item.ReleaseGroup = processedItem.ReleaseGroup;
|
||||
}
|
||||
|
||||
@@ -562,7 +562,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tagList",
|
||||
"name": "tags",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
@@ -39,7 +41,10 @@ namespace Radarr.Http.Frontend.Mappers
|
||||
contentType = "application/octet-stream";
|
||||
}
|
||||
|
||||
return new FileStreamResult(GetContentStream(filePath), contentType);
|
||||
return new FileStreamResult(GetContentStream(filePath), new MediaTypeHeaderValue(contentType)
|
||||
{
|
||||
Encoding = contentType == "text/plain" ? Encoding.UTF8 : null
|
||||
});
|
||||
}
|
||||
|
||||
_logger.Warn("File {0} not found", filePath);
|
||||
|
||||
@@ -1180,10 +1180,10 @@
|
||||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@microsoft/signalr@6.0.8":
|
||||
version "6.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-6.0.8.tgz#e55b4488b352c580c08bab381110cdfe42d4e53c"
|
||||
integrity sha512-TgvNf8opondnX6nqN2AvWThOMl+CjPwxonhAbBpmLveECcnTuBibJQdiCUA4iwjBz26k2HIiiJ25DYgqXIJVQw==
|
||||
"@microsoft/signalr@6.0.16":
|
||||
version "6.0.16"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-6.0.16.tgz#d36498a9b16bf11c0e9213d77d24c0ad8ebffa47"
|
||||
integrity sha512-wekzRtt2Ti38Ja0OQwLE0EKN0Zm7RI9VilrungwHe5Eej1IwnRYuhpauAqNtwwP3CY2j7uFT4XUk74E2vythTQ==
|
||||
dependencies:
|
||||
abort-controller "^3.0.0"
|
||||
eventsource "^1.0.7"
|
||||
|
||||
Reference in New Issue
Block a user