Compare commits

..

1 Commits

Author SHA1 Message Date
bakerboy448
20b82146ac New: Deprecate Readarr 2025-10-30 23:41:08 -05:00
14 changed files with 115 additions and 33 deletions

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '2.3.2'
majorVersion: '2.3.0'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
@@ -18,9 +18,9 @@ variables:
dotnetVersion: '8.0.405'
nodeVersion: '20.X'
innoVersion: '6.2.2'
windowsImage: 'windows-2025'
linuxImage: 'ubuntu-24.04'
macImage: 'macOS-15'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-22.04'
macImage: 'macOS-13'
trigger:
branches:

View File

@@ -1,14 +1,18 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Label from 'Components/Label';
import Button from 'Components/Link/Button';
import Link from 'Components/Link/Link';
import Menu from 'Components/Menu/Menu';
import MenuContent from 'Components/Menu/MenuContent';
import { sizes } from 'Helpers/Props';
import { kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddApplicationPresetMenuItem from './AddApplicationPresetMenuItem';
import styles from './AddApplicationItem.css';
const DEPRECATED_APPLICATIONS = ['Readarr'];
const OBSOLETE_APPLICATIONS = [];
class AddApplicationItem extends Component {
//
@@ -36,6 +40,8 @@ class AddApplicationItem extends Component {
} = this.props;
const hasPresets = !!presets && !!presets.length;
const isDeprecated = DEPRECATED_APPLICATIONS.includes(implementation);
const isObsolete = OBSOLETE_APPLICATIONS.includes(implementation);
return (
<div
@@ -49,6 +55,24 @@ class AddApplicationItem extends Component {
<div className={styles.overlay}>
<div className={styles.name}>
{implementationName}
{
isDeprecated &&
<Label
kind={kinds.WARNING}
title={translate('DeprecatedApplicationMessage', { applicationName: implementationName })}
>
{translate('Deprecated')}
</Label>
}
{
isObsolete &&
<Label
kind={kinds.DANGER}
title={translate('ObsoleteApplicationMessage', { applicationName: implementationName })}
>
{translate('Obsolete')}
</Label>
}
</div>
<div className={styles.actions}>

View File

@@ -10,6 +10,9 @@ import translate from 'Utilities/String/translate';
import EditApplicationModalConnector from './EditApplicationModalConnector';
import styles from './Application.css';
const DEPRECATED_APPLICATIONS = ['Readarr'];
const OBSOLETE_APPLICATIONS = [];
class Application extends Component {
//
@@ -61,10 +64,13 @@ class Application extends Component {
syncLevel,
fields,
tags,
tagList
tagList,
implementation
} = this.props;
const applicationUrl = fields.find((field) => field.name === 'baseUrl')?.value;
const isDeprecated = DEPRECATED_APPLICATIONS.includes(implementation);
const isObsolete = OBSOLETE_APPLICATIONS.includes(implementation);
return (
<Card
@@ -88,6 +94,26 @@ class Application extends Component {
}
</div>
{
isDeprecated &&
<Label
kind={kinds.WARNING}
title={translate('DeprecatedApplicationMessage', { applicationName: implementation })}
>
{translate('Deprecated')}
</Label>
}
{
isObsolete &&
<Label
kind={kinds.DANGER}
title={translate('ObsoleteApplicationMessage', { applicationName: implementation })}
>
{translate('Obsolete')}
</Label>
}
{
syncLevel === 'addOnly' &&
<Label kind={kinds.WARNING}>
@@ -141,6 +167,7 @@ class Application extends Component {
Application.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
implementation: PropTypes.string.isRequired,
enable: PropTypes.bool.isRequired,
syncLevel: PropTypes.string.isRequired,
fields: PropTypes.arrayOf(PropTypes.object).isRequired,

View File

@@ -39,6 +39,9 @@ const syncLevelOptions = [
}
];
const DEPRECATED_APPLICATIONS = ['Readarr'];
const OBSOLETE_APPLICATIONS = [];
function EditApplicationModalContent(props) {
const {
advancedSettings,
@@ -60,6 +63,7 @@ function EditApplicationModalContent(props) {
const {
id,
implementation,
implementationName,
name,
syncLevel,
@@ -68,6 +72,9 @@ function EditApplicationModalContent(props) {
message
} = item;
const isDeprecated = DEPRECATED_APPLICATIONS.includes(implementation);
const isObsolete = OBSOLETE_APPLICATIONS.includes(implementation);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
@@ -90,6 +97,28 @@ function EditApplicationModalContent(props) {
{
!isFetching && !error &&
<Form {...otherProps}>
{
isDeprecated &&
<Alert
className={styles.message}
kind={kinds.WARNING}
>
<div>{translate('DeprecatedApplicationTitle', { applicationName: implementationName })}</div>
<div>{translate('DeprecatedApplicationMessage', { applicationName: implementationName })}</div>
</Alert>
}
{
isObsolete &&
<Alert
className={styles.message}
kind={kinds.DANGER}
>
<div>{translate('ObsoleteApplicationTitle', { applicationName: implementationName })}</div>
<div>{translate('ObsoleteApplicationMessage', { applicationName: implementationName })}</div>
</Alert>
}
{
!!message &&
<Alert

View File

@@ -159,7 +159,7 @@ namespace NzbDrone.Core.Applications.Lidarr
case HttpStatusCode.BadRequest:
if (ex.Response.Content.Contains("Query successful, but no results in the configured categories were returned from your indexer.", StringComparison.InvariantCultureIgnoreCase))
{
_logger.Warn(ex, "No Results in configured categories. See FAQ Entry: Prowlarr will not sync X Indexer to App");
_logger.Warn(ex, "No Results in configured categories. See FAQ Entry: https://wiki.servarr.com/prowlarr/faq#prowlarr-will-not-sync-x-indexer-to-app");
break;
}

View File

@@ -172,7 +172,7 @@ namespace NzbDrone.Core.Applications.Radarr
case HttpStatusCode.BadRequest:
if (ex.Response.Content.Contains("Query successful, but no results in the configured categories were returned from your indexer.", StringComparison.InvariantCultureIgnoreCase))
{
_logger.Warn(ex, "No Results in configured categories. See FAQ Entry: Prowlarr will not sync X Indexer to App");
_logger.Warn(ex, "No Results in configured categories. See FAQ Entry: https://wiki.servarr.com/prowlarr/faq#prowlarr-will-not-sync-x-indexer-to-app");
break;
}

View File

@@ -14,6 +14,7 @@ using NzbDrone.Core.Indexers;
namespace NzbDrone.Core.Applications.Readarr
{
[Obsolete("Readarr is deprecated and will be removed in a future version")]
public class Readarr : ApplicationBase<ReadarrSettings>
{
public override string Name => "Readarr";

View File

@@ -146,7 +146,7 @@ namespace NzbDrone.Core.Applications.Readarr
case HttpStatusCode.BadRequest:
if (ex.Response.Content.Contains("Query successful, but no results in the configured categories were returned from your indexer.", StringComparison.InvariantCultureIgnoreCase))
{
_logger.Warn(ex, "No Results in configured categories. See FAQ Entry: Prowlarr will not sync X Indexer to App");
_logger.Warn(ex, "No Results in configured categories. See FAQ Entry: https://wiki.servarr.com/prowlarr/faq#prowlarr-will-not-sync-x-indexer-to-app");
break;
}

View File

@@ -159,7 +159,7 @@ namespace NzbDrone.Core.Applications.Sonarr
case HttpStatusCode.BadRequest:
if (ex.Response.Content.Contains("Query successful, but no results in the configured categories were returned from your indexer.", StringComparison.InvariantCultureIgnoreCase))
{
_logger.Warn(ex, "No Results in configured categories. See FAQ Entry: Prowlarr will not sync X Indexer to App");
_logger.Warn(ex, "No Results in configured categories. See FAQ Entry: https://wiki.servarr.com/prowlarr/faq#prowlarr-will-not-sync-x-indexer-to-app");
break;
}

View File

@@ -144,7 +144,7 @@ namespace NzbDrone.Core.Applications.Whisparr
case HttpStatusCode.BadRequest:
if (ex.Response.Content.Contains("Query successful, but no results in the configured categories were returned from your indexer.", StringComparison.InvariantCultureIgnoreCase))
{
_logger.Warn(ex, "No Results in configured categories. See FAQ Entry: Prowlarr will not sync X Indexer to App");
_logger.Warn(ex, "No Results in configured categories. See FAQ Entry: https://wiki.servarr.com/prowlarr/faq#prowlarr-will-not-sync-x-indexer-to-app");
break;
}

View File

@@ -669,7 +669,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var advancedSeasonRegex = new Regex(@"\b(?:(?<season>\d+)(?:st|nd|rd|th) Season|Season (?<season>\d+))\b", RegexOptions.Compiled | RegexOptions.IgnoreCase);
var seasonCharactersRegex = new Regex(@"(I{2,})$", RegexOptions.Compiled);
var seasonNumberRegex = new Regex(@"\b(?<!(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)
{

View File

@@ -69,8 +69,8 @@ public class Shazbat : TorrentIndexerBase<ShazbatSettings>
.AddFormParameter("referer", "")
.AddFormParameter("query", "")
.AddFormParameter("tv_timezone", "0")
.AddFormParameter("username", Settings.Username)
.AddFormParameter("password", Settings.Password)
.AddFormParameter("tv_login", Settings.Username)
.AddFormParameter("tv_password", Settings.Password)
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
.SetHeader("Referer", loginUrl)
.Build();
@@ -92,9 +92,9 @@ public class Shazbat : TorrentIndexerBase<ShazbatSettings>
_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()
@@ -202,7 +202,7 @@ public class ShazbatRequestGenerator : IIndexerRequestGenerator
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public partial class ShazbatParser : IParseIndexerResponse
public class ShazbatParser : IParseIndexerResponse
{
private readonly ProviderDefinition _definition;
private readonly ShazbatSettings _settings;
@@ -210,10 +210,8 @@ public partial class ShazbatParser : IParseIndexerResponse
private readonly IIndexerHttpClient _httpClient;
private readonly Logger _logger;
private readonly HashSet<string> _hdResolutions = ["1080p", "1080i", "720p"];
[GeneratedRegex(@"\((?<size>\d+)\)\s*:(?<seeders>\d+) \/ :(?<leechers>\d+)$", RegexOptions.Compiled)]
private static partial Regex TorrentInfoRegex();
private readonly Regex _torrentInfoRegex = new(@"\((?<size>\d+)\):(?<seeders>\d+) \/ :(?<leechers>\d+)$", RegexOptions.Compiled);
private readonly HashSet<string> _hdResolutions = new() { "1080p", "1080i", "720p" };
public ShazbatParser(ProviderDefinition definition, ShazbatSettings settings, TimeSpan rateLimit, IIndexerHttpClient httpClient, Logger logger)
{
@@ -280,8 +278,7 @@ public partial class ShazbatParser : IParseIndexerResponse
var releaseRequest = new IndexerRequest(showRequest);
var releaseResponse = new IndexerResponse(releaseRequest, _httpClient.ExecuteProxied(releaseRequest.HttpRequest, _definition));
if ((releaseResponse.HttpResponse.HasHttpRedirect && releaseResponse.HttpResponse.RedirectUrl.ContainsIgnoreCase("login")) ||
releaseResponse.HttpResponse.Content.ContainsIgnoreCase("sign in now"))
if (releaseResponse.HttpResponse.Content.ContainsIgnoreCase("sign in now"))
{
// Remove cookie cache
CookiesUpdater(null, null);
@@ -327,15 +324,13 @@ public partial class ShazbatParser : IParseIndexerResponse
var title = ParseTitle(row.QuerySelector("td:nth-of-type(3)"));
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 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 dateTimestamp = row.QuerySelector(".datetime[data-timestamp]")?.GetAttribute("data-timestamp");
publishDate = dateTimestamp != null && ParseUtil.TryCoerceLong(dateTimestamp, out var timestamp)
? DateTimeUtil.UnixTimestampToDateTime(timestamp)
: publishDate.AddMinutes(-1);
publishDate = dateTimestamp != null && ParseUtil.TryCoerceDouble(dateTimestamp, out var timestamp) ? DateTimeUtil.UnixTimestampToDateTime(timestamp) : publishDate.AddMinutes(-1);
var release = new TorrentInfo
{
@@ -369,10 +364,10 @@ public partial class ShazbatParser : IParseIndexerResponse
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,
title switch
{
@@ -380,7 +375,9 @@ public partial class ShazbatParser : IParseIndexerResponse
_ when title.Contains("2160p") => NewznabStandardCategory.TVUHD,
_ => NewznabStandardCategory.TVSD
}
];
};
return categories;
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }

View File

@@ -538,6 +538,12 @@
"NotificationsTelegramSettingsIncludeAppName": "Include {appName} in Title",
"NotificationsTelegramSettingsIncludeAppNameHelpText": "Optionally prefix message title with {appName} to differentiate notifications from different applications",
"OAuthPopupMessage": "Pop-ups are being blocked by your browser",
"Obsolete": "Obsolete",
"ObsoleteApplicationMessage": "{applicationName} is obsolete and has been removed",
"ObsoleteApplicationTitle": "{applicationName} is Obsolete",
"Deprecated": "Deprecated",
"DeprecatedApplicationMessage": "{applicationName} is deprecated and will be removed in a future version",
"DeprecatedApplicationTitle": "{applicationName} is Deprecated",
"Ok": "Ok",
"OnApplicationUpdate": "On Application Update",
"OnApplicationUpdateHelpText": "On Application Update",

View File

@@ -64,8 +64,6 @@ namespace NzbDrone.Host
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8));
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);