Compare commits

...

39 Commits

Author SHA1 Message Date
Qstick
7f28f64cbe Fix server settings on API docs 2021-12-28 18:47:44 -06:00
Qstick
9bad31af84 Eliminate PR Extension from pipeline 2021-12-28 18:26:51 -06:00
Qstick
01c7a05841 Only push to api-docs when changes 2021-12-28 17:52:15 -06:00
Qstick
9859b4a3d9 force it 2021-12-28 17:37:30 -06:00
Gabriel Sjöberg
177084fe8b Fixed: (Indexer) Update RARBG API query options
* Added app_id to captcha check to avoid 403 forbidden error
* Migrated app_id from hard coded to BuildInfo.AppName
2021-12-28 17:04:55 -06:00
Qstick
c57a91bc64 Skip build of doc only change, ignore PR errors for docs 2021-12-28 17:01:31 -06:00
Servarr
ca67a40c72 Automated API Docs update 2021-12-28 15:39:14 -06:00
Qstick
de7505bbe6 correctly push upstream 2021-12-28 15:27:54 -06:00
Qstick
97956ce951 Branch and push prior to PR 2021-12-28 15:10:56 -06:00
Qstick
8a38e124fd Speed up Checkout for Docs job 2021-12-28 14:45:28 -06:00
Qstick
38fcffe871 Identify user for git 2021-12-28 14:31:12 -06:00
Qstick
4c7b5a47d3 Autogenerated API docs 2021-12-28 13:43:45 -06:00
Qstick
34597e6ecb Boolean default should be a boolean
Fixes #729
2021-12-24 14:23:09 -06:00
bakerboy448
735be4f467 New: (TvVault) Mark as Obsolete per Site Bot Ban
Closes #573
2021-12-24 14:08:00 -06:00
bakerboy448
1c737d77fb Bump to 0.1.9 2021-12-24 14:07:42 -06:00
bakerboy448
55788ac04d Fixed: (Usenet) (DrunkenSlug) Update URL
https://api.drunkenslug.com gives a 301
2021-12-24 14:07:17 -06:00
bakerboy448
d108ab0339 Fix misleading Tags helptext [skip ci] 2021-12-24 14:06:42 -06:00
bakerboy448
5928eea83e Fixed: (PornoLab) Add new 2022 Categories
Based on Jackett f61a2b47400e68422ba6620a5ef2f5b4d0a929a3
2021-12-24 12:58:51 -06:00
bakerboy448
27898aa3b5 Fixed: (DanishBytes) Update Domain to .club
Based on Jackett f890ddd119a35eb4ba40b407aa65461c713b5e5d
2021-12-24 12:58:51 -06:00
bakerboy448
5e3322c538 New: OnApplicationUpdate Notifications
(based on Radarr Commits
9e175e28efcfc6ac3e414649b955a10fb0e951e7
4f5f9ff77ee4de05ba04cc677eb7baf4df726af5
4ebcbc28aa3e3268ecc37c5fc2b5565da8f13305
)
Fixes #723

Co-authored-by: Qstick <qstick@gmail.com>
2021-12-22 19:07:07 -06:00
bakerboy448
80c31e8660 fixup add rationale for Obsolete of C# Indexer Implementations 2021-12-18 09:34:51 -06:00
bakerboy448
46401ee187 Fixed: (BB) Remove '.' from Search String
based on jackett fbb1f15d7014b2d8c23c6ee94c2bcf37612066db
2021-12-18 09:27:26 -06:00
Davo1624
3610becc64 Fixed: (Orpheus) Drop Caps Support for Movie & TV Search
Drop TV and Movie search as no Movie/TV categories (#713)

e-learning results are how to play guitar videos, etc
comedy are comedy/standup recordings
2021-12-15 16:30:26 -06:00
Davo1624
06d9c157d8 Fixed: (Orpheus) Map Categories Comedy & E-Learning Videos to 'Other'
indexer does not actually have movies and tv
2021-12-15 16:27:50 -06:00
bakerboy448
d0d1f40128 Fixed: (Anilibria) Duplicate entries
Mark C# Anilibria as obsolete
2021-12-12 17:28:07 -06:00
bakerboy448
383d5464b7 New: (FlareSolverr Proxy) Configurable Request Timeout
Closes #696
2021-12-11 22:10:06 -06:00
Qstick
62d15536df Fixed: NullRef in SchemaBuilder when sending payload without optional Provider.Settings fields 2021-12-11 13:38:25 -06:00
ta264
147cdf2cce Fixed: Forms login persists across restarts in docker
(cherry picked from commit a219b4a1b869863b2ef47d4bdf33d308cb261ba3)

Fixes #409
2021-12-10 03:14:55 -06:00
bakerboy448
dd27d69e97 fix erroneous logging for windows service on non-windows 2021-12-10 03:14:23 -06:00
Robin Dadswell
32fd0911a2 New: Application Placeholders instead of default values 2021-12-09 15:19:11 -06:00
Robin Dadswell
0e6ec58a83 New: Placeholders in notification fields 2021-12-09 15:19:11 -06:00
Robin Dadswell
69f5963f6f New: Frontend Placeholders from the Backend 2021-12-09 15:19:11 -06:00
PearsonFlyer
6ca708f523 Fixed: (HDTorrents) Remove . from searches 2021-12-08 14:26:46 -06:00
ta264
9e7af8369e Fixed: Support older glibc in libMonoPosixHelper 2021-12-08 19:14:25 +00:00
Qstick
b05d8c930d Date Routines Test Cases 2021-12-07 20:19:05 -06:00
Qstick
6b886b938c New: Better Fuzzy DateTime Parse 2021-12-07 18:48:46 -06:00
ta264
4a7bf39723 Fixed: Speed up parsing DateTime 2021-12-06 21:57:22 +00:00
bakerboy448
7fcd320e23 Fixed: (PrivateHD) Drop support for IMDB search
imdb search does not support S/E params
this matches the behavior for TorrentLeech
Fixes #119
2021-12-06 21:12:37 -06:00
Qstick
88677ce236 Bump to 0.1.8 2021-12-06 17:19:04 -06:00
88 changed files with 1341 additions and 381 deletions

4
.gitignore vendored
View File

@@ -188,6 +188,10 @@ packages.config.md5sum
**/.idea/**/*.iml
**/.idea/**/contentModel.xml
**/.idea/**/modules.xml
# ignore node_modules symlink
node_modules
node_modules.nosync
# API doc generation
.config/

View File

@@ -7,7 +7,7 @@ variables:
outputFolder: './_output'
artifactsFolder: './_artifacts'
testsFolder: './_tests'
majorVersion: '0.1.7'
majorVersion: '0.1.9'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
@@ -29,6 +29,7 @@ pr:
paths:
exclude:
- src/NzbDrone.Core/Localization/Core
- src/Prowlarr.API.*/openapi.json
stages:
- stage: Setup
@@ -823,6 +824,59 @@ stages:
FORCE_COLOR: 0
YARN_CACHE_FOLDER: $(yarnCacheFolder)
- job: Api_Docs
displayName: API Docs
dependsOn: Prepare
condition: |
and
(
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')),
and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
)
pool:
vmImage: windows-2019
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
- checkout: self
submodules: true
persistCredentials: true
fetchDepth: 1
- bash: ./docs.sh Windows
displayName: Create openapi.json
- bash: |
git config --global user.email "development@lidarr.audio"
git config --global user.name "Servarr"
git checkout -b api-docs
git add .
if git status | grep -q modified
then
git commit -am 'Automated API Docs update'
git push -f --set-upstream origin api-docs
curl -X POST -H 'Authorization: token $GITHUB_TOKEN' -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/prowlarr/prowlarr/pulls -d '{"head":"api-docs","base":"develop","title":"Update API docs"}'
else
echo "No changes since last run"
fi
displayName: Commit API Doc Change
continueOnError: true
env:
GITHUB_TOKEN: $(githubToken)
- task: CopyFiles@2
displayName: 'Copy openapi.json to: $(Build.ArtifactStagingDirectory)'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
**/*openapi.json
TargetFolder: '$(Build.ArtifactStagingDirectory)/api_docs'
- publish: $(Build.ArtifactStagingDirectory)/api_docs
artifact: 'APIDocs'
displayName: Publish API Docs Bundle
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
- job: Analyze_Backend
displayName: Backend
dependsOn: Prepare

38
docs.sh Normal file
View File

@@ -0,0 +1,38 @@
PLATFORM=$1
if [ "$PLATFORM" = "Windows" ]; then
RUNTIME="win-x64"
elif [ "$PLATFORM" = "Linux" ]; then
WHERE="linux-x64"
elif [ "$PLATFORM" = "Mac" ]; then
WHERE="osx-x64"
else
echo "Platform must be provided as first arguement: Windows, Linux or Mac"
exit 1
fi
outputFolder='_output'
testPackageFolder='_tests'
rm -rf $outputFolder
rm -rf $testPackageFolder
slnFile=src/Prowlarr.sln
platform=Posix
dotnet clean $slnFile -c Debug
dotnet clean $slnFile -c Release
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
dotnet new tool-manifest
dotnet tool install --version 6.2.3 Swashbuckle.AspNetCore.Cli
dotnet tool run swagger tofile --output ./src/Prowlarr.Api.V1/openapi.json "$outputFolder/net6.0/$RUNTIME/prowlarr.console.dll" v1 &
sleep 10
kill %1
exit 0

View File

@@ -68,6 +68,7 @@ function ProviderFieldFormGroup(props) {
label,
helpText,
helpLink,
placeholder,
value,
type,
advanced,
@@ -100,6 +101,7 @@ function ProviderFieldFormGroup(props) {
label={label}
helpText={helpText}
helpLink={helpLink}
placeholder={placeholder}
value={value}
values={getSelectValues(selectOptions)}
errors={errors}
@@ -125,6 +127,7 @@ ProviderFieldFormGroup.propTypes = {
label: PropTypes.string,
helpText: PropTypes.string,
helpLink: PropTypes.string,
placeholder: PropTypes.string,
value: PropTypes.any,
type: PropTypes.string.isRequired,
advanced: PropTypes.bool.isRequired,

View File

@@ -40,7 +40,7 @@ class Notification extends Component {
});
};
onDeleteNotificationModalClose= () => {
onDeleteNotificationModalClose = () => {
this.setState({ isDeleteNotificationModalOpen: false });
};
@@ -61,12 +61,14 @@ class Notification extends Component {
onRename,
onDelete,
onHealthIssue,
onApplicationUpdate,
supportsOnGrab,
supportsOnDownload,
supportsOnUpgrade,
supportsOnRename,
supportsOnDelete,
supportsOnHealthIssue
supportsOnHealthIssue,
supportsOnApplicationUpdate
} = this.props;
return (
@@ -82,53 +84,62 @@ class Notification extends Component {
{
supportsOnGrab && onGrab &&
<Label kind={kinds.SUCCESS}>
On Grab
{translate('OnGrab')}
</Label>
}
{
supportsOnDelete && onDelete &&
<Label kind={kinds.SUCCESS}>
On Delete
{translate('OnDelete')}
</Label>
}
{
supportsOnDownload && onDownload &&
<Label kind={kinds.SUCCESS}>
On Import
{translate('OnImport')}
</Label>
}
{
supportsOnUpgrade && onDownload && onUpgrade &&
<Label kind={kinds.SUCCESS}>
On Upgrade
{translate('OnUpgrade')}
</Label>
}
{
supportsOnRename && onRename &&
<Label kind={kinds.SUCCESS}>
On Rename
{translate('OnRename')}
</Label>
}
{
supportsOnHealthIssue && onHealthIssue &&
<Label kind={kinds.SUCCESS}>
On Health Issue
{translate('OnHealthIssue')}
</Label>
}
{
!onGrab && !onDownload && !onRename && !onHealthIssue && !onDelete &&
supportsOnApplicationUpdate && onApplicationUpdate ?
<Label kind={kinds.SUCCESS}>
{translate('OnApplicationUpdate')}
</Label> :
null
}
{
!onGrab && !onDownload && !onRename && !onHealthIssue && !onDelete && !onApplicationUpdate ?
<Label
kind={kinds.DISABLED}
outline={true}
>
Disabled
</Label>
{translate('Disabled')}
</Label> :
null
}
<EditNotificationModalConnector
@@ -161,12 +172,14 @@ Notification.propTypes = {
onRename: PropTypes.bool.isRequired,
onDelete: PropTypes.bool.isRequired,
onHealthIssue: PropTypes.bool.isRequired,
onApplicationUpdate: PropTypes.bool.isRequired,
supportsOnGrab: PropTypes.bool.isRequired,
supportsOnDownload: PropTypes.bool.isRequired,
supportsOnDelete: PropTypes.bool.isRequired,
supportsOnUpgrade: PropTypes.bool.isRequired,
supportsOnRename: PropTypes.bool.isRequired,
supportsOnHealthIssue: PropTypes.bool.isRequired,
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired
};

View File

@@ -16,8 +16,10 @@ function NotificationEventItems(props) {
const {
onHealthIssue,
onApplicationUpdate,
supportsOnHealthIssue,
includeHealthWarnings
includeHealthWarnings,
supportsOnApplicationUpdate
} = item;
return (
@@ -53,6 +55,17 @@ function NotificationEventItems(props) {
/>
</div>
}
<div>
<FormInputGroup
type={inputTypes.CHECK}
name="onApplicationUpdate"
helpText={translate('OnApplicationUpdateHelpText')}
isDisabled={!supportsOnApplicationUpdate.value}
{...onApplicationUpdate}
onChange={onInputChange}
/>
</div>
</div>
</div>
</FormGroup>

View File

@@ -106,6 +106,7 @@ export default {
selectedSchema.onDownload = selectedSchema.supportsOnDownload;
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
selectedSchema.onRename = selectedSchema.supportsOnRename;
selectedSchema.onApplicationUpdate = selectedSchema.supportsOnApplicationUpdate;
return selectedSchema;
});

View File

@@ -258,6 +258,11 @@ namespace NzbDrone.Common.Extensions
return appFolderInfo.AppDataFolder;
}
public static string GetDataProtectionPath(this IAppFolderInfo appFolderInfo)
{
return Path.Combine(GetAppDataPath(appFolderInfo), "asp");
}
public static string GetLogFolder(this IAppFolderInfo appFolderInfo)
{
return Path.Combine(GetAppDataPath(appFolderInfo), "logs");

View File

@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Rarbg;
@@ -51,7 +52,7 @@ namespace NzbDrone.Core.Test.IndexerTests.RarbgTests
torrentInfo.Title.Should().Be("Sense8.S01E01.WEBRip.x264-FGT");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("magnet:?xt=urn:btih:d8bde635f573acb390c7d7e7efc1556965fdc802&dn=Sense8.S01E01.WEBRip.x264-FGT&tr=http%3A%2F%2Ftracker.trackerfix.com%3A80%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2710&tr=udp%3A%2F%2F9.rarbg.to%3A2710&tr=udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce");
torrentInfo.InfoUrl.Should().Be("https://torrentapi.org/redirect_to_info.php?token=i5cx7b9agd&p=8_6_4_4_5_6__d8bde635f5&app_id=Prowlarr");
torrentInfo.InfoUrl.Should().Be($"https://torrentapi.org/redirect_to_info.php?token=i5cx7b9agd&p=8_6_4_4_5_6__d8bde635f5&app_id={BuildInfo.AppName}");
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2015-06-05 16:58:11 +0000").ToUniversalTime());
torrentInfo.Size.Should().Be(564198371);

View File

@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using FluentValidation.Results;
using NUnit.Framework;
using NzbDrone.Core.Notifications;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.NotificationTests
{
[TestFixture]
public class NotificationBaseFixture : TestBase
{
private class TestSetting : IProviderConfig
{
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult();
}
}
private class TestNotificationWithApplicationUpdate : NotificationBase<TestSetting>
{
public override string Name => "TestNotification";
public override string Link => "";
public override ValidationResult Test()
{
throw new NotImplementedException();
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
TestLogger.Info("OnApplicationUpdate was called");
}
}
private class TestNotificationWithAllEvents : NotificationBase<TestSetting>
{
public override string Name => "TestNotification";
public override string Link => "";
public override ValidationResult Test()
{
throw new NotImplementedException();
}
public override void OnHealthIssue(NzbDrone.Core.HealthCheck.HealthCheck artist)
{
TestLogger.Info("OnHealthIssue was called");
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
TestLogger.Info("OnApplicationUpdate was called");
}
}
private class TestNotificationWithNoEvents : NotificationBase<TestSetting>
{
public override string Name => "TestNotification";
public override string Link => "";
public override ValidationResult Test()
{
throw new NotImplementedException();
}
}
[Test]
public void should_support_all_if_implemented()
{
var notification = new TestNotificationWithAllEvents();
notification.SupportsOnHealthIssue.Should().BeTrue();
notification.SupportsOnApplicationUpdate.Should().BeTrue();
}
[Test]
public void should_support_none_if_none_are_implemented()
{
var notification = new TestNotificationWithNoEvents();
notification.SupportsOnHealthIssue.Should().BeFalse();
notification.SupportsOnApplicationUpdate.Should().BeFalse();
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
using System.Collections;
using NUnit.Framework;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.ParserTests
{
[TestFixture]
public class DateTimeRoutinesFixture : CoreTest
{
public static IEnumerable DateTimeTestCases
{
get
{
yield return new TestCaseData(@"Member since: 10-Feb-2008").Returns(new DateTime(2008, 2, 10, 0, 0, 0));
yield return new TestCaseData(@"Last Update: 18:16 11 Feb '08 ").Returns(new DateTime(2008, 2, 11, 18, 16, 0));
yield return new TestCaseData(@"date Tue, Feb 10, 2008 at 11:06 AM").Returns(new DateTime(2008, 2, 10, 11, 06, 0));
yield return new TestCaseData(@"see at 12/31/2007 14:16:32").Returns(new DateTime(2007, 12, 31, 14, 16, 32));
yield return new TestCaseData(@"sack finish 14:16:32 November 15 2008, 1-144 app").Returns(new DateTime(2008, 11, 15, 14, 16, 32));
yield return new TestCaseData(@"Genesis Message - Wed 04 Feb 08 - 19:40").Returns(new DateTime(2008, 2, 4, 19, 40, 0));
yield return new TestCaseData(@"The day 07/31/07 14:16:32 is ").Returns(new DateTime(2007, 7, 31, 14, 16, 32));
yield return new TestCaseData(@"Shipping is on us until December 24, 2008 within the U.S. ").Returns(new DateTime(2008, 12, 24, 0, 0, 0));
yield return new TestCaseData(@" 2008 within the U.S. at 14:16:32").Returns(new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 14, 16, 32));
yield return new TestCaseData(@"5th November, 1994, 8:15:30 pm").Returns(new DateTime(1994, 11, 5, 20, 15, 30));
yield return new TestCaseData(@"7 boxes January 31 , 14:16:32.").Returns(new DateTime(DateTime.Now.Year, 1, 31, 14, 16, 32));
yield return new TestCaseData(@"the blue sky of Sept 30th 2008 14:16:32").Returns(new DateTime(2008, 9, 30, 14, 16, 32));
yield return new TestCaseData(@" e.g. 1997-07-16T19:20:30+01:00").Returns(new DateTime(1997, 7, 16, 19, 20, 30));
yield return new TestCaseData(@"Apr 1st, 2008 14:16:32 tufa 6767").Returns(new DateTime(2008, 4, 1, 14, 16, 32));
yield return new TestCaseData(@"wait for 07/31/07 14:16:32").Returns(new DateTime(2007, 7, 31, 14, 16, 32));
yield return new TestCaseData(@"later 12.31.08 and before 1.01.09").Returns(new DateTime(2008, 12, 31, 0, 0, 0));
yield return new TestCaseData(@"Expires: Sept 30th 2008 14:16:32").Returns(new DateTime(2008, 9, 30, 14, 16, 32));
yield return new TestCaseData(@"Offer expires Apr 1st, 2007, 14:16:32").Returns(new DateTime(2007, 4, 1, 14, 16, 32));
yield return new TestCaseData(@"Expires 14:16:32 January 31.").Returns(new DateTime(DateTime.Now.Year, 1, 31, 14, 16, 32));
yield return new TestCaseData(@"Expires 14:16:32 January 31-st.").Returns(new DateTime(DateTime.Now.Year, 1, 31, 14, 16, 32));
yield return new TestCaseData(@"Expires 23rd January 2010.").Returns(new DateTime(2010, 1, 23, 0, 0, 0));
yield return new TestCaseData(@"Expires January 22nd, 2010.").Returns(new DateTime(2010, 1, 22, 0, 0, 0));
yield return new TestCaseData(@"Expires DEC 22, 2010.").Returns(new DateTime(2010, 12, 22, 0, 0, 0));
yield return new TestCaseData(@"Version: 1.0.0.692 6/1/2010 2:28:04 AM ").Returns(new DateTime(2010, 6, 1, 2, 28, 4));
yield return new TestCaseData(@"Version: 1.0.0.692 04/21/11 12:30am ").Returns(new DateTime(2011, 4, 21, 00, 30, 00));
yield return new TestCaseData(@"Version: 1.0.0.692 04/21/11 12:30pm ").Returns(new DateTime(2011, 4, 21, 12, 30, 00));
yield return new TestCaseData(@"Version: Thu Aug 06 22:32:15 MDT 2009 ").Returns(new DateTime(2009, 8, 6, 22, 32, 15));
}
}
[TestCaseSource("DateTimeTestCases")]
public DateTime should_parse_date(string date)
{
DateTimeRoutines.TryParseDateOrTime(date, DateTimeRoutines.DateTimeFormat.USDate, out var parsedDateTime);
return parsedDateTime.DateTime;
}
}
}

View File

@@ -23,6 +23,7 @@ namespace NzbDrone.Core.Annotations
public string Section { get; set; }
public HiddenType Hidden { get; set; }
public PrivacyLevel Privacy { get; set; }
public string Placeholder { get; set; }
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]

View File

@@ -23,8 +23,6 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
public LazyLibrarianSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:5299";
SyncCategories = new[]
{
NewznabStandardCategory.AudioAudiobook.Id,
@@ -38,10 +36,10 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
};
}
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as LazyLibrarian sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as LazyLibrarian sees it, including http(s)://, port, and urlbase if needed", Placeholder = "http://localhost:9696")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "LazyLibrarian Server", HelpText = "URL used to connect to LazyLibrarian server, including http(s)://, port, and urlbase if required")]
[FieldDefinition(1, Label = "LazyLibrarian Server", HelpText = "URL used to connect to LazyLibrarian server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:5299")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by LazyLibrarian in Settings/Web Interface")]

View File

@@ -22,15 +22,13 @@ namespace NzbDrone.Core.Applications.Lidarr
public LidarrSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8686";
SyncCategories = new[] { 3000, 3010, 3030, 3040, 3050, 3060 };
}
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Lidarr sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Lidarr sees it, including http(s)://, port, and urlbase if needed", Placeholder = "http://localhost:9696")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Lidarr Server", HelpText = "URL used to connect to Lidarr server, including http(s)://, port, and urlbase if required")]
[FieldDefinition(1, Label = "Lidarr Server", HelpText = "URL used to connect to Lidarr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:8686")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Lidarr in Settings/General")]

View File

@@ -23,15 +23,13 @@ namespace NzbDrone.Core.Applications.Mylar
public MylarSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8090";
SyncCategories = new[] { NewznabStandardCategory.BooksComics.Id };
}
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Mylar sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Mylar sees it, including http(s)://, port, and urlbase if needed", Placeholder = "http://localhost:9696")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Mylar Server", HelpText = "URL used to connect to Mylar server, including http(s)://, port, and urlbase if required")]
[FieldDefinition(1, Label = "Mylar Server", HelpText = "URL used to connect to Mylar server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:8090")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Mylar in Settings/Web Interface")]

View File

@@ -23,15 +23,13 @@ namespace NzbDrone.Core.Applications.Radarr
public RadarrSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:7878";
SyncCategories = new[] { 2000, 2010, 2020, 2030, 2040, 2045, 2050, 2060, 2070, 2080 };
}
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Radarr sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Radarr sees it, including http(s)://, port, and urlbase if needed", Placeholder = "http://localhost:9696")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Radarr Server", HelpText = "URL used to connect to Radarr server, including http(s)://, port, and urlbase if required")]
[FieldDefinition(1, Label = "Radarr Server", HelpText = "URL used to connect to Radarr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:7878")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Radarr in Settings/General")]

View File

@@ -23,15 +23,13 @@ namespace NzbDrone.Core.Applications.Readarr
public ReadarrSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8787";
SyncCategories = new[] { 3030, 7000, 7010, 7020, 7030, 7040, 7050, 7060 };
}
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Readarr sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Readarr sees it, including http(s)://, port, and urlbase if needed", Placeholder = "http://localhost:9696")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Readarr Server", HelpText = "URL used to connect to Readarr server, including http(s)://, port, and urlbase if required")]
[FieldDefinition(1, Label = "Readarr Server", HelpText = "URL used to connect to Readarr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:8787")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Readarr in Settings/General")]

View File

@@ -22,16 +22,14 @@ namespace NzbDrone.Core.Applications.Sonarr
public SonarrSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8989";
SyncCategories = new[] { 5000, 5010, 5020, 5030, 5040, 5045, 5050 };
AnimeSyncCategories = new[] { 5070 };
}
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Sonarr sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Sonarr sees it, including http(s)://, port, and urlbase if needed", Placeholder = "http://localhost:9696")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Sonarr Server", HelpText = "URL used to connect to Sonarr server, including http(s)://, port, and urlbase if required")]
[FieldDefinition(1, Label = "Sonarr Server", HelpText = "URL used to connect to Sonarr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:8989")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Sonarr in Settings/General")]

View File

@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(014)]
public class add_on_update_to_notifications : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Notifications").AddColumn("OnApplicationUpdate").AsBoolean().WithDefaultValue(false);
}
}
}

View File

@@ -63,7 +63,8 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<NotificationDefinition>("Notifications").RegisterModel()
.Ignore(x => x.ImplementationName)
.Ignore(i => i.SupportsOnHealthIssue);
.Ignore(i => i.SupportsOnHealthIssue)
.Ignore(i => i.SupportsOnApplicationUpdate);
Mapper.Entity<IndexerProxyDefinition>("IndexerProxies").RegisterModel()
.Ignore(x => x.ImplementationName);

View File

@@ -113,7 +113,7 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
var url = request.Url.ToString();
var userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36";
var maxTimeout = 60000;
var maxTimeout = Settings.RequestTimeout * 1000;
if (request.Method == HttpMethod.GET)
{

View File

@@ -1,4 +1,5 @@
using FluentValidation;
using NLog.Config;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Validation;
@@ -9,6 +10,7 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
public FlareSolverrSettingsValidator()
{
RuleFor(c => c.Host).NotEmpty();
RuleFor(c => c.RequestTimeout).InclusiveBetween(1, 180);
}
}
@@ -19,11 +21,15 @@ namespace NzbDrone.Core.IndexerProxies.FlareSolverr
public FlareSolverrSettings()
{
Host = "http://localhost:8191/";
RequestTimeout = 60;
}
[FieldDefinition(0, Label = "Host")]
public string Host { get; set; }
[FieldDefinition(2, Label = "Request Timeout", Advanced = true, HelpText = "FlareSolverr maxTimeout Request Parameter", Unit = "seconds")]
public int RequestTimeout { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@@ -8,7 +8,7 @@ using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete]
[Obsolete("Moved to YML for Cardigann v3")]
public class Aither : Unit3dBase
{
public override string Name => "Aither";

View File

@@ -17,6 +17,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete("Moved to YML for Cardigann v3")]
public class Anilibria : TorrentIndexerBase<AnilibriaSettings>
{
public override string Name => "Anilibria";

View File

@@ -8,7 +8,7 @@ using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete]
[Obsolete("Moved to YML for Cardigann v3")]
public class AnimeWorld : Unit3dBase
{
public override string Name => "AnimeWorld";

View File

@@ -178,7 +178,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{ "searchtags", "" },
{ "tags_type", "0" },
{ "action", "basic" },
{ "searchstr", term }
{ "searchstr", term.Replace(".", " ") }
};
var catList = Capabilities.Categories.MapTorznabCapsToTrackers(categories);

View File

@@ -8,7 +8,7 @@ using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete]
[Obsolete("Moved to YML for Cardigann v3")]
public class Blutopia : Unit3dBase
{
public override string Name => "Blutopia";

View File

@@ -633,7 +633,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
var date = DateTimeUtil.ParseDateTimeGoLang(data, layout);
data = date.ToString(DateTimeUtil.Rfc1123ZPattern);
}
catch (FormatException ex)
catch (InvalidDateException ex)
{
_logger.Debug(ex.Message);
}

View File

@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class DanishBytes : TorrentIndexerBase<DanishBytesSettings>
{
public override string Name => "DanishBytes";
public override string[] IndexerUrls => new string[] { "https://danishbytes.org/" };
public override string[] IndexerUrls => new string[] { "https://danishbytes.club/" };
public override string Description => "DanishBytes is a Private Danish Tracker";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;

View File

@@ -9,7 +9,7 @@ using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete]
[Obsolete("Moved to YML for Cardigann v3")]
public class DesiTorrents : Unit3dBase
{
public override string Name => "DesiTorrents";

View File

@@ -20,7 +20,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete]
[Obsolete("Moved to YML for Cardigann v3")]
public class DigitalCore : TorrentIndexerBase<DigitalCoreSettings>
{
public override string Name => "DigitalCore";

View File

@@ -158,7 +158,7 @@ namespace NzbDrone.Core.Indexers.Definitions
};
// manually url encode parenthesis to prevent "hacking" detection
searchUrl += queryCollection.GetQueryString().Replace("(", "%28").Replace(")", "%29");
searchUrl += queryCollection.GetQueryString().Replace("(", "%28").Replace(")", "%29").Replace(".", " ");
var request = new IndexerRequest(searchUrl, HttpAccept.Rss);

View File

@@ -17,7 +17,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete]
[Obsolete("Moved to YML for Cardigann v3")]
public class InternetArchive : TorrentIndexerBase<InternetArchiveSettings>
{
public override string Name => "Internet Archive";

View File

@@ -15,7 +15,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete]
[Obsolete("Moved to YML for Cardigann v3")]
public class Milkie : TorrentIndexerBase<MilkieSettings>
{
public override string Name => "Milkie";

View File

@@ -88,7 +88,7 @@ namespace NzbDrone.Core.Indexers.Newznab
yield return GetDefinition("altHUB", GetSettings("https://althub.co.za"));
yield return GetDefinition("AnimeTosho (Usenet)", GetSettings("https://feed.animetosho.org"));
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("GingaDADDY", GetSettings("https://www.gingadaddy.com"));
yield return GetDefinition("Miatrix", GetSettings("https://www.miatrix.com"));
yield return GetDefinition("Newz-Complex", GetSettings("https://newz-complex.org/www"));

View File

@@ -23,14 +23,6 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q, MusicSearchParam.Album, MusicSearchParam.Artist, MusicSearchParam.Label, MusicSearchParam.Year
@@ -45,8 +37,8 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PC, "Applications");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.Books, "E-Books");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.AudioAudiobook, "Audiobooks");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Movies, "E-Learning Videos");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.TV, "Comedy");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Other, "E-Learning Videos");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.Other, "Comedy");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.Books, "Comics");
return caps;

View File

@@ -142,6 +142,7 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(1836, NewznabStandardCategory.XXX, "Сайтрипы 2019 (HD Video) / SiteRip's 2019 (HD Video)");
caps.Categories.AddCategoryMapping(1842, NewznabStandardCategory.XXX, "Сайтрипы 2020 (HD Video) / SiteRip's 2020 (HD Video)");
caps.Categories.AddCategoryMapping(1846, NewznabStandardCategory.XXX, "Сайтрипы 2021 (HD Video) / SiteRip's 2021 (HD Video)");
caps.Categories.AddCategoryMapping(1857, NewznabStandardCategory.XXX, "Сайтрипы 2022 (HD Video) / SiteRip's 2022 (HD Video)");
caps.Categories.AddCategoryMapping(1451, NewznabStandardCategory.XXX, "Сайтрипы 1991-2010 / SiteRip's 1991-2010");
caps.Categories.AddCategoryMapping(1788, NewznabStandardCategory.XXX, "Сайтрипы 2011-2012 / SiteRip's 2011-2012");
@@ -154,6 +155,7 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(1837, NewznabStandardCategory.XXX, "Сайтрипы 2019 / SiteRip's 2019");
caps.Categories.AddCategoryMapping(1843, NewznabStandardCategory.XXX, "Сайтрипы 2020 / SiteRip's 2020");
caps.Categories.AddCategoryMapping(1847, NewznabStandardCategory.XXX, "Сайтрипы 2021 / SiteRip's 2021");
caps.Categories.AddCategoryMapping(1856, NewznabStandardCategory.XXX, "Сайтрипы 2022 / SiteRip's 2022");
caps.Categories.AddCategoryMapping(1707, NewznabStandardCategory.XXX, "Сцены из фильмов / Movie Scenes");
caps.Categories.AddCategoryMapping(284, NewznabStandardCategory.XXX, "Порноролики Разное / Clips (various)");

View File

@@ -36,7 +36,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
@@ -103,7 +104,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
try
{
var request = new HttpRequestBuilder(Settings.BaseUrl.Trim('/'))
.Resource("/pubapi_v2.php?get_token=get_token")
.Resource($"/pubapi_v2.php?get_token=get_token&app_id={BuildInfo.AppName}")
.Accept(HttpAccept.Json)
.Build();

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model;
@@ -62,7 +63,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
torrentInfo.Title = torrent.title;
torrentInfo.Size = torrent.size;
torrentInfo.DownloadUrl = torrent.download;
torrentInfo.InfoUrl = torrent.info_page + "&app_id=Prowlarr";
torrentInfo.InfoUrl = $"{torrent.info_page}&app_id={BuildInfo.AppName}";
torrentInfo.PublishDate = torrent.pubdate.ToUniversalTime();
torrentInfo.Seeders = torrent.seeders;
torrentInfo.Peers = torrent.leechers + torrent.seeders;

View File

@@ -2,6 +2,7 @@ using System;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
@@ -32,7 +33,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
{
var requestBuilder = new HttpRequestBuilder(baseUrl.Trim('/'))
.WithRateLimit(3.0)
.Resource("/pubapi_v2.php?get_token=get_token&app_id=Prowlarr")
.Resource($"/pubapi_v2.php?get_token=get_token&app_id={BuildInfo.AppName}")
.Accept(HttpAccept.Json);
if (settings.CaptchaToken.IsNotNullOrWhiteSpace())

View File

@@ -8,7 +8,7 @@ using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete]
[Obsolete("Moved to YML for Cardigann v3")]
public class ShareIsland : Unit3dBase
{
public override string Name => "ShareIsland";

View File

@@ -18,7 +18,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete]
[Obsolete("Moved to YML for Cardigann v3")]
public class SuperBits : TorrentIndexerBase<SuperBitsSettings>
{
public override string Name => "SuperBits";

View File

@@ -22,6 +22,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete("Remove per Site Request Prowlarr Issue 573")]
public class TVVault : TorrentIndexerBase<TVVaultSettings>
{
public override string Name => "TVVault";

View File

@@ -16,7 +16,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete]
[Obsolete("Moved to YML for Cardigann v3")]
public class ThePirateBay : TorrentIndexerBase<ThePirateBaySettings>
{
public override string Name => "ThePirateBay";

View File

@@ -20,7 +20,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete]
[Obsolete("Moved to YML for Cardigann v3")]
public class TorrentLeech : TorrentIndexerBase<TorrentLeechSettings>
{
public override string Name => "TorrentLeech";

View File

@@ -18,7 +18,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete]
[Obsolete("Moved to YML for Cardigann v3")]
public class TorrentParadiseMl : TorrentIndexerBase<TorrentParadiseMlSettings>
{
public override string Name => "TorrentParadiseMl";

View File

@@ -18,7 +18,7 @@ using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete]
[Obsolete("Moved to YML for Cardigann v3")]
public class YTS : TorrentIndexerBase<YTSSettings>
{
public override string Name => "YTS";

View File

@@ -203,7 +203,7 @@
"IndexersSelectedInterp": "{0} Indexer(s) Selected",
"IndexerStatusCheckAllClientMessage": "All indexers are unavailable due to failures",
"IndexerStatusCheckSingleClientMessage": "Indexers unavailable due to failures: {0}",
"IndexerTagsHelpText": "Use tags to specify default clients, specify Indexer Proxies, or just to organize your indexers.",
"IndexerTagsHelpText": "Use tags to specify Indexer Proxies or just to organize your indexers.",
"IndexerVipCheckExpiredClientMessage": "Indexer VIP benefits have expired: {0}",
"IndexerVipCheckExpiringClientMessage": "Indexer VIP benefits expiring soon: {0}",
"Info": "Info",
@@ -262,6 +262,9 @@
"NoUpdatesAreAvailable": "No updates are available",
"OAuthPopupMessage": "Pop-ups are being blocked by your browser",
"Ok": "Ok",
"OnApplicationUpdate": "On Application Update",
"OnApplicationUpdateHelpText": "On Application Update",
"OnHealthIssue": "On Health Issue",
"OnHealthIssueHelpText": "On Health Issue",
"OpenBrowserOnStart": "Open browser on start",
"OpenThisModal": "Open This Modal",
@@ -446,4 +449,4 @@
"Wiki": "Wiki",
"YesCancel": "Yes, Cancel",
"Yesterday": "Yesterday"
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace NzbDrone.Core.Notifications
{
public class ApplicationUpdateMessage
{
public string Message { get; set; }
public Version PreviousVersion { get; set; }
public Version NewVersion { get; set; }
public override string ToString()
{
return NewVersion.ToString();
}
}
}

View File

@@ -20,6 +20,11 @@ namespace NzbDrone.Core.Notifications.Boxcar
_proxy.SendNotification(HEALTH_ISSUE_TITLE, message.Message, Settings);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage message)
{
_proxy.SendNotification(APPLICATION_UPDATE_TITLE, message.Message, Settings);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();

View File

@@ -44,6 +44,18 @@ namespace NzbDrone.Core.Notifications.CustomScript
ExecuteScript(environmentVariables);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
var environmentVariables = new StringDictionary();
environmentVariables.Add("Prowlarr_EventType", "ApplicationUpdate");
environmentVariables.Add("Prowlarr_Update_Message", updateMessage.Message);
environmentVariables.Add("Prowlarr_Update_NewVersion", updateMessage.NewVersion.ToString());
environmentVariables.Add("Prowlarr_Update_PreviousVersion", updateMessage.PreviousVersion.ToString());
ExecuteScript(environmentVariables);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();

View File

@@ -42,6 +42,41 @@ namespace NzbDrone.Core.Notifications.Discord
_proxy.SendPayload(payload, Settings);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
var attachments = new List<Embed>
{
new Embed
{
Author = new DiscordAuthor
{
Name = Settings.Author.IsNullOrWhiteSpace() ? Environment.MachineName : Settings.Author,
IconUrl = "https://raw.githubusercontent.com/Prowlarr/Prowlarr/develop/Logo/256.png"
},
Title = APPLICATION_UPDATE_TITLE,
Timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
Color = (int)DiscordColors.Standard,
Fields = new List<DiscordField>()
{
new DiscordField()
{
Name = "Previous Version",
Value = updateMessage.PreviousVersion.ToString()
},
new DiscordField()
{
Name = "New Version",
Value = updateMessage.NewVersion.ToString()
}
},
}
};
var payload = CreatePayload(null, attachments);
_proxy.SendPayload(payload, Settings);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();

View File

@@ -28,6 +28,13 @@ namespace NzbDrone.Core.Notifications.Email
SendEmail(Settings, HEALTH_ISSUE_TITLE_BRANDED, message.Message);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
var body = $"{updateMessage.Message}";
SendEmail(Settings, APPLICATION_UPDATE_TITLE_BRANDED, body);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();

View File

@@ -32,15 +32,13 @@ namespace NzbDrone.Core.Notifications.Email
public EmailSettings()
{
Server = "smtp.gmail.com";
Port = 587;
To = Array.Empty<string>();
CC = Array.Empty<string>();
Bcc = Array.Empty<string>();
}
[FieldDefinition(0, Label = "Server", HelpText = "Hostname or IP of Email server")]
[FieldDefinition(0, Label = "Server", HelpText = "Hostname or IP of Email server", Placeholder = "smtp.gmail.com")]
public string Server { get; set; }
[FieldDefinition(1, Label = "Port")]
@@ -55,16 +53,16 @@ namespace NzbDrone.Core.Notifications.Email
[FieldDefinition(4, Label = "Password", HelpText = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "From Address")]
[FieldDefinition(5, Label = "From Address", Placeholder = "example@email.com")]
public string From { get; set; }
[FieldDefinition(6, Label = "Recipient Address(es)", HelpText = "Comma separated list of email recipients")]
[FieldDefinition(6, Label = "Recipient Address(es)", HelpText = "Comma separated list of email recipients", Placeholder = "example@email.com,example1@email.com")]
public IEnumerable<string> To { get; set; }
[FieldDefinition(7, Label = "CC Address(es)", HelpText = "Comma separated list of email cc recipients", Advanced = true)]
[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; }
[FieldDefinition(8, Label = "BCC Address(es)", HelpText = "Comma separated list of email bcc recipients", Advanced = true)]
[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; }
public NzbDroneValidationResult Validate()

View File

@@ -24,6 +24,11 @@ namespace NzbDrone.Core.Notifications.Gotify
_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>();

View File

@@ -7,7 +7,9 @@ namespace NzbDrone.Core.Notifications
string Link { get; }
void OnHealthIssue(HealthCheck.HealthCheck healthCheck);
void OnApplicationUpdate(ApplicationUpdateMessage updateMessage);
void ProcessQueue();
bool SupportsOnHealthIssue { get; }
bool SupportsOnApplicationUpdate { get; }
}
}

View File

@@ -22,6 +22,11 @@ namespace NzbDrone.Core.Notifications.Join
_proxy.SendNotification(HEALTH_ISSUE_TITLE_BRANDED, message.Message, Settings);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
_proxy.SendNotification(APPLICATION_UPDATE_TITLE_BRANDED, updateMessage.Message, Settings);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();

View File

@@ -29,6 +29,18 @@ namespace NzbDrone.Core.Notifications.Notifiarr
_proxy.SendNotification(variables, Settings);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
var variables = new StringDictionary();
variables.Add("Prowlarr_EventType", "ApplicationUpdate");
variables.Add("Prowlarr_Update_Message", updateMessage.Message);
variables.Add("Prowlarr_Update_NewVersion", updateMessage.NewVersion.ToString());
variables.Add("Prowlarr_Update_PreviousVersion", updateMessage.PreviousVersion.ToString());
_proxy.SendNotification(variables, Settings);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();

View File

@@ -9,8 +9,10 @@ namespace NzbDrone.Core.Notifications
where TSettings : IProviderConfig, new()
{
protected const string HEALTH_ISSUE_TITLE = "Health Check Failure";
protected const string APPLICATION_UPDATE_TITLE = "Application Updated";
protected const string HEALTH_ISSUE_TITLE_BRANDED = "Prowlarr - " + HEALTH_ISSUE_TITLE;
protected const string APPLICATION_UPDATE_TITLE_BRANDED = "Prowlarr - " + APPLICATION_UPDATE_TITLE;
public abstract string Name { get; }
@@ -29,11 +31,16 @@ namespace NzbDrone.Core.Notifications
{
}
public virtual void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
}
public virtual void ProcessQueue()
{
}
public bool SupportsOnHealthIssue => HasConcreteImplementation("OnHealthIssue");
public bool SupportsOnApplicationUpdate => HasConcreteImplementation("OnApplicationUpdate");
protected TSettings Settings => (TSettings)Definition.Settings;

View File

@@ -5,9 +5,11 @@ namespace NzbDrone.Core.Notifications
public class NotificationDefinition : ProviderDefinition
{
public bool OnHealthIssue { get; set; }
public bool OnApplicationUpdate { get; set; }
public bool SupportsOnHealthIssue { get; set; }
public bool IncludeHealthWarnings { get; set; }
public bool SupportsOnApplicationUpdate { get; set; }
public override bool Enable => OnHealthIssue;
public override bool Enable => OnHealthIssue || OnApplicationUpdate;
}
}

View File

@@ -10,6 +10,7 @@ namespace NzbDrone.Core.Notifications
public interface INotificationFactory : IProviderFactory<INotification, NotificationDefinition>
{
List<INotification> OnHealthIssueEnabled();
List<INotification> OnApplicationUpdateEnabled();
}
public class NotificationFactory : ProviderFactory<INotification, NotificationDefinition>, INotificationFactory
@@ -24,11 +25,17 @@ namespace NzbDrone.Core.Notifications
return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnHealthIssue).ToList();
}
public List<INotification> OnApplicationUpdateEnabled()
{
return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnApplicationUpdate).ToList();
}
public override void SetProviderCharacteristics(INotification provider, NotificationDefinition definition)
{
base.SetProviderCharacteristics(provider, definition);
definition.SupportsOnHealthIssue = provider.SupportsOnHealthIssue;
definition.SupportsOnApplicationUpdate = provider.SupportsOnApplicationUpdate;
}
}
}

View File

@@ -2,12 +2,14 @@ using System;
using NLog;
using NzbDrone.Core.HealthCheck;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Update.History.Events;
namespace NzbDrone.Core.Notifications
{
public class NotificationService
: IHandle<HealthCheckFailedEvent>,
IHandleAsync<HealthCheckCompleteEvent>
IHandleAsync<HealthCheckCompleteEvent>,
IHandle<UpdateInstalledEvent>
{
private readonly INotificationFactory _notificationFactory;
private readonly Logger _logger;
@@ -56,6 +58,26 @@ namespace NzbDrone.Core.Notifications
ProcessQueue();
}
public void Handle(UpdateInstalledEvent message)
{
var updateMessage = new ApplicationUpdateMessage();
updateMessage.Message = $"Prowlarr updated from {message.PreviousVerison.ToString()} to {message.NewVersion.ToString()}";
updateMessage.PreviousVersion = message.PreviousVerison;
updateMessage.NewVersion = message.NewVersion;
foreach (var notification in _notificationFactory.OnApplicationUpdateEnabled())
{
try
{
notification.OnApplicationUpdate(updateMessage);
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to send OnApplicationUpdate notification to: " + notification.Definition.Name);
}
}
}
private void ProcessQueue()
{
foreach (var notification in _notificationFactory.GetAvailableProviders())

View File

@@ -21,6 +21,11 @@ namespace NzbDrone.Core.Notifications.Prowl
_prowlProxy.SendNotification(HEALTH_ISSUE_TITLE, healthCheck.Message, Settings);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
_prowlProxy.SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, Settings);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();

View File

@@ -24,6 +24,11 @@ namespace NzbDrone.Core.Notifications.PushBullet
_proxy.SendNotification(HEALTH_ISSUE_TITLE_BRANDED, healthCheck.Message, Settings);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
_proxy.SendNotification(APPLICATION_UPDATE_TITLE_BRANDED, updateMessage.Message, Settings);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();

View File

@@ -28,10 +28,10 @@ namespace NzbDrone.Core.Notifications.PushBullet
[FieldDefinition(0, Label = "Access Token", Privacy = PrivacyLevel.ApiKey, HelpLink = "https://www.pushbullet.com/#settings/account")]
public string ApiKey { get; set; }
[FieldDefinition(1, Label = "Device IDs", HelpText = "List of device IDs (leave blank to send to all devices)", Type = FieldType.Device)]
[FieldDefinition(1, Label = "Device IDs", HelpText = "List of device IDs (leave blank to send to all devices)", Type = FieldType.Device, Placeholder = "123456789,987654321")]
public IEnumerable<string> DeviceIds { get; set; }
[FieldDefinition(2, Label = "Channel Tags", HelpText = "List of Channel Tags to send notifications to", Type = FieldType.Tag)]
[FieldDefinition(2, Label = "Channel Tags", HelpText = "List of Channel Tags to send notifications to", Type = FieldType.Tag, Placeholder = "Channel1234,Channel4321")]
public IEnumerable<string> ChannelTags { get; set; }
[FieldDefinition(3, Label = "Sender ID", HelpText = "The device ID to send notifications from, use device_iden in the device's URL on pushbullet.com (leave blank to send from yourself)")]

View File

@@ -21,6 +21,11 @@ namespace NzbDrone.Core.Notifications.Pushover
_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>();

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
@@ -23,7 +24,7 @@ namespace NzbDrone.Core.Notifications.Pushover
public PushoverSettings()
{
Priority = 0;
Devices = System.Array.Empty<string>();
Devices = Array.Empty<string>();
}
//TODO: Get Pushover to change our app name (or create a new app) when we have a new logo
@@ -33,7 +34,7 @@ namespace NzbDrone.Core.Notifications.Pushover
[FieldDefinition(1, Label = "User Key", Privacy = PrivacyLevel.UserName, HelpLink = "https://pushover.net/")]
public string UserKey { get; set; }
[FieldDefinition(2, Label = "Devices", HelpText = "List of device names (leave blank to send to all devices)", Type = FieldType.Tag)]
[FieldDefinition(2, Label = "Devices", HelpText = "List of device names (leave blank to send to all devices)", Type = FieldType.Tag, Placeholder = "device1")]
public IEnumerable<string> Devices { get; set; }
[FieldDefinition(3, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(PushoverPriority))]

View File

@@ -24,6 +24,11 @@ namespace NzbDrone.Core.Notifications.SendGrid
_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>();

View File

@@ -36,7 +36,7 @@ namespace NzbDrone.Core.Notifications.SendGrid
[FieldDefinition(2, Label = "From Address")]
public string From { get; set; }
[FieldDefinition(3, Label = "Recipient Address(es)", Type = FieldType.Tag)]
[FieldDefinition(3, Label = "Recipient Address(es)", Type = FieldType.Tag, Placeholder = "example@email.com,example1@email.com")]
public IEnumerable<string> Recipients { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -36,6 +36,23 @@ namespace NzbDrone.Core.Notifications.Slack
_proxy.SendPayload(payload, Settings);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
var attachments = new List<Attachment>
{
new Attachment
{
Title = Environment.MachineName,
Text = updateMessage.Message,
Color = "good"
}
};
var payload = CreatePayload("Application Updated", attachments);
_proxy.SendPayload(payload, Settings);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();

View File

@@ -21,6 +21,11 @@ namespace NzbDrone.Core.Notifications.Telegram
_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>();

View File

@@ -23,6 +23,11 @@ namespace NzbDrone.Core.Notifications.Twitter
_twitterService.SendNotification($"Health Issue: {healthCheck.Message}", Settings);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
_twitterService.SendNotification($"Application Updated: {updateMessage.Message}", Settings);
}
public override object RequestAction(string action, IDictionary<string, string> query)
{
if (action == "startOAuth")

View File

@@ -19,13 +19,26 @@ namespace NzbDrone.Core.Notifications.Webhook
public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck)
{
var payload = new WebhookHealthPayload
{
EventType = WebhookEventType.Health,
Level = healthCheck.Type,
Message = healthCheck.Message,
Type = healthCheck.Source.Name,
WikiUrl = healthCheck.WikiUrl?.ToString()
};
{
EventType = WebhookEventType.Health,
Level = healthCheck.Type,
Message = healthCheck.Message,
Type = healthCheck.Source.Name,
WikiUrl = healthCheck.WikiUrl?.ToString()
};
_proxy.SendWebhook(payload, Settings);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
var payload = new WebhookApplicationUpdatePayload
{
EventType = WebhookEventType.ApplicationUpdate,
Message = updateMessage.Message,
PreviousVersion = updateMessage.PreviousVersion.ToString(),
NewVersion = updateMessage.NewVersion.ToString()
};
_proxy.SendWebhook(payload, Settings);
}

View File

@@ -0,0 +1,11 @@
using NzbDrone.Core.HealthCheck;
namespace NzbDrone.Core.Notifications.Webhook
{
public class WebhookApplicationUpdatePayload : WebhookPayload
{
public string Message { get; set; }
public string PreviousVersion { get; set; }
public string NewVersion { get; set; }
}
}

View File

@@ -12,6 +12,7 @@ namespace NzbDrone.Core.Notifications.Webhook
Grab,
Download,
Rename,
Health
Health,
ApplicationUpdate
}
}

View File

@@ -0,0 +1,466 @@
//********************************************************************************************
//Author: Sergey Stoyan, CliverSoft.com
// http://cliversoft.com
// stoyan@cliversoft.com
// sergey.stoyan@gmail.com
// 27 February 2007
//********************************************************************************************
using System;
using System.Text.RegularExpressions;
namespace NzbDrone.Core.Parser
{
public static class DateTimeRoutines
{
public class ParsedDateTime
{
public readonly int IndexOfDate = -1;
public readonly int LengthOfDate = -1;
public readonly int IndexOfTime = -1;
public readonly int LengthOfTime = -1;
public readonly DateTime DateTime;
public readonly bool IsDateFound;
public readonly bool IsTimeFound;
public readonly TimeSpan UtcOffset;
public readonly bool IsUtcOffsetFound;
public DateTime UtcDateTime;
internal ParsedDateTime(int index_of_date, int length_of_date, int index_of_time, int length_of_time, DateTime date_time)
{
IndexOfDate = index_of_date;
LengthOfDate = length_of_date;
IndexOfTime = index_of_time;
LengthOfTime = length_of_time;
DateTime = date_time;
IsDateFound = index_of_date > -1;
IsTimeFound = index_of_time > -1;
UtcOffset = new TimeSpan(25, 0, 0);
IsUtcOffsetFound = false;
UtcDateTime = new DateTime(1, 1, 1);
}
internal ParsedDateTime(int index_of_date, int length_of_date, int index_of_time, int length_of_time, DateTime date_time, TimeSpan utc_offset)
{
IndexOfDate = index_of_date;
LengthOfDate = length_of_date;
IndexOfTime = index_of_time;
LengthOfTime = length_of_time;
DateTime = date_time;
IsDateFound = index_of_date > -1;
IsTimeFound = index_of_time > -1;
UtcOffset = utc_offset;
IsUtcOffsetFound = Math.Abs(utc_offset.TotalHours) < 12;
if (!IsUtcOffsetFound)
{
UtcDateTime = new DateTime(1, 1, 1);
}
else
{
if (index_of_date < 0)
{
//to avoid negative date exception when date is undefined
var ts = date_time.TimeOfDay + utc_offset;
if (ts < new TimeSpan(0))
{
UtcDateTime = new DateTime(1, 1, 2) + ts;
}
else
{
UtcDateTime = new DateTime(1, 1, 1) + ts;
}
}
else
{
UtcDateTime = date_time + utc_offset;
}
}
}
}
public static DateTime DefaultDate
{
get
{
if (DefaultDateIsNow)
{
return DateTime.Now;
}
else
{
return _DefaultDate;
}
}
set
{
_DefaultDate = value;
DefaultDateIsNow = false;
}
}
private static DateTime _DefaultDate = DateTime.Now;
public static bool DefaultDateIsNow = true;
public enum DateTimeFormat
{
USDate,
UKDate,
}
public static bool TryParseDateOrTime(this string str, DateTimeFormat default_format, out ParsedDateTime parsed_date_time)
{
parsed_date_time = null;
ParsedDateTime parsed_date;
ParsedDateTime parsed_time;
if (!TryParseDate(str, default_format, out parsed_date))
{
if (!TryParseTime(str, default_format, out parsed_time, null))
{
return false;
}
var date_time = new DateTime(DefaultDate.Year, DefaultDate.Month, DefaultDate.Day, parsed_time.DateTime.Hour, parsed_time.DateTime.Minute, parsed_time.DateTime.Second);
parsed_date_time = new ParsedDateTime(-1, -1, parsed_time.IndexOfTime, parsed_time.LengthOfTime, date_time, parsed_time.UtcOffset);
}
else
{
if (!TryParseTime(str, default_format, out parsed_time, parsed_date))
{
var date_time = new DateTime(parsed_date.DateTime.Year, parsed_date.DateTime.Month, parsed_date.DateTime.Day, 0, 0, 0);
parsed_date_time = new ParsedDateTime(parsed_date.IndexOfDate, parsed_date.LengthOfDate, -1, -1, date_time);
}
else
{
var date_time = new DateTime(parsed_date.DateTime.Year, parsed_date.DateTime.Month, parsed_date.DateTime.Day, parsed_time.DateTime.Hour, parsed_time.DateTime.Minute, parsed_time.DateTime.Second);
parsed_date_time = new ParsedDateTime(parsed_date.IndexOfDate, parsed_date.LengthOfDate, parsed_time.IndexOfTime, parsed_time.LengthOfTime, date_time, parsed_time.UtcOffset);
}
}
return true;
}
public static bool TryParseTime(this string str, DateTimeFormat default_format, out ParsedDateTime parsed_time, ParsedDateTime parsed_date)
{
parsed_time = null;
string time_zone_r;
if (default_format == DateTimeFormat.USDate)
{
time_zone_r = @"(?:\s*(?'time_zone'UTC|GMT|CST|EST))?";
}
else
{
time_zone_r = @"(?:\s*(?'time_zone'UTC|GMT))?";
}
Match m;
if (parsed_date != null && parsed_date.IndexOfDate > -1)
{
//look around the found date
//look for <date> hh:mm:ss <UTC offset>
m = Regex.Match(str.Substring(parsed_date.IndexOfDate + parsed_date.LengthOfDate), @"(?<=^\s*,?\s+|^\s*at\s*|^\s*[T\-]\s*)(?'hour'\d{2})\s*:\s*(?'minute'\d{2})\s*:\s*(?'second'\d{2})\s+(?'offset_sign'[\+\-])(?'offset_hh'\d{2}):?(?'offset_mm'\d{2})(?=$|[^\d\w])", RegexOptions.Compiled);
if (!m.Success)
{
//look for <date> [h]h:mm[:ss] [PM/AM] [UTC/GMT]
m = Regex.Match(str.Substring(parsed_date.IndexOfDate + parsed_date.LengthOfDate), @"(?<=^\s*,?\s+|^\s*at\s*|^\s*[T\-]\s*)(?'hour'\d{1,2})\s*:\s*(?'minute'\d{2})\s*(?::\s*(?'second'\d{2}))?(?:\s*(?'ampm'AM|am|PM|pm))?" + time_zone_r + @"(?=$|[^\d\w])", RegexOptions.Compiled);
}
if (!m.Success)
{
//look for [h]h:mm:ss [PM/AM] [UTC/GMT] <date>
m = Regex.Match(str.Substring(0, parsed_date.IndexOfDate), @"(?<=^|[^\d])(?'hour'\d{1,2})\s*:\s*(?'minute'\d{2})\s*(?::\s*(?'second'\d{2}))?(?:\s*(?'ampm'AM|am|PM|pm))?" + time_zone_r + @"(?=$|[\s,]+)", RegexOptions.Compiled);
}
if (!m.Success)
{
//look for [h]h:mm:ss [PM/AM] [UTC/GMT] within <date>
m = Regex.Match(str.Substring(parsed_date.IndexOfDate, parsed_date.LengthOfDate), @"(?<=^|[^\d])(?'hour'\d{1,2})\s*:\s*(?'minute'\d{2})\s*(?::\s*(?'second'\d{2}))?(?:\s*(?'ampm'AM|am|PM|pm))?" + time_zone_r + @"(?=$|[\s,]+)", RegexOptions.Compiled);
}
}
else
{
//look anywhere within string
//look for hh:mm:ss <UTC offset>
m = Regex.Match(str, @"(?<=^|\s+|\s*T\s*)(?'hour'\d{2})\s*:\s*(?'minute'\d{2})\s*:\s*(?'second'\d{2})\s+(?'offset_sign'[\+\-])(?'offset_hh'\d{2}):?(?'offset_mm'\d{2})?(?=$|[^\d\w])", RegexOptions.Compiled);
if (!m.Success)
{
//look for [h]h:mm[:ss] [PM/AM] [UTC/GMT]
m = Regex.Match(str, @"(?<=^|\s+|\s*T\s*)(?'hour'\d{1,2})\s*:\s*(?'minute'\d{2})\s*(?::\s*(?'second'\d{2}))?(?:\s*(?'ampm'AM|am|PM|pm))?" + time_zone_r + @"(?=$|[^\d\w])", RegexOptions.Compiled);
}
}
if (!m.Success)
{
return false;
}
//try
//{
var hour = int.Parse(m.Groups["hour"].Value);
if (hour < 0 || hour > 23)
{
return false;
}
var minute = int.Parse(m.Groups["minute"].Value);
if (minute < 0 || minute > 59)
{
return false;
}
var second = 0;
if (!string.IsNullOrEmpty(m.Groups["second"].Value))
{
second = int.Parse(m.Groups["second"].Value);
if (second < 0 || second > 59)
{
return false;
}
}
if (string.Compare(m.Groups["ampm"].Value, "PM", true) == 0 && hour < 12)
{
hour += 12;
}
else if (string.Compare(m.Groups["ampm"].Value, "AM", true) == 0 && hour == 12)
{
hour -= 12;
}
var date_time = new DateTime(1, 1, 1, hour, minute, second);
if (m.Groups["offset_hh"].Success)
{
var offset_hh = int.Parse(m.Groups["offset_hh"].Value);
var offset_mm = 0;
if (m.Groups["offset_mm"].Success)
{
offset_mm = int.Parse(m.Groups["offset_mm"].Value);
}
var utc_offset = new TimeSpan(offset_hh, offset_mm, 0);
if (m.Groups["offset_sign"].Value == "-")
{
utc_offset = -utc_offset;
}
parsed_time = new ParsedDateTime(-1, -1, m.Index, m.Length, date_time, utc_offset);
return true;
}
if (m.Groups["time_zone"].Success)
{
TimeSpan utc_offset;
switch (m.Groups["time_zone"].Value)
{
case "UTC":
case "GMT":
utc_offset = new TimeSpan(0, 0, 0);
break;
case "CST":
utc_offset = new TimeSpan(-6, 0, 0);
break;
case "EST":
utc_offset = new TimeSpan(-5, 0, 0);
break;
default:
throw new Exception("Time zone: " + m.Groups["time_zone"].Value + " is not defined.");
}
parsed_time = new ParsedDateTime(-1, -1, m.Index, m.Length, date_time, utc_offset);
return true;
}
parsed_time = new ParsedDateTime(-1, -1, m.Index, m.Length, date_time);
return true;
}
public static bool TryParseDate(this string str, DateTimeFormat default_format, out ParsedDateTime parsed_date)
{
parsed_date = null;
if (string.IsNullOrEmpty(str))
{
return false;
}
//look for dd/mm/yy
var m = Regex.Match(str, @"(?<=^|[^\d])(?'day'\d{1,2})\s*(?'separator'[\\/\.])+\s*(?'month'\d{1,2})\s*\'separator'+\s*(?'year'\d{2}|\d{4})(?=$|[^\d])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
if (m.Success)
{
DateTime date;
if ((default_format ^ DateTimeFormat.USDate) == DateTimeFormat.USDate)
{
if (!ConvertToDate(int.Parse(m.Groups["year"].Value), int.Parse(m.Groups["day"].Value), int.Parse(m.Groups["month"].Value), out date))
{
return false;
}
}
else
{
if (!ConvertToDate(int.Parse(m.Groups["year"].Value), int.Parse(m.Groups["month"].Value), int.Parse(m.Groups["day"].Value), out date))
{
return false;
}
}
parsed_date = new ParsedDateTime(m.Index, m.Length, -1, -1, date);
return true;
}
//look for [yy]yy-mm-dd
m = Regex.Match(str, @"(?<=^|[^\d])(?'year'\d{2}|\d{4})\s*(?'separator'[\-])\s*(?'month'\d{1,2})\s*\'separator'+\s*(?'day'\d{1,2})(?=$|[^\d])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
if (m.Success)
{
DateTime date;
if (!ConvertToDate(int.Parse(m.Groups["year"].Value), int.Parse(m.Groups["month"].Value), int.Parse(m.Groups["day"].Value), out date))
{
return false;
}
parsed_date = new ParsedDateTime(m.Index, m.Length, -1, -1, date);
return true;
}
//look for month dd yyyy
m = Regex.Match(str, @"(?:^|[^\d\w])(?'month'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[uarychilestmbro]*\s+(?'day'\d{1,2})(?:-?st|-?th|-?rd|-?nd)?\s*,?\s*(?'year'\d{4})(?=$|[^\d\w])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
if (!m.Success)
{
//look for dd month [yy]yy
m = Regex.Match(str, @"(?:^|[^\d\w:])(?'day'\d{1,2})(?:-?st\s+|-?th\s+|-?rd\s+|-?nd\s+|-|\s+)(?'month'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[uarychilestmbro]*(?:\s*,?\s*|-)'?(?'year'\d{2}|\d{4})(?=$|[^\d\w])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
if (!m.Success)
{
//look for yyyy month dd
m = Regex.Match(str, @"(?:^|[^\d\w])(?'year'\d{4})\s+(?'month'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[uarychilestmbro]*\s+(?'day'\d{1,2})(?:-?st|-?th|-?rd|-?nd)?(?=$|[^\d\w])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
if (!m.Success)
{
//look for month dd hh:mm:ss MDT|UTC yyyy
m = Regex.Match(str, @"(?:^|[^\d\w])(?'month'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[uarychilestmbro]*\s+(?'day'\d{1,2})\s+\d{2}\:\d{2}\:\d{2}\s+(?:MDT|UTC)\s+(?'year'\d{4})(?=$|[^\d\w])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
if (!m.Success)
{
//look for month dd [yyyy]
m = Regex.Match(str, @"(?:^|[^\d\w])(?'month'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[uarychilestmbro]*\s+(?'day'\d{1,2})(?:-?st|-?th|-?rd|-?nd)?(?:\s*,?\s*(?'year'\d{4}))?(?=$|[^\d\w])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
if (m.Success)
{
var month = -1;
var index_of_date = m.Index;
var length_of_date = m.Length;
switch (m.Groups["month"].Value)
{
case "Jan":
case "JAN":
month = 1;
break;
case "Feb":
case "FEB":
month = 2;
break;
case "Mar":
case "MAR":
month = 3;
break;
case "Apr":
case "APR":
month = 4;
break;
case "May":
case "MAY":
month = 5;
break;
case "Jun":
case "JUN":
month = 6;
break;
case "Jul":
month = 7;
break;
case "Aug":
case "AUG":
month = 8;
break;
case "Sep":
case "SEP":
month = 9;
break;
case "Oct":
case "OCT":
month = 10;
break;
case "Nov":
case "NOV":
month = 11;
break;
case "Dec":
case "DEC":
month = 12;
break;
}
int year;
if (!string.IsNullOrEmpty(m.Groups["year"].Value))
{
year = int.Parse(m.Groups["year"].Value);
}
else
{
year = DefaultDate.Year;
}
DateTime date;
if (!ConvertToDate(year, month, int.Parse(m.Groups["day"].Value), out date))
{
return false;
}
parsed_date = new ParsedDateTime(index_of_date, length_of_date, -1, -1, date);
return true;
}
return false;
}
private static bool ConvertToDate(int year, int month, int day, out DateTime date)
{
if (year >= 100)
{
if (year < 1000)
{
date = new DateTime(1, 1, 1);
return false;
}
}
else
if (year > 30)
{
year += 1900;
}
else
{
year += 2000;
}
try
{
date = new DateTime(year, month, day);
}
catch
{
date = new DateTime(1, 1, 1);
return false;
}
return true;
}
}
}

View File

@@ -91,7 +91,7 @@ namespace NzbDrone.Core.Parser
}
else
{
throw new Exception("TimeAgo parsing failed, unknown unit: " + unit);
throw new InvalidDateException("TimeAgo parsing failed, unknown unit: " + unit);
}
}
@@ -102,19 +102,17 @@ namespace NzbDrone.Core.Parser
// http://www.codeproject.com/Articles/33298/C-Date-Time-Parser
public static DateTime FromFuzzyTime(string str, string format = null)
{
//var dtFormat = format == "UK" ?
// DateTimeRoutines.DateTimeRoutines.DateTimeFormat.UkDate :
// DateTimeRoutines.DateTimeRoutines.DateTimeFormat.UsaDate;
var dtFormat = format == "UK" ?
DateTimeRoutines.DateTimeFormat.UKDate :
DateTimeRoutines.DateTimeFormat.USDate;
//if (DateTimeRoutines.DateTimeRoutines.TryParseDateOrTime(
// str, dtFormat, out DateTimeRoutines.DateTimeRoutines.ParsedDateTime dt))
// return dt.DateTime;
if (DateTime.TryParse(str, out var dateTimeParsed))
if (DateTimeRoutines.TryParseDateOrTime(
str, dtFormat, out DateTimeRoutines.ParsedDateTime dt))
{
return dateTimeParsed;
return dt.DateTime;
}
throw new Exception($"FromFuzzyTime parsing failed for string {str}");
throw new InvalidDateException($"FromFuzzyTime parsing failed for string {str}");
}
public static DateTime FromUnknown(string str, string format = null)
@@ -214,16 +212,13 @@ namespace NzbDrone.Core.Parser
return dt;
}
try
// try parsing the str as an unix timestamp
if (long.TryParse(str, out var unixTimeStamp))
{
// try parsing the str as an unix timestamp
var unixTimeStamp = long.Parse(str);
return UnixTimestampToDateTime(unixTimeStamp);
}
catch (FormatException)
{
// it wasn't a timestamp, continue....
}
// it wasn't a timestamp, continue....
// add missing year
match = _MissingYearRegexp.Match(str);
@@ -247,7 +242,7 @@ namespace NzbDrone.Core.Parser
}
catch (Exception ex)
{
throw new Exception($"DateTime parsing failed for \"{str}\": {ex}");
throw new InvalidDateException($"DateTime parsing failed for \"{str}\": {ex}");
}
}
@@ -317,7 +312,7 @@ namespace NzbDrone.Core.Parser
}
catch (FormatException ex)
{
throw new FormatException($"Error while parsing DateTime \"{date}\", using layout \"{layout}\" ({pattern}): {ex.Message}");
throw new InvalidDateException($"Error while parsing DateTime \"{date}\", using layout \"{layout}\" ({pattern}): {ex.Message}");
}
}

View File

@@ -8,8 +8,10 @@ using System.Security.Cryptography.X509Certificates;
using System.Text;
using DryIoc;
using DryIoc.Microsoft.DependencyInjection;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting.WindowsServices;
using NLog;
@@ -129,6 +131,7 @@ namespace NzbDrone.Host
})
.ConfigureWebHost(builder =>
{
builder.UseConfiguration(config);
builder.UseUrls(urls.ToArray());
builder.UseKestrel(options =>
{
@@ -171,8 +174,6 @@ namespace NzbDrone.Host
return ApplicationModes.UninstallService;
}
Logger.Debug("Getting windows service status");
// IsWindowsService can throw sometimes, so wrap it
var isWindowsService = false;
try
@@ -197,6 +198,7 @@ namespace NzbDrone.Host
var appFolder = new AppFolderInfo(context);
return new ConfigurationBuilder()
.AddXmlFile(appFolder.GetConfigPath(), optional: true, reloadOnChange: false)
.AddInMemoryCollection(new List<KeyValuePair<string, string>> { new ("dataProtectionFolder", appFolder.GetDataProtectionPath()) })
.Build();
}

View File

@@ -7,6 +7,7 @@
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.2" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.2.3" />
<PackageReference Include="DryIoc.dll" Version="4.8.1" />
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="5.1.0" />
</ItemGroup>

View File

@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using NLog.Extensions.Logging;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation;
@@ -45,6 +48,7 @@ namespace NzbDrone.Host
b.SetMinimumLevel(LogLevel.Trace);
b.AddFilter("Microsoft.AspNetCore", LogLevel.Warning);
b.AddFilter("Prowlarr.Http.Authentication", LogLevel.Information);
b.AddFilter("Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager", LogLevel.Error);
b.AddNLog();
});
@@ -87,6 +91,73 @@ namespace NzbDrone.Host
})
.AddControllersAsServices();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "1.0.0",
Title = "Prowlarr",
Description = "Prowlarr API docs",
License = new OpenApiLicense
{
Name = "GPL-3.0",
Url = new Uri("https://github.com/Prowlarr/Prowlarr/blob/develop/LICENSE")
}
});
var apiKeyHeader = new OpenApiSecurityScheme
{
Name = "X-Api-Key",
Type = SecuritySchemeType.ApiKey,
Scheme = "apiKey",
Description = "Apikey passed as header",
In = ParameterLocation.Header,
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "X-Api-Key"
},
};
c.AddSecurityDefinition("X-Api-Key", apiKeyHeader);
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{ apiKeyHeader, new string[] { } }
});
var apikeyQuery = new OpenApiSecurityScheme
{
Name = "apikey",
Type = SecuritySchemeType.ApiKey,
Scheme = "apiKey",
Description = "Apikey passed as header",
In = ParameterLocation.Query,
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "apikey"
},
};
c.AddServer(new OpenApiServer
{
Url = "{protocol}://{hostpath}",
Variables = new Dictionary<string, OpenApiServerVariable>
{
{ "protocol", new OpenApiServerVariable { Default = "http", Enum = new List<string> { "http", "https" } } },
{ "hostpath", new OpenApiServerVariable { Default = "localhost:9696" } }
}
});
c.AddSecurityDefinition("apikey", apikeyQuery);
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{ apikeyQuery, new string[] { } }
});
});
services
.AddSignalR()
.AddJsonProtocol(options =>
@@ -94,6 +165,9 @@ namespace NzbDrone.Host
options.PayloadSerializerOptions = STJson.GetSerializerSettings();
});
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(Configuration["dataProtectionFolder"]));
services.AddSingleton<IAuthorizationPolicyProvider, UiAuthorizationPolicyProvider>();
services.AddAuthorization(options =>
{
@@ -175,6 +249,15 @@ namespace NzbDrone.Host
app.UseWebSockets();
// Enable middleware to serve generated Swagger as a JSON endpoint.
if (BuildInfo.IsDebug)
{
app.UseSwagger(c =>
{
c.RouteTemplate = "docs/{documentName}/openapi.json";
});
}
app.UseEndpoints(x =>
{
x.MapHub<MessageHub>("/signalr/messages").RequireAuthorization("SignalR");

View File

@@ -3,7 +3,7 @@
<TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr17" />
<PackageReference Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr18" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Common.Test\Prowlarr.Common.Test.csproj" />

View File

@@ -3,7 +3,7 @@
<TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr17" />
<PackageReference Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr18" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -6,8 +6,10 @@ namespace Prowlarr.Api.V1.Notifications
{
public string Link { get; set; }
public bool OnHealthIssue { get; set; }
public bool OnApplicationUpdate { get; set; }
public bool SupportsOnHealthIssue { get; set; }
public bool IncludeHealthWarnings { get; set; }
public bool SupportsOnApplicationUpdate { get; set; }
public string TestCommand { get; set; }
}
@@ -25,6 +27,8 @@ namespace Prowlarr.Api.V1.Notifications
resource.OnHealthIssue = definition.OnHealthIssue;
resource.SupportsOnHealthIssue = definition.SupportsOnHealthIssue;
resource.IncludeHealthWarnings = definition.IncludeHealthWarnings;
resource.OnApplicationUpdate = definition.OnApplicationUpdate;
resource.SupportsOnApplicationUpdate = definition.SupportsOnApplicationUpdate;
return resource;
}
@@ -41,6 +45,8 @@ namespace Prowlarr.Api.V1.Notifications
definition.OnHealthIssue = resource.OnHealthIssue;
definition.SupportsOnHealthIssue = resource.SupportsOnHealthIssue;
definition.IncludeHealthWarnings = resource.IncludeHealthWarnings;
definition.OnApplicationUpdate = resource.OnApplicationUpdate;
definition.SupportsOnApplicationUpdate = resource.SupportsOnApplicationUpdate;
return definition;
}

View File

@@ -20,6 +20,7 @@ namespace Prowlarr.Api.V1.Profiles.App
}
[RestPostById]
[Produces("application/json")]
public ActionResult<AppProfileResource> Create(AppProfileResource resource)
{
var model = resource.ToModel();
@@ -28,12 +29,14 @@ namespace Prowlarr.Api.V1.Profiles.App
}
[RestDeleteById]
[Produces("application/json")]
public void DeleteProfile(int id)
{
_profileService.Delete(id);
}
[RestPutById]
[Produces("application/json")]
public ActionResult<AppProfileResource> Update(AppProfileResource resource)
{
var model = resource.ToModel();
@@ -43,12 +46,17 @@ namespace Prowlarr.Api.V1.Profiles.App
return Accepted(model.Id);
}
[Produces("application/json")]
[ProducesResponseType(typeof(AppProfileResource), 200)]
[ProducesResponseType(typeof(IDictionary<string, string>), 404)]
[ProducesResponseType(500)]
public override AppProfileResource GetResourceById(int id)
{
return _profileService.Get(id).ToResource();
}
[HttpGet]
[Produces("application/json")]
public List<AppProfileResource> GetAll()
{
return _profileService.All().ToResource();

View File

@@ -2,6 +2,11 @@
"openapi": "3.0.1",
"info": {
"title": "Prowlarr",
"description": "Prowlarr API docs",
"license": {
"name": "GPL-3.0",
"url": "https://github.com/Prowlarr/Prowlarr/blob/develop/LICENSE"
},
"version": "1.0.0"
},
"paths": {
@@ -356,20 +361,10 @@
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/AppProfileResource"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/AppProfileResource"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/AppProfileResource"
}
}
}
}
@@ -383,14 +378,6 @@
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/AppProfileResource"
}
}
},
"application/json": {
"schema": {
"type": "array",
@@ -398,14 +385,6 @@
"$ref": "#/components/schemas/AppProfileResource"
}
}
},
"text/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/AppProfileResource"
}
}
}
}
}
@@ -471,20 +450,10 @@
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/AppProfileResource"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/AppProfileResource"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/AppProfileResource"
}
}
}
}
@@ -509,22 +478,28 @@
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/AppProfileResource"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/AppProfileResource"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/AppProfileResource"
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
},
"500": {
"description": "Server Error"
}
}
}
@@ -2341,131 +2316,6 @@
}
}
},
"/api/v1/config/indexer/{id}": {
"get": {
"tags": [
"IndexerConfig"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer",
"format": "int32"
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/IndexerConfigResource"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/IndexerConfigResource"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/IndexerConfigResource"
}
}
}
}
}
},
"put": {
"tags": [
"IndexerConfig"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/IndexerConfigResource"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/IndexerConfigResource"
}
},
"application/*+json": {
"schema": {
"$ref": "#/components/schemas/IndexerConfigResource"
}
}
}
},
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/IndexerConfigResource"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/IndexerConfigResource"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/IndexerConfigResource"
}
}
}
}
}
}
},
"/api/v1/config/indexer": {
"get": {
"tags": [
"IndexerConfig"
],
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/IndexerConfigResource"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/IndexerConfigResource"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/IndexerConfigResource"
}
}
}
}
}
}
},
"/api/v1/indexer/categories": {
"get": {
"tags": [
@@ -2892,6 +2742,24 @@
"tags": [
"IndexerStats"
],
"parameters": [
{
"name": "startDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
}
},
{
"name": "endDate",
"in": "query",
"schema": {
"type": "string",
"format": "date-time"
}
}
],
"responses": {
"200": {
"description": "Success",
@@ -4176,6 +4044,14 @@
"format": "int32"
}
}
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string",
"default": "search"
}
}
],
"responses": {
@@ -4211,6 +4087,63 @@
}
}
},
"/api/v1/search/bulk": {
"post": {
"tags": [
"Search"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SearchResource"
}
}
},
"text/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SearchResource"
}
}
},
"application/*+json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SearchResource"
}
}
}
}
},
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/SearchResource"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/SearchResource"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/SearchResource"
}
}
}
}
}
}
},
"/content/{path}": {
"get": {
"tags": [
@@ -5292,22 +5225,6 @@
"downloadClientWorkingFolders": {
"type": "string",
"nullable": true
},
"enableCompletedDownloadHandling": {
"type": "boolean"
},
"removeCompletedDownloads": {
"type": "boolean"
},
"checkForFinishedDownloadInterval": {
"type": "integer",
"format": "int32"
},
"autoRedownloadFailed": {
"type": "boolean"
},
"removeFailedDownloads": {
"type": "boolean"
}
},
"additionalProperties": false
@@ -5441,6 +5358,10 @@
"hidden": {
"type": "string",
"nullable": true
},
"placeholder": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
@@ -5719,34 +5640,28 @@
},
"scheme": {
"type": "string",
"nullable": true,
"readOnly": true
"nullable": true
},
"host": {
"type": "string",
"nullable": true,
"readOnly": true
"nullable": true
},
"port": {
"type": "integer",
"format": "int32",
"nullable": true,
"readOnly": true
"nullable": true
},
"path": {
"type": "string",
"nullable": true,
"readOnly": true
"nullable": true
},
"query": {
"type": "string",
"nullable": true,
"readOnly": true
"nullable": true
},
"fragment": {
"type": "string",
"nullable": true,
"readOnly": true
"nullable": true
}
},
"additionalProperties": false
@@ -5804,46 +5719,6 @@
},
"additionalProperties": false
},
"IndexerConfigResource": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int32"
},
"minimumAge": {
"type": "integer",
"format": "int32"
},
"maximumSize": {
"type": "integer",
"format": "int32"
},
"retention": {
"type": "integer",
"format": "int32"
},
"rssSyncInterval": {
"type": "integer",
"format": "int32"
},
"preferIndexerFlags": {
"type": "boolean"
},
"availabilityDelay": {
"type": "integer",
"format": "int32"
},
"allowHardcodedSubs": {
"type": "boolean"
},
"whitelistedHardcodedSubs": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
},
"IndexerEditorResource": {
"type": "object",
"properties": {
@@ -6359,12 +6234,18 @@
"onHealthIssue": {
"type": "boolean"
},
"onApplicationUpdate": {
"type": "boolean"
},
"supportsOnHealthIssue": {
"type": "boolean"
},
"includeHealthWarnings": {
"type": "boolean"
},
"supportsOnApplicationUpdate": {
"type": "boolean"
},
"testCommand": {
"type": "string",
"nullable": true
@@ -6555,6 +6436,11 @@
"hint": {
"type": "string",
"nullable": true
},
"parentValue": {
"type": "integer",
"format": "int32",
"nullable": true
}
},
"additionalProperties": false
@@ -6661,33 +6547,27 @@
"properties": {
"ticks": {
"type": "integer",
"format": "int64",
"readOnly": true
"format": "int64"
},
"days": {
"type": "integer",
"format": "int32",
"readOnly": true
"format": "int32"
},
"hours": {
"type": "integer",
"format": "int32",
"readOnly": true
"format": "int32"
},
"milliseconds": {
"type": "integer",
"format": "int32",
"readOnly": true
"format": "int32"
},
"minutes": {
"type": "integer",
"format": "int32",
"readOnly": true
"format": "int32"
},
"seconds": {
"type": "integer",
"format": "int32",
"readOnly": true
"format": "int32"
},
"totalDays": {
"type": "number",
@@ -6864,23 +6744,19 @@
"properties": {
"major": {
"type": "integer",
"format": "int32",
"readOnly": true
"format": "int32"
},
"minor": {
"type": "integer",
"format": "int32",
"readOnly": true
"format": "int32"
},
"build": {
"type": "integer",
"format": "int32",
"readOnly": true
"format": "int32"
},
"revision": {
"type": "integer",
"format": "int32",
"readOnly": true
"format": "int32"
},
"majorRevision": {
"type": "integer",
@@ -6895,6 +6771,28 @@
},
"additionalProperties": false
}
},
"securitySchemes": {
"X-Api-Key": {
"type": "apiKey",
"description": "Apikey passed as header",
"name": "X-Api-Key",
"in": "header"
},
"apikey": {
"type": "apiKey",
"description": "Apikey passed as header",
"name": "apikey",
"in": "query"
}
}
}
},
"security": [
{
"X-Api-Key": [ ]
},
{
"apikey": [ ]
}
]
}

View File

@@ -19,6 +19,7 @@ namespace Prowlarr.Http.ClientSchema
public string SelectOptionsProviderAction { get; set; }
public string Section { get; set; }
public string Hidden { get; set; }
public string Placeholder { get; set; }
public Field Clone()
{

View File

@@ -47,7 +47,10 @@ namespace Prowlarr.Http.ClientSchema
var propertyType = mapping.PropertyType;
var field = fields.Find(f => f.Name == mapping.Field.Name);
mapping.SetterFunc(target, field.Value);
if (field != null)
{
mapping.SetterFunc(target, field.Value);
}
}
return target;
@@ -101,7 +104,8 @@ namespace Prowlarr.Http.ClientSchema
Order = fieldAttribute.Order,
Advanced = fieldAttribute.Advanced,
Type = fieldAttribute.Type.ToString().FirstCharToLower(),
Section = fieldAttribute.Section
Section = fieldAttribute.Section,
Placeholder = fieldAttribute.Placeholder
};
if (fieldAttribute.Type == FieldType.Select || fieldAttribute.Type == FieldType.TagSelect)