Compare commits

..

No commits in common. "develop" and "v2.1.2.5186" have entirely different histories.

22 changed files with 70 additions and 186 deletions

View file

@ -9,7 +9,7 @@ variables:
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '2.3.2' majorVersion: '2.1.2'
minorVersion: $[counter('minorVersion', 1)] minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)' prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)' buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
@ -18,9 +18,9 @@ variables:
dotnetVersion: '8.0.405' dotnetVersion: '8.0.405'
nodeVersion: '20.X' nodeVersion: '20.X'
innoVersion: '6.2.2' innoVersion: '6.2.2'
windowsImage: 'windows-2025' windowsImage: 'windows-2022'
linuxImage: 'ubuntu-24.04' linuxImage: 'ubuntu-22.04'
macImage: 'macOS-15' macImage: 'macOS-13'
trigger: trigger:
branches: branches:
@ -1158,7 +1158,7 @@ stages:
extraProperties: | extraProperties: |
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/** sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/**
sonar.coverage.exclusions=**/Prowlarr.Api.V1/**/* sonar.coverage.exclusions=**/Prowlarr.Api.V1/**/*
sonar.cs.cobertura.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.cobertura.xml sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
- bash: | - bash: |
./build.sh --backend -f net8.0 -r win-x64 ./build.sh --backend -f net8.0 -r win-x64
@ -1170,11 +1170,10 @@ stages:
- task: reportgenerator@5 - task: reportgenerator@5
displayName: Generate Coverage Report displayName: Generate Coverage Report
inputs: inputs:
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.cobertura.xml' reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml'
targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined' targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined'
reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges' reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges'
publishCodeCoverageResults: true publishCodeCoverageResults: true
sourcedirs: src
- stage: Report_Out - stage: Report_Out
dependsOn: dependsOn:

View file

@ -19,7 +19,6 @@
.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

@ -1,56 +0,0 @@
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import themes from 'Styles/Themes';
function createThemeSelector() {
return createSelector(
(state: AppState) => state.settings.ui.item.theme || window.Prowlarr.theme,
(theme) => theme
);
}
const useTheme = () => {
const selectedTheme = useSelector(createThemeSelector());
const [resolvedTheme, setResolvedTheme] = useState(selectedTheme);
useEffect(() => {
if (selectedTheme !== 'auto') {
setResolvedTheme(selectedTheme);
return;
}
const applySystemTheme = () => {
setResolvedTheme(
window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
);
};
applySystemTheme();
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', applySystemTheme);
return () => {
window
.matchMedia('(prefers-color-scheme: dark)')
.removeEventListener('change', applySystemTheme);
};
}, [selectedTheme]);
return resolvedTheme;
};
export default useTheme;
export const useThemeColor = (color: string) => {
const theme = useTheme();
const themeVariables = themes[theme];
// @ts-expect-error - themeVariables is a string indexable type
return themeVariables[color];
};

View file

@ -84,7 +84,7 @@
<Deterministic Condition="$(AssemblyVersion.EndsWith('*'))">False</Deterministic> <Deterministic Condition="$(AssemblyVersion.EndsWith('*'))">False</Deterministic>
<PathMap>$(MSBuildThisFileDirectory)=./</PathMap> <PathMap>$(MSBuildProjectDirectory)=./$(MSBuildProjectName)/</PathMap>
</PropertyGroup> </PropertyGroup>
<!-- Set the AssemblyConfiguration attribute for projects --> <!-- Set the AssemblyConfiguration attribute for projects -->
@ -123,11 +123,14 @@
<!-- Standard testing packages --> <!-- Standard testing packages -->
<ItemGroup Condition="'$(TestProject)'=='true'"> <ItemGroup Condition="'$(TestProject)'=='true'">
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="NUnit" Version="3.14.0" /> <PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="NunitXml.TestLogger" Version="3.1.20" /> <PackageReference Include="NunitXml.TestLogger" Version="3.0.131" />
</ItemGroup>
<ItemGroup Condition="'$(TestProject)'=='true' and '$(TargetFramework)'=='net8.0'">
<PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" />
</ItemGroup> </ItemGroup>
<PropertyGroup Condition="'$(ProwlarrProject)'=='true' and '$(EnableAnalyzers)'=='false'"> <PropertyGroup Condition="'$(ProwlarrProject)'=='true' and '$(EnableAnalyzers)'=='false'">

View file

@ -5,6 +5,8 @@
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" /> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="dotnet-bsd-crossbuild" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/dotnet-bsd-crossbuild/nuget/v3/index.json" /> <add key="dotnet-bsd-crossbuild" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/dotnet-bsd-crossbuild/nuget/v3/index.json" />
<add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" /> <add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" />
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
<add key="coverlet-nightly" value="https://pkgs.dev.azure.com/Servarr/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" />
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FluentMigrator/nuget/v3/index.json" /> <add key="FluentMigrator" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FluentMigrator/nuget/v3/index.json" />
</packageSources> </packageSources>
</configuration> </configuration>

View file

@ -8,7 +8,7 @@
<PackageReference Include="IPAddressRange" Version="6.2.0" /> <PackageReference Include="IPAddressRange" Version="6.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.4.0" /> <PackageReference Include="NLog" Version="5.4.0" />
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.3" /> <PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.3" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.4.0" /> <PackageReference Include="NLog.Extensions.Logging" Version="5.4.0" />
@ -16,11 +16,9 @@
<PackageReference Include="Sentry" Version="4.0.2" /> <PackageReference Include="Sentry" Version="4.0.2" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" /> <PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.4.2" /> <PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="SourceGear.sqlite3" Version="3.50.4.2" /> <PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="System.Data.SQLite" Version="2.0.2" />
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
<PackageReference Include="System.Text.Json" Version="8.0.6" />
<PackageReference Include="System.ValueTuple" Version="4.6.1" /> <PackageReference Include="System.ValueTuple" Version="4.6.1" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.1" /> <PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.1" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" /> <PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" /> <PackageReference Include="System.Runtime.Loader" Version="4.3.0" />

View file

@ -15,24 +15,24 @@ private HttpProxySettings GetProxySettings()
return new HttpProxySettings(ProxyType.Socks5, "localhost", 8080, "*.httpbin.org,google.com,172.16.0.0/12", true, null, null); return new HttpProxySettings(ProxyType.Socks5, "localhost", 8080, "*.httpbin.org,google.com,172.16.0.0/12", true, null, null);
} }
[TestCase("http://eu.httpbin.org/get")] [Test]
[TestCase("http://google.com/get")] public void should_bypass_proxy()
[TestCase("http://localhost:8654/get")]
[TestCase("http://172.21.0.1:8989/api/v3/indexer/schema")]
public void should_bypass_proxy(string url)
{ {
var settings = GetProxySettings(); var settings = GetProxySettings();
Subject.ShouldProxyBeBypassed(settings, new HttpUri(url)).Should().BeTrue(); Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://eu.httpbin.org/get")).Should().BeTrue();
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://google.com/get")).Should().BeTrue();
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://localhost:8654/get")).Should().BeTrue();
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://172.21.0.1:8989/api/v3/indexer/schema")).Should().BeTrue();
} }
[TestCase("http://bing.com/get")] [Test]
[TestCase("http://172.3.0.1:8989/api/v3/indexer/schema")] public void should_not_bypass_proxy()
public void should_not_bypass_proxy(string url)
{ {
var settings = GetProxySettings(); var settings = GetProxySettings();
Subject.ShouldProxyBeBypassed(settings, new HttpUri(url)).Should().BeFalse(); Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://bing.com/get")).Should().BeFalse();
Subject.ShouldProxyBeBypassed(settings, new HttpUri("http://172.3.0.1:8989/api/v3/indexer/schema")).Should().BeFalse();
} }
} }
} }

View file

@ -5,6 +5,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Dapper" Version="2.1.66" /> <PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="NBuilder" Version="6.1.0" /> <PackageReference Include="NBuilder" Version="6.1.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="YamlDotNet" Version="16.3.0" /> <PackageReference Include="YamlDotNet" Version="16.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -1,18 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Data.SQLite;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using Dapper; using Dapper;
using NLog;
using NzbDrone.Common.Instrumentation;
using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using Polly;
using Polly.Retry;
namespace NzbDrone.Core.Datastore namespace NzbDrone.Core.Datastore
{ {
@ -45,31 +40,12 @@ public interface IBasicRepository<TModel>
public class BasicRepository<TModel> : IBasicRepository<TModel> public class BasicRepository<TModel> : IBasicRepository<TModel>
where TModel : ModelBase, new() where TModel : ModelBase, new()
{ {
private static readonly ILogger Logger = NzbDroneLogger.GetLogger(typeof(BasicRepository<TModel>));
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly PropertyInfo _keyProperty; private readonly PropertyInfo _keyProperty;
private readonly List<PropertyInfo> _properties; private readonly List<PropertyInfo> _properties;
private readonly string _updateSql; private readonly string _updateSql;
private readonly string _insertSql; private readonly string _insertSql;
private static ResiliencePipeline RetryStrategy => new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
ShouldHandle = new PredicateBuilder().Handle<SQLiteException>(ex => ex.ResultCode == SQLiteErrorCode.Busy),
Delay = TimeSpan.FromMilliseconds(100),
MaxRetryAttempts = 3,
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
OnRetry = args =>
{
Logger.Warn(args.Outcome.Exception, "Failed writing to database. Retry #{0}", args.AttemptNumber);
return default;
}
})
.Build();
protected readonly IDatabase _database; protected readonly IDatabase _database;
protected readonly string _table; protected readonly string _table;
@ -210,9 +186,7 @@ private string GetInsertSql()
private TModel Insert(IDbConnection connection, IDbTransaction transaction, TModel model) private TModel Insert(IDbConnection connection, IDbTransaction transaction, TModel model)
{ {
SqlBuilderExtensions.LogQuery(_insertSql, model); SqlBuilderExtensions.LogQuery(_insertSql, model);
var multi = connection.QueryMultiple(_insertSql, model, transaction);
var multi = RetryStrategy.Execute(static (state, _) => state.connection.QueryMultiple(state._insertSql, state.model, state.transaction), (connection, _insertSql, model, transaction));
var multiRead = multi.Read(); var multiRead = multi.Read();
var id = (int)(multiRead.First().id ?? multiRead.First().Id); var id = (int)(multiRead.First().id ?? multiRead.First().Id);
_keyProperty.SetValue(model, id); _keyProperty.SetValue(model, id);
@ -409,7 +383,7 @@ private void UpdateFields(IDbConnection connection, IDbTransaction transaction,
SqlBuilderExtensions.LogQuery(sql, model); SqlBuilderExtensions.LogQuery(sql, model);
RetryStrategy.Execute(static (state, _) => state.connection.Execute(state.sql, state.model, transaction: state.transaction), (connection, sql, model, transaction)); connection.Execute(sql, model, transaction: transaction);
} }
private void UpdateFields(IDbConnection connection, IDbTransaction transaction, IList<TModel> models, List<PropertyInfo> propertiesToUpdate) private void UpdateFields(IDbConnection connection, IDbTransaction transaction, IList<TModel> models, List<PropertyInfo> propertiesToUpdate)
@ -421,7 +395,7 @@ private void UpdateFields(IDbConnection connection, IDbTransaction transaction,
SqlBuilderExtensions.LogQuery(sql, model); SqlBuilderExtensions.LogQuery(sql, model);
} }
RetryStrategy.Execute(static (state, _) => state.connection.Execute(state.sql, state.models, transaction: state.transaction), (connection, sql, models, transaction)); connection.Execute(sql, models, transaction: transaction);
} }
protected virtual SqlBuilder PagedBuilder() => Builder(); protected virtual SqlBuilder PagedBuilder() => Builder();

View file

@ -424,8 +424,8 @@ private void AuthenticateClient(HttpRequestBuilder requestBuilder, QBittorrentSe
} }
catch (HttpException ex) catch (HttpException ex)
{ {
_logger.Debug(ex, "qbitTorrent authentication failed."); _logger.Debug("qbitTorrent authentication failed.");
if (ex.Response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) if (ex.Response.StatusCode == HttpStatusCode.Forbidden)
{ {
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.", ex); throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.", ex);
} }
@ -437,9 +437,9 @@ 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);
} }
// returns "Fails." on bad login if (response.Content != "Ok.")
if (response.Content.IsNotNullOrWhiteSpace() && response.Content != "Ok.")
{ {
// returns "Fails." on bad login
_logger.Debug("qbitTorrent authentication failed."); _logger.Debug("qbitTorrent authentication failed.");
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent."); throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.");
} }

View file

@ -669,7 +669,7 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{ {
var advancedSeasonRegex = new Regex(@"\b(?:(?<season>\d+)(?:st|nd|rd|th) Season|Season (?<season>\d+))\b", RegexOptions.Compiled | RegexOptions.IgnoreCase); var advancedSeasonRegex = new Regex(@"\b(?:(?<season>\d+)(?:st|nd|rd|th) Season|Season (?<season>\d+))\b", RegexOptions.Compiled | RegexOptions.IgnoreCase);
var seasonCharactersRegex = new Regex(@"(I{2,})$", RegexOptions.Compiled); var seasonCharactersRegex = new Regex(@"(I{2,})$", RegexOptions.Compiled);
var seasonNumberRegex = new Regex(@"\b(?<!(Part|No\.)[- ._])(?<!\d[/])(?<!\#)(?:S)?(?<season>[2-9])$", RegexOptions.Compiled); var seasonNumberRegex = new Regex(@"\b(?<!Part[- ._])(?<!\d[/])(?:S)?(?<season>[2-9])$", RegexOptions.Compiled);
foreach (var title in titles) foreach (var title in titles)
{ {

View file

@ -1,4 +1,3 @@
using System;
using NLog; using NLog;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.Gazelle; using NzbDrone.Core.Indexers.Definitions.Gazelle;
@ -6,7 +5,6 @@
namespace NzbDrone.Core.Indexers.Definitions; namespace NzbDrone.Core.Indexers.Definitions;
[Obsolete("Moved to YML for Cardigann")]
public class CGPeers : GazelleBase<GazelleSettings> public class CGPeers : GazelleBase<GazelleSettings>
{ {
public override string Name => "CGPeers"; public override string Name => "CGPeers";

View file

@ -35,16 +35,6 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
throw new RequestLimitReachedException(indexerResponse, "PTP Query Limit Reached. Please try again later."); throw new RequestLimitReachedException(indexerResponse, "PTP Query Limit Reached. Please try again later.");
} }
if (httpResponse.StatusCode == HttpStatusCode.TooManyRequests && indexerResponse.Content.Contains("We are not a TV indexer"))
{
throw new IndexerException(indexerResponse, "Invalid indexer request: We are not a TV indexer", HttpStatusCode.BadRequest);
}
if (httpResponse.StatusCode == HttpStatusCode.TooManyRequests)
{
throw new RequestLimitReachedException(indexerResponse, "PTP Request Limit Reached. Please try again later.");
}
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request"); throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request");
} }

View file

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using System.Net;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
@ -116,7 +115,6 @@ private IEnumerable<IndexerRequest> GetRequest(string searchTerm, SearchCriteria
var request = new IndexerRequest(searchUrl, HttpAccept.Json); var request = new IndexerRequest(searchUrl, HttpAccept.Json);
request.HttpRequest.Headers.Add("ApiUser", _settings.APIUser); request.HttpRequest.Headers.Add("ApiUser", _settings.APIUser);
request.HttpRequest.Headers.Add("ApiKey", _settings.APIKey); request.HttpRequest.Headers.Add("ApiKey", _settings.APIKey);
request.HttpRequest.SuppressHttpErrorStatusCodes = new[] { HttpStatusCode.TooManyRequests };
yield return request; yield return request;
} }

View file

@ -23,8 +23,7 @@ public class RevolutionTT : TorrentIndexerBase<UserPassTorrentBaseSettings>
{ {
public override string Name => "RevolutionTT"; public override string Name => "RevolutionTT";
public override string[] IndexerUrls => new[] { "https://revott.me/" }; public override string[] IndexerUrls => new[] { "https://revolutiontt.me/" };
public override string[] LegacyUrls => new[] { "https://revolutiontt.me/" };
public override string Description => "The Revolution has begun"; public override string Description => "The Revolution has begun";
private string LoginUrl => Settings.BaseUrl + "takelogin.php"; private string LoginUrl => Settings.BaseUrl + "takelogin.php";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private; public override IndexerPrivacy Privacy => IndexerPrivacy.Private;

View file

@ -1541,7 +1541,7 @@ private IEnumerable<IndexerRequest> GetPagedRequests(string term, string[] track
if (season != 0) if (season != 0)
{ {
searchString += " ТВ | Сезон: " + season; searchString += " Сезон: " + season;
} }
parameters.Set("nm", searchString); parameters.Set("nm", searchString);
@ -1712,8 +1712,6 @@ public class RuTrackerTitleParser
private readonly Regex _tvTitleRusSeasonRegex = new(@"Сезон\s*[:]*\s+(\d+(?:-\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase); private readonly Regex _tvTitleRusSeasonRegex = new(@"Сезон\s*[:]*\s+(\d+(?:-\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly Regex _tvTitleRusEpisodeOfRegex = new(@"(?:Серии|Эпизод|Выпуски)+\s*[:]*\s+(\d+(?:-\d+)?)\s*из\s*([\w?])", RegexOptions.Compiled | RegexOptions.IgnoreCase); private readonly Regex _tvTitleRusEpisodeOfRegex = new(@"(?:Серии|Эпизод|Выпуски)+\s*[:]*\s+(\d+(?:-\d+)?)\s*из\s*([\w?])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly Regex _tvTitleRusEpisodeRegex = new(@"(?:Серии|Эпизод|Выпуски)+\s*[:]*\s+(\d+(?:-\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase); private readonly Regex _tvTitleRusEpisodeRegex = new(@"(?:Серии|Эпизод|Выпуски)+\s*[:]*\s+(\d+(?:-\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly Regex _tvTitleRusSeasonAnimeRegex = new(@"ТВ[-]*(?:(\d+))", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly Regex _tvTitleRusEpisodeAnimeOfRegex = new(@"\[(\d+(\+\d+)?)\s+из\s+(\d+(\+\d+)?)\]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public string Parse(string title, public string Parse(string title,
ICollection<IndexerCategory> categories, ICollection<IndexerCategory> categories,
@ -1738,8 +1736,6 @@ public string Parse(string title,
title = _tvTitleRusSeasonRegex.Replace(title, "S$1"); title = _tvTitleRusSeasonRegex.Replace(title, "S$1");
title = _tvTitleRusEpisodeOfRegex.Replace(title, "E$1 of $2"); title = _tvTitleRusEpisodeOfRegex.Replace(title, "E$1 of $2");
title = _tvTitleRusEpisodeRegex.Replace(title, "E$1"); title = _tvTitleRusEpisodeRegex.Replace(title, "E$1");
title = _tvTitleRusSeasonAnimeRegex.Replace(title, "S$1");
title = _tvTitleRusEpisodeAnimeOfRegex.Replace(title, "E$1 of $3");
} }
else if (IsAnyMovieCategory(categories)) else if (IsAnyMovieCategory(categories))
{ {

View file

@ -27,8 +27,7 @@ namespace NzbDrone.Core.Indexers.Definitions;
public class Shazbat : TorrentIndexerBase<ShazbatSettings> public class Shazbat : TorrentIndexerBase<ShazbatSettings>
{ {
public override string Name => "Shazbat"; public override string Name => "Shazbat";
public override string[] IndexerUrls => new[] { "https://www.shazbat.tube/" }; public override string[] IndexerUrls => new[] { "https://www.shazbat.tv/" };
public override string[] LegacyUrls => new[] { "https://www.shazbat.tv/" };
public override string Description => "Shazbat is a PRIVATE Torrent Tracker with highly curated TV content"; public override string Description => "Shazbat is a PRIVATE Torrent Tracker with highly curated TV content";
public override string Language => "en-US"; public override string Language => "en-US";
public override Encoding Encoding => Encoding.UTF8; public override Encoding Encoding => Encoding.UTF8;
@ -69,8 +68,8 @@ protected override async Task DoLogin()
.AddFormParameter("referer", "") .AddFormParameter("referer", "")
.AddFormParameter("query", "") .AddFormParameter("query", "")
.AddFormParameter("tv_timezone", "0") .AddFormParameter("tv_timezone", "0")
.AddFormParameter("username", Settings.Username) .AddFormParameter("tv_login", Settings.Username)
.AddFormParameter("password", Settings.Password) .AddFormParameter("tv_password", Settings.Password)
.SetHeader("Content-Type", "application/x-www-form-urlencoded") .SetHeader("Content-Type", "application/x-www-form-urlencoded")
.SetHeader("Referer", loginUrl) .SetHeader("Referer", loginUrl)
.Build(); .Build();
@ -92,9 +91,9 @@ protected override async Task DoLogin()
_logger.Debug("Authentication succeeded."); _logger.Debug("Authentication succeeded.");
} }
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse) protected override bool CheckIfLoginNeeded(HttpResponse response)
{ {
return (httpResponse.HasHttpRedirect && httpResponse.RedirectUrl.ContainsIgnoreCase("login")) || httpResponse.Content.ContainsIgnoreCase("sign in now"); return response.Content.ContainsIgnoreCase("sign in now");
} }
private IndexerCapabilities SetCapabilities() private IndexerCapabilities SetCapabilities()
@ -202,7 +201,7 @@ private static string FixSearchTerm(string term)
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; } public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
} }
public partial class ShazbatParser : IParseIndexerResponse public class ShazbatParser : IParseIndexerResponse
{ {
private readonly ProviderDefinition _definition; private readonly ProviderDefinition _definition;
private readonly ShazbatSettings _settings; private readonly ShazbatSettings _settings;
@ -210,10 +209,8 @@ public partial class ShazbatParser : IParseIndexerResponse
private readonly IIndexerHttpClient _httpClient; private readonly IIndexerHttpClient _httpClient;
private readonly Logger _logger; private readonly Logger _logger;
private readonly HashSet<string> _hdResolutions = ["1080p", "1080i", "720p"]; private readonly Regex _torrentInfoRegex = new(@"\((?<size>\d+)\):(?<seeders>\d+) \/ :(?<leechers>\d+)$", RegexOptions.Compiled);
private readonly HashSet<string> _hdResolutions = new() { "1080p", "1080i", "720p" };
[GeneratedRegex(@"\((?<size>\d+)\)\s*:(?<seeders>\d+) \/ :(?<leechers>\d+)$", RegexOptions.Compiled)]
private static partial Regex TorrentInfoRegex();
public ShazbatParser(ProviderDefinition definition, ShazbatSettings settings, TimeSpan rateLimit, IIndexerHttpClient httpClient, Logger logger) public ShazbatParser(ProviderDefinition definition, ShazbatSettings settings, TimeSpan rateLimit, IIndexerHttpClient httpClient, Logger logger)
{ {
@ -280,8 +277,7 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
var releaseRequest = new IndexerRequest(showRequest); var releaseRequest = new IndexerRequest(showRequest);
var releaseResponse = new IndexerResponse(releaseRequest, _httpClient.ExecuteProxied(releaseRequest.HttpRequest, _definition)); var releaseResponse = new IndexerResponse(releaseRequest, _httpClient.ExecuteProxied(releaseRequest.HttpRequest, _definition));
if ((releaseResponse.HttpResponse.HasHttpRedirect && releaseResponse.HttpResponse.RedirectUrl.ContainsIgnoreCase("login")) || if (releaseResponse.HttpResponse.Content.ContainsIgnoreCase("sign in now"))
releaseResponse.HttpResponse.Content.ContainsIgnoreCase("sign in now"))
{ {
// Remove cookie cache // Remove cookie cache
CookiesUpdater(null, null); CookiesUpdater(null, null);
@ -327,15 +323,13 @@ private IList<ReleaseInfo> ParseResults(IndexerResponse indexerResponse, bool ha
var title = ParseTitle(row.QuerySelector("td:nth-of-type(3)")); var title = ParseTitle(row.QuerySelector("td:nth-of-type(3)"));
var infoString = row.QuerySelector("td:nth-of-type(4)")?.TextContent.Trim() ?? string.Empty; var infoString = row.QuerySelector("td:nth-of-type(4)")?.TextContent.Trim() ?? string.Empty;
var matchInfo = TorrentInfoRegex().Match(infoString); var matchInfo = _torrentInfoRegex.Match(infoString);
var size = matchInfo.Groups["size"].Success && long.TryParse(matchInfo.Groups["size"].Value, out var outSize) ? outSize : 0; var size = matchInfo.Groups["size"].Success && long.TryParse(matchInfo.Groups["size"].Value, out var outSize) ? outSize : 0;
var seeders = matchInfo.Groups["seeders"].Success && int.TryParse(matchInfo.Groups["seeders"].Value, out var outSeeders) ? outSeeders : 0; var seeders = matchInfo.Groups["seeders"].Success && int.TryParse(matchInfo.Groups["seeders"].Value, out var outSeeders) ? outSeeders : 0;
var leechers = matchInfo.Groups["leechers"].Success && int.TryParse(matchInfo.Groups["leechers"].Value, out var outLeechers) ? outLeechers : 0; var leechers = matchInfo.Groups["leechers"].Success && int.TryParse(matchInfo.Groups["leechers"].Value, out var outLeechers) ? outLeechers : 0;
var dateTimestamp = row.QuerySelector(".datetime[data-timestamp]")?.GetAttribute("data-timestamp"); var dateTimestamp = row.QuerySelector(".datetime[data-timestamp]")?.GetAttribute("data-timestamp");
publishDate = dateTimestamp != null && ParseUtil.TryCoerceLong(dateTimestamp, out var timestamp) publishDate = dateTimestamp != null && ParseUtil.TryCoerceDouble(dateTimestamp, out var timestamp) ? DateTimeUtil.UnixTimestampToDateTime(timestamp) : publishDate.AddMinutes(-1);
? DateTimeUtil.UnixTimestampToDateTime(timestamp)
: publishDate.AddMinutes(-1);
var release = new TorrentInfo var release = new TorrentInfo
{ {
@ -369,10 +363,10 @@ private static string ParseTitle(IElement titleRow)
return title?.TextContent.Trim(); return title?.TextContent.Trim();
} }
private List<IndexerCategory> ParseCategories(string title) protected virtual List<IndexerCategory> ParseCategories(string title)
{ {
return var categories = new List<IndexerCategory>
[ {
NewznabStandardCategory.TV, NewznabStandardCategory.TV,
title switch title switch
{ {
@ -380,7 +374,9 @@ _ when _hdResolutions.Any(title.Contains) => NewznabStandardCategory.TVHD,
_ when title.Contains("2160p") => NewznabStandardCategory.TVUHD, _ when title.Contains("2160p") => NewznabStandardCategory.TVUHD,
_ => NewznabStandardCategory.TVSD _ => NewznabStandardCategory.TVSD
} }
]; };
return categories;
} }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; } public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }

View file

@ -662,13 +662,5 @@
"DownloadClientFloodSettingsTagsHelpText": "Initiële tags van een download. Om herkend te worden, moet een download alle initiële tags hebben. Dit voorkomt conflicten met niet-gerelateerde downloads.", "DownloadClientFloodSettingsTagsHelpText": "Initiële tags van een download. Om herkend te worden, moet een download alle initiële tags hebben. Dit voorkomt conflicten met niet-gerelateerde downloads.",
"IndexerNewznabSettingsVipExpirationHelpText": "Voer de datum in (jjjj-mm-dd) voor VIP-vervaldatum of laat leeg. {appName} zal 1 week voor het verstrijken van de VIP-periode een melding geven", "IndexerNewznabSettingsVipExpirationHelpText": "Voer de datum in (jjjj-mm-dd) voor VIP-vervaldatum of laat leeg. {appName} zal 1 week voor het verstrijken van de VIP-periode een melding geven",
"DownloadClientSettingsDefaultCategorySubFolderHelpText": "Standaard fallback-categorie als er geen gekoppelde categorie bestaat voor een release. Het toevoegen van een categorie specifiek voor {appName} voorkomt conflicten met niet-gerelateerde downloads die niet van {appName} zijn. Het gebruik van een categorie is optioneel, maar wordt sterk aanbevolen. Er wordt een [categorie]-submap aangemaakt in de uitvoermap.", "DownloadClientSettingsDefaultCategorySubFolderHelpText": "Standaard fallback-categorie als er geen gekoppelde categorie bestaat voor een release. Het toevoegen van een categorie specifiek voor {appName} voorkomt conflicten met niet-gerelateerde downloads die niet van {appName} zijn. Het gebruik van een categorie is optioneel, maar wordt sterk aanbevolen. Er wordt een [categorie]-submap aangemaakt in de uitvoermap.",
"IndexerPassThePopcornSettingsApiUserHelpText": "Deze instellingen vind je in je PassThePopcorn beveiligingsinstellingen (Profiel bewerken > Beveiliging).", "IndexerPassThePopcornSettingsApiUserHelpText": "Deze instellingen vind je in je PassThePopcorn beveiligingsinstellingen (Profiel bewerken > Beveiliging)."
"IndexerGazelleGamesSettingsFreeleechOnlyHelpText": "Doorzoek alleen freeleech releases",
"UsenetBlackholeNzbFolder": "Nzb map",
"IndexerSettingsAppsMinimumSeedersHelpText": "Minimum aantal vereiste seeders door de applicaties voordat de indexer iets ophaalt. Leeg laten gebruikt de standaardwaarde van het synchronisatieprofiel",
"IndexerBeyondHDSettingsFreeleechOnlyHelpText": "Doorzoek alleen freeleech releases",
"IndexerTorrentSyndikatSettingsApiKeyHelpText": "Site API-sleutel",
"SearchTypes": "Zoek types",
"IndexerAlphaRatioSettingsFreeleechOnlyHelpText": "Doorzoek alleen freeleech releases",
"IndexerAvistazSettingsFreeleechOnlyHelpText": "Doorzoek alleen freeleech releases"
} }

View file

@ -48,7 +48,7 @@
"Album": "專輯", "Album": "專輯",
"Authentication": "認證", "Authentication": "認證",
"AutomaticSearch": "自動搜尋", "AutomaticSearch": "自動搜尋",
"BackupFolderHelpText": "相對路徑將位於 {appName} 的 AppData 目錄下", "BackupFolderHelpText": "相對路徑將位於Radarr的AppData目錄下",
"BackupNow": "馬上備份", "BackupNow": "馬上備份",
"BackupRetentionHelpText": "超過保留期的自動備份將被自動清理", "BackupRetentionHelpText": "超過保留期的自動備份將被自動清理",
"BindAddress": "綁定地址Bind Address", "BindAddress": "綁定地址Bind Address",
@ -75,7 +75,7 @@
"Theme": "主題", "Theme": "主題",
"ApiKeyValidationHealthCheckMessage": "請將您的API金鑰更新為至少{length}個字元長。您可以通過設定或配置文件進行此操作。", "ApiKeyValidationHealthCheckMessage": "請將您的API金鑰更新為至少{length}個字元長。您可以通過設定或配置文件進行此操作。",
"AppDataLocationHealthCheckMessage": "為了避免在更新過程中刪除AppData將無法進行更新。", "AppDataLocationHealthCheckMessage": "為了避免在更新過程中刪除AppData將無法進行更新。",
"AuthenticationMethodHelpText": "需要使用者名稱和密碼才能存取 {appName}", "AuthenticationMethodHelpText": "需要使用者名稱和密碼來存取Radarr",
"Backup": "備份", "Backup": "備份",
"Enable": "啟用", "Enable": "啟用",
"Grabs": "抓取", "Grabs": "抓取",
@ -99,7 +99,7 @@
"Seeders": "種子", "Seeders": "種子",
"Settings": "設定", "Settings": "設定",
"DownloadClients": "下載用戶端", "DownloadClients": "下載用戶端",
"AnalyticsEnabledHelpText": "傳送匿名使用和錯誤資訊到 {appName} 的伺服器。這包括您的瀏覽器資訊、您使用的 {appName} WebUI 頁面、錯誤報告以及作業系統和執行階段版本。我們將使用這些資訊來優先處理功能和錯誤修正。", "AnalyticsEnabledHelpText": "將使用和錯誤資訊匿名傳送至Radarr的伺服器。這些資訊包括您的瀏覽器資訊、使用的Radarr WebUI頁面、錯誤報告以及作業系統和執行時版本。我們將使用這些資訊來優先處理功能和錯誤修復。",
"ApiKey": "API 金鑰", "ApiKey": "API 金鑰",
"AppDataDirectory": "AppData 路徑", "AppDataDirectory": "AppData 路徑",
"Applications": "應用程式", "Applications": "應用程式",
@ -135,7 +135,7 @@
"IndexerHDBitsSettingsCodecs": "編解碼器", "IndexerHDBitsSettingsCodecs": "編解碼器",
"Directory": "目錄", "Directory": "目錄",
"BuiltIn": "內建的", "BuiltIn": "內建的",
"AllSearchResultsHiddenByFilter": "根據所使用的篩選器已將所有結果隱藏", "AllSearchResultsHiddenByFilter": "根據所使用的篩選器已將所有結果隱藏",
"AptUpdater": "使用apt安裝更新", "AptUpdater": "使用apt安裝更新",
"Discord": "Discord", "Discord": "Discord",
"Any": "任何", "Any": "任何",

View file

@ -6,23 +6,23 @@
<PackageReference Include="AngleSharp.Xml" Version="1.0.0" /> <PackageReference Include="AngleSharp.Xml" Version="1.0.0" />
<PackageReference Include="Dapper" Version="2.1.66" /> <PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="Diacritical.Net" Version="1.0.4" /> <PackageReference Include="Diacritical.Net" Version="1.0.4" />
<PackageReference Include="MailKit" Version="4.14.0" /> <PackageReference Include="MailKit" Version="4.12.1" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="8.0.16" /> <PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="8.0.16" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.16" /> <PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.16" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" /> <PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" /> <PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="Npgsql" Version="9.0.3" /> <PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="Polly" Version="8.6.4" /> <PackageReference Include="Polly" Version="8.6.0" />
<PackageReference Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" /> <PackageReference Include="Servarr.FluentMigrator.Runner" 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="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" /> <PackageReference Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
<PackageReference Include="System.Drawing.Common" Version="8.0.19" />
<PackageReference Include="System.Memory" Version="4.6.3" /> <PackageReference Include="System.Memory" Version="4.6.3" />
<PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" /> <PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" />
<PackageReference Include="FluentValidation" Version="9.5.4" /> <PackageReference Include="FluentValidation" Version="9.5.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.4.0" /> <PackageReference Include="NLog" Version="5.4.0" />
<PackageReference Include="System.Text.Json" Version="8.0.6" /> <PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="MonoTorrent" Version="2.0.7" /> <PackageReference Include="MonoTorrent" Version="2.0.7" />
<PackageReference Include="YamlDotNet" Version="16.3.0" /> <PackageReference Include="YamlDotNet" Version="16.3.0" />
<PackageReference Include="AngleSharp" Version="1.3.0" /> <PackageReference Include="AngleSharp" Version="1.3.0" />

View file

@ -1,7 +1,6 @@
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;
@ -32,7 +31,6 @@
using Prowlarr.Http.ErrorManagement; using Prowlarr.Http.ErrorManagement;
using Prowlarr.Http.Frontend; using Prowlarr.Http.Frontend;
using Prowlarr.Http.Middleware; using Prowlarr.Http.Middleware;
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
using LogLevel = Microsoft.Extensions.Logging.LogLevel; using LogLevel = Microsoft.Extensions.Logging.LogLevel;
namespace NzbDrone.Host namespace NzbDrone.Host
@ -61,11 +59,8 @@ 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.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8)); options.KnownNetworks.Clear();
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("172.16.0.0"), 12)); options.KnownProxies.Clear();
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);

View file

@ -4,7 +4,7 @@
<DataCollectors> <DataCollectors>
<DataCollector friendlyName="XPlat code coverage"> <DataCollector friendlyName="XPlat code coverage">
<Configuration> <Configuration>
<Format>cobertura</Format> <Format>opencover</Format>
<Exclude>[Prowlarr.*.Test]*,[Prowlarr.Test.*]*,[Prowlarr.Api*]*</Exclude> <Exclude>[Prowlarr.*.Test]*,[Prowlarr.Test.*]*,[Prowlarr.Api*]*</Exclude>
</Configuration> </Configuration>
</DataCollector> </DataCollector>