Compare commits

...

16 commits

Author SHA1 Message Date
Mark McDowall
52972e7efc
Add private IPv6 networks 2025-11-03 07:35:36 -08:00
Mark McDowall
8c50919499
Bump version to 4.0.16 2025-10-28 15:53:41 -07:00
Polgonite
fdc07a47b1
Fixed: qBittorrent /login API success check 2025-10-26 08:31:50 -07:00
Collin Heist
36225c3709
Fixed: Prevent modals from overflowing screen width
Closes #8085
2025-10-26 08:31:50 -07:00
康小广
bc037ae356
Follow redirects when fetching Custom Lists 2025-10-26 08:31:50 -07:00
Mark McDowall
77a335de30
Fixed: Default runtime to 45 minutes if unavailable when importing episode files
Closes #7780
2025-10-26 08:31:50 -07:00
Mark McDowall
88d56361c4
Add XML declaration and clean up Kodi metadata generation
Closes #7753
2025-10-26 08:31:50 -07:00
Mark McDowall
d10107739b
Set known networks to RFC 1918 ranges during startup 2025-10-25 19:18:43 -07:00
Mark McDowall
7db7567c8e
Bump version to 4.0.15 2025-06-09 17:19:54 -07:00
Michael Peleshenko
2b2b973b30
Fixed: Prevent series without IMDB ID from being removed erroneously 2025-06-09 17:19:10 -07:00
Mark McDowall
bb954a7424
Fixed: Trakt Import List authentication after 24 hours
Closes #7874
2025-06-09 17:18:54 -07:00
Mark McDowall
640e3e5d44
Bump version to 4.0.14 2025-03-15 09:43:34 -07:00
Mark McDowall
1260d3c800
Upgrade ImageSharp 2025-03-15 09:29:03 -07:00
v3DJG6GL
feeed9a7cf
New: .arj and .lzh extensions are potentially dangerous 2025-03-15 09:25:40 -07:00
Mark McDowall
c8cb74a976
Fixed: Downloads failed for file contents will be removed from client 2025-03-08 19:59:13 -08:00
Stevie Robinson
7193acb5ee
Fixed: Improve rejected download handling 2025-03-08 19:59:07 -08:00
16 changed files with 278 additions and 217 deletions

View file

@ -22,7 +22,7 @@ env:
FRAMEWORK: net6.0 FRAMEWORK: net6.0
RAW_BRANCH_NAME: ${{ github.head_ref || github.ref_name }} RAW_BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
SONARR_MAJOR_VERSION: 4 SONARR_MAJOR_VERSION: 4
VERSION: 4.0.13 VERSION: 4.0.16
jobs: jobs:
backend: backend:

View file

@ -19,6 +19,7 @@
.modal { .modal {
position: relative; position: relative;
display: flex; display: flex;
max-width: 90%;
max-height: 90%; max-height: 90%;
border-radius: 6px; border-radius: 6px;
opacity: 1; opacity: 1;

View file

@ -390,5 +390,12 @@ public virtual HttpRequestBuilder AddFormUpload(string name, string fileName, by
return this; return this;
} }
public virtual HttpRequestBuilder AllowRedirect(bool allowAutoRedirect = true)
{
AllowAutoRedirect = allowAutoRedirect;
return this;
}
} }
} }

View file

@ -0,0 +1,9 @@
using System.IO;
using System.Text;
namespace NzbDrone.Common;
public class Utf8StringWriter : StringWriter
{
public override Encoding Encoding => Encoding.UTF8;
}

View file

@ -213,5 +213,16 @@ public void should_use_runtime_from_episode_over_series()
Subject.IsSample(_localEpisode).Should().Be(DetectSampleResult.Sample); Subject.IsSample(_localEpisode).Should().Be(DetectSampleResult.Sample);
} }
[Test]
public void should_default_to_45_minutes_if_runtime_is_zero()
{
GivenRuntime(120);
_localEpisode.Series.Runtime = 0;
_localEpisode.Episodes.First().Runtime = 0;
Subject.IsSample(_localEpisode).Should().Be(DetectSampleResult.Sample);
}
} }
} }

View file

@ -425,8 +425,8 @@ private void AuthenticateClient(HttpRequestBuilder requestBuilder, QBittorrentSe
} }
catch (HttpException ex) catch (HttpException ex)
{ {
_logger.Debug("qbitTorrent authentication failed."); _logger.Debug(ex, "qbitTorrent authentication failed.");
if (ex.Response.StatusCode == HttpStatusCode.Forbidden) if (ex.Response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
{ {
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.", ex); throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.", ex);
} }
@ -438,7 +438,7 @@ private void AuthenticateClient(HttpRequestBuilder requestBuilder, QBittorrentSe
throw new DownloadClientUnavailableException("Failed to connect to qBittorrent, please check your settings.", ex); throw new DownloadClientUnavailableException("Failed to connect to qBittorrent, please check your settings.", ex);
} }
if (response.Content != "Ok.") if (response.Content.IsNotNullOrWhiteSpace() && response.Content != "Ok.")
{ {
// returns "Fails." on bad login // returns "Fails." on bad login
_logger.Debug("qbitTorrent authentication failed."); _logger.Debug("qbitTorrent authentication failed.");

View file

@ -1,4 +1,5 @@
using System.Linq; using System.Linq;
using NLog;
using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.MediaFiles.EpisodeImport;
@ -13,15 +14,17 @@ public interface IRejectedImportService
public class RejectedImportService : IRejectedImportService public class RejectedImportService : IRejectedImportService
{ {
private readonly ICachedIndexerSettingsProvider _cachedIndexerSettingsProvider; private readonly ICachedIndexerSettingsProvider _cachedIndexerSettingsProvider;
private readonly Logger _logger;
public RejectedImportService(ICachedIndexerSettingsProvider cachedIndexerSettingsProvider) public RejectedImportService(ICachedIndexerSettingsProvider cachedIndexerSettingsProvider, Logger logger)
{ {
_cachedIndexerSettingsProvider = cachedIndexerSettingsProvider; _cachedIndexerSettingsProvider = cachedIndexerSettingsProvider;
_logger = logger;
} }
public bool Process(TrackedDownload trackedDownload, ImportResult importResult) public bool Process(TrackedDownload trackedDownload, ImportResult importResult)
{ {
if (importResult.Result != ImportResultType.Rejected || importResult.ImportDecision.LocalEpisode == null || trackedDownload.RemoteEpisode?.Release == null) if (importResult.Result != ImportResultType.Rejected || trackedDownload.RemoteEpisode?.Release == null)
{ {
return false; return false;
} }
@ -38,11 +41,13 @@ public bool Process(TrackedDownload trackedDownload, ImportResult importResult)
if (rejectionReason == ImportRejectionReason.DangerousFile && if (rejectionReason == ImportRejectionReason.DangerousFile &&
indexerSettings.FailDownloads.Contains(FailDownloads.PotentiallyDangerous)) indexerSettings.FailDownloads.Contains(FailDownloads.PotentiallyDangerous))
{ {
_logger.Trace("Download '{0}' contains potentially dangerous file, marking as failed", trackedDownload.DownloadItem.Title);
trackedDownload.Fail(); trackedDownload.Fail();
} }
else if (rejectionReason == ImportRejectionReason.ExecutableFile && else if (rejectionReason == ImportRejectionReason.ExecutableFile &&
indexerSettings.FailDownloads.Contains(FailDownloads.Executables)) indexerSettings.FailDownloads.Contains(FailDownloads.Executables))
{ {
_logger.Trace("Download '{0}' contains executable file, marking as failed", trackedDownload.DownloadItem.Title);
trackedDownload.Fail(); trackedDownload.Fail();
} }
else else

View file

@ -40,6 +40,9 @@ public void Fail()
{ {
Status = TrackedDownloadStatus.Error; Status = TrackedDownloadStatus.Error;
State = TrackedDownloadState.FailedPending; State = TrackedDownloadState.FailedPending;
// Set CanBeRemoved to allow the failed item to be removed from the client
DownloadItem.CanBeRemoved = true;
} }
} }

View file

@ -8,6 +8,7 @@
using System.Xml; using System.Xml;
using System.Xml.Linq; using System.Xml.Linq;
using NLog; using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
@ -149,13 +150,7 @@ public override MetadataFileResult SeriesMetadata(Series series, SeriesMetadataR
if (Settings.SeriesMetadata) if (Settings.SeriesMetadata)
{ {
_logger.Debug("Generating Series Metadata for: {0}", series.Title); _logger.Debug("Generating Series Metadata for: {0}", series.Title);
var sb = new StringBuilder();
var xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Indent = false;
using (var xw = XmlWriter.Create(sb, xws))
{
var tvShow = new XElement("tvshow"); var tvShow = new XElement("tvshow");
tvShow.Add(new XElement("title", series.Title)); tvShow.Add(new XElement("title", series.Title));
@ -248,11 +243,23 @@ public override MetadataFileResult SeriesMetadata(Series series, SeriesMetadataR
tvShow.Add(new XElement("episodeguide", JsonSerializer.Serialize(episodeGuide, serializerSettings))); tvShow.Add(new XElement("episodeguide", JsonSerializer.Serialize(episodeGuide, serializerSettings)));
} }
var doc = new XDocument(tvShow); var doc = new XDocument(tvShow)
doc.Save(xw); {
Declaration = new XDeclaration("1.0", "UTF-8", "yes"),
};
xmlResult += doc.ToString(); var sb = new StringBuilder();
} using var sw = new Utf8StringWriter();
using var xw = XmlWriter.Create(sw, new XmlWriterSettings
{
Encoding = Encoding.UTF8,
Indent = true
});
doc.Save(xw);
xw.Flush();
xmlResult += sw.ToString();
} }
if (Settings.SeriesMetadataUrl) if (Settings.SeriesMetadataUrl)
@ -280,16 +287,19 @@ public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile ep
var watched = GetExistingWatchedStatus(series, episodeFile.RelativePath); var watched = GetExistingWatchedStatus(series, episodeFile.RelativePath);
var xmlResult = string.Empty; var xmlResult = string.Empty;
var xws = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
Indent = true
};
foreach (var episode in episodeFile.Episodes.Value) foreach (var episode in episodeFile.Episodes.Value)
{ {
var sb = new StringBuilder(); var doc = new XDocument
var xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Indent = false;
using (var xw = XmlWriter.Create(sb, xws))
{ {
var doc = new XDocument(); Declaration = new XDeclaration("1.0", "UTF-8", "yes")
};
var image = episode.Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot); var image = episode.Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot);
var details = new XElement("episodedetails"); var details = new XElement("episodedetails");
@ -381,13 +391,16 @@ public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile ep
// details.Add(new XElement("credits", tvdbEpisode.Writer.FirstOrDefault())); // details.Add(new XElement("credits", tvdbEpisode.Writer.FirstOrDefault()));
// details.Add(new XElement("director", tvdbEpisode.Directors.FirstOrDefault())); // details.Add(new XElement("director", tvdbEpisode.Directors.FirstOrDefault()));
using var sw = new Utf8StringWriter();
using var xw = XmlWriter.Create(sw, xws);
doc.Add(details); doc.Add(details);
doc.Save(xw); doc.Save(xw);
xw.Flush();
xmlResult += doc.ToString(); xmlResult += sw.ToString();
xmlResult += Environment.NewLine; xmlResult += Environment.NewLine;
} }
}
return new MetadataFileResult(GetEpisodeMetadataFilename(episodeFile.RelativePath), xmlResult.Trim(Environment.NewLine.ToCharArray())); return new MetadataFileResult(GetEpisodeMetadataFilename(episodeFile.RelativePath), xmlResult.Trim(Environment.NewLine.ToCharArray()));
} }

View file

@ -72,7 +72,7 @@ private List<TResource> Execute<TResource>(CustomSettings settings)
} }
var baseUrl = settings.BaseUrl.TrimEnd('/'); var baseUrl = settings.BaseUrl.TrimEnd('/');
var request = new HttpRequestBuilder(baseUrl).Accept(HttpAccept.Json).Build(); var request = new HttpRequestBuilder(baseUrl).Accept(HttpAccept.Json).AllowRedirect().Build();
var response = _httpClient.Get(request); var response = _httpClient.Get(request);
var results = JsonConvert.DeserializeObject<List<TResource>>(response.Content); var results = JsonConvert.DeserializeObject<List<TResource>>(response.Content);

View file

@ -305,7 +305,7 @@ private void CleanLibrary()
{ {
var seriesExists = allListItems.Where(l => var seriesExists = allListItems.Where(l =>
l.TvdbId == series.TvdbId || l.TvdbId == series.TvdbId ||
l.ImdbId == series.ImdbId || (l.ImdbId.IsNotNullOrWhiteSpace() && series.ImdbId.IsNotNullOrWhiteSpace() && l.ImdbId == series.ImdbId) ||
l.TmdbId == series.TmdbId || l.TmdbId == series.TmdbId ||
series.MalIds.Contains(l.MalId) || series.MalIds.Contains(l.MalId) ||
series.AniListIds.Contains(l.AniListId)).ToList(); series.AniListIds.Contains(l.AniListId)).ToList();

View file

@ -1,5 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization; using Newtonsoft.Json;
namespace NzbDrone.Core.ImportLists.Trakt namespace NzbDrone.Core.ImportLists.Trakt
{ {
@ -17,7 +17,7 @@ public class TraktSeriesResource
public string Title { get; set; } public string Title { get; set; }
public int? Year { get; set; } public int? Year { get; set; }
public TraktSeriesIdsResource Ids { get; set; } public TraktSeriesIdsResource Ids { get; set; }
[JsonPropertyName("aired_episodes")] [JsonProperty("aired_episodes")]
public int AiredEpisodes { get; set; } public int AiredEpisodes { get; set; }
} }
@ -44,11 +44,11 @@ public class TraktWatchedResponse : TraktResponse
public class RefreshRequestResponse public class RefreshRequestResponse
{ {
[JsonPropertyName("access_token")] [JsonProperty("access_token")]
public string AccessToken { get; set; } public string AccessToken { get; set; }
[JsonPropertyName("expires_in")] [JsonProperty("expires_in")]
public int ExpiresIn { get; set; } public int ExpiresIn { get; set; }
[JsonPropertyName("refresh_token")] [JsonProperty("refresh_token")]
public string RefreshToken { get; set; } public string RefreshToken { get; set; }
} }

View file

@ -66,6 +66,12 @@ public DetectSampleResult IsSample(LocalEpisode localEpisode)
return DetectSampleResult.Indeterminate; return DetectSampleResult.Indeterminate;
} }
if (runtime == 0)
{
_logger.Debug("Series runtime is 0, defaulting runtime to 45 minutes");
runtime = 45;
}
return IsSample(localEpisode.Path, localEpisode.MediaInfo.RunTime, runtime); return IsSample(localEpisode.Path, localEpisode.MediaInfo.RunTime, runtime);
} }

View file

@ -23,7 +23,9 @@ internal static class FileExtensions
private static List<string> _dangerousExtensions = new List<string> private static List<string> _dangerousExtensions = new List<string>
{ {
".arj",
".lnk", ".lnk",
".lzh",
".ps1", ".ps1",
".scr", ".scr",
".vbs", ".vbs",

View file

@ -20,7 +20,7 @@
<PackageReference Include="Servarr.FluentMigrator.Runner.SQLite" 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="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
<PackageReference Include="FluentValidation" Version="9.5.4" /> <PackageReference Include="FluentValidation" Version="9.5.4" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.3.4" /> <PackageReference Include="NLog" Version="5.3.4" />
<PackageReference Include="MonoTorrent" Version="2.0.7" /> <PackageReference Include="MonoTorrent" Version="2.0.7" />

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net;
using DryIoc; using DryIoc;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
@ -59,8 +60,11 @@ public void ConfigureServices(IServiceCollection services)
services.Configure<ForwardedHeadersOptions>(options => services.Configure<ForwardedHeadersOptions>(options =>
{ {
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost; options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
options.KnownNetworks.Clear(); options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8));
options.KnownProxies.Clear(); options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("172.16.0.0"), 12));
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("192.168.0.0"), 16));
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fc00::"), 7));
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fe80::"), 10));
}); });
services.AddRouting(options => options.LowercaseUrls = true); services.AddRouting(options => options.LowercaseUrls = true);