Compare commits

...

18 Commits

Author SHA1 Message Date
Bogdan
9f4f6a5726 Add missing translation for query type 2023-06-29 17:42:59 +03:00
Bogdan
d9ace9a862 Fixed: (Stats) Exclude cached queries from average elapsed time 2023-06-29 16:55:46 +03:00
Bogdan
95691c7476 New: Show query type in history 2023-06-29 16:25:49 +03:00
Bogdan
90f2020e59 Fixed: Misaligned table border in history 2023-06-29 16:08:23 +03:00
Bogdan
6afa1dc8ba Fixed: (Cardigann) Don't check for captcha when captcha answer is empty 2023-06-29 14:43:11 +03:00
Bogdan
e8139f2a5b Fixed: (PornoLab) Moved to YML for Cardigann 2023-06-28 17:56:01 +03:00
Bogdan
45328db2c7 Add close reason to label actions 2023-06-28 15:25:36 +03:00
Bogdan
e55d6b827a Add ContentSummary to HDBits requests 2023-06-27 13:19:57 +03:00
Bogdan
34cd68fa07 Add ContentSummary to BeyondHD requests 2023-06-27 13:19:57 +03:00
Bogdan
aed3f9f887 Create overload for ToJson for Formatting 2023-06-27 13:19:57 +03:00
Bogdan
6880e67507 Fixed: (Apps) Ensure validation for test connection 2023-06-27 06:52:59 +03:00
Bogdan
e0e1b1494e Exclude RSS history events in migration 2023-06-27 05:20:44 +03:00
Bogdan
20df31919d Check for event type to prevent multiple runs on the same row 2023-06-26 20:49:15 +03:00
Bogdan
8785fe02e8 Execute update queries only for certain rows in migration 34 2023-06-26 18:29:14 +03:00
Bogdan
b2b877a8c3 Fix: (UI) Maintain search type and parameters on repeat search 2023-06-26 15:08:31 +03:00
Bogdan
0de302ad48 Don't save empty data in history service 2023-06-26 15:08:31 +03:00
Bogdan
06391489cf Fixed: (Apps) Use forceSave=true to avoid validation warnings 2023-06-26 10:58:14 +03:00
Qstick
8fcceb0702 Bump version to 1.7.0 2023-06-25 20:35:32 -05:00
40 changed files with 740 additions and 511 deletions

View File

@@ -7,6 +7,7 @@
to be a support request. Please hop over onto our [Discord](https://prowlarr.com/discord)
or [Subreddit](https://reddit.com/r/prowlarr)
close: true
close-reason: 'not planned'
'Type: Indexer Request':
comment: >
@@ -14,6 +15,7 @@
for bug reports and feature requests. However, this issue appears
to be a indexer request. Please use our Indexer request [site](https://requests.prowlarr.com/)
close: true
close-reason: 'not planned'
'Status: Logs Needed':
comment: >

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '1.6.3'
majorVersion: '1.7.0'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'

View File

@@ -76,7 +76,7 @@ function HistoryDetails(props) {
if (eventType === 'releaseGrabbed') {
const {
source,
title,
grabTitle,
url
} = data;
@@ -101,8 +101,8 @@ function HistoryDetails(props) {
{
!!data &&
<DescriptionListItem
title={translate('Title')}
data={title ? title : '-'}
title={translate('GrabTitle')}
data={grabTitle ? grabTitle : '-'}
/>
}

View File

@@ -26,9 +26,7 @@
width: 70px;
}
.parameters {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
.parametersContent {
display: flex;
flex-wrap: wrap;
}

View File

@@ -6,7 +6,7 @@ interface CssExports {
'details': string;
'elapsedTime': string;
'indexer': string;
'parameters': string;
'parametersContent': string;
'query': string;
'releaseGroup': string;
'source': string;

View File

@@ -1,17 +1,39 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import { icons } from 'Helpers/Props';
import { icons, kinds } from 'Helpers/Props';
import CapabilitiesLabel from 'Indexer/Index/Table/CapabilitiesLabel';
import translate from 'Utilities/String/translate';
import HistoryDetailsModal from './Details/HistoryDetailsModal';
import * as historyDataTypes from './historyDataTypes';
import HistoryEventTypeCell from './HistoryEventTypeCell';
import HistoryRowParameter from './HistoryRowParameter';
import styles from './HistoryRow.css';
const historyParameters = [
{ key: historyDataTypes.IMDB_ID, title: 'IMDb' },
{ key: historyDataTypes.TMDB_ID, title: 'TMDb' },
{ key: historyDataTypes.TVDB_ID, title: 'TVDb' },
{ key: historyDataTypes.TRAKT_ID, title: 'Trakt' },
{ key: historyDataTypes.R_ID, title: 'TvRage' },
{ key: historyDataTypes.TVMAZE_ID, title: 'TvMaze' },
{ key: historyDataTypes.SEASON, title: translate('Season') },
{ key: historyDataTypes.EPISODE, title: translate('Episode') },
{ key: historyDataTypes.ARTIST, title: translate('Artist') },
{ key: historyDataTypes.ALBUM, title: translate('Album') },
{ key: historyDataTypes.LABEL, title: translate('Label') },
{ key: historyDataTypes.TRACK, title: translate('Track') },
{ key: historyDataTypes.YEAR, title: translate('Year') },
{ key: historyDataTypes.GENRE, title: translate('Genre') },
{ key: historyDataTypes.AUTHOR, title: translate('Author') },
{ key: historyDataTypes.TITLE, title: translate('Title') },
{ key: historyDataTypes.PUBLISHER, title: translate('Publisher') }
];
class HistoryRow extends Component {
//
@@ -44,15 +66,52 @@ class HistoryRow extends Component {
data
} = this.props;
const { query, queryType } = data;
let searchQuery = query;
let categories = [];
if (data.categories) {
categories = data.categories.split(',').map((item) => {
return parseInt(item);
});
categories = data.categories.split(',').map((item) => parseInt(item));
}
this.props.onSearchPress(data.query, indexer.id, categories);
const searchParams = [
historyDataTypes.IMDB_ID,
historyDataTypes.TMDB_ID,
historyDataTypes.TVDB_ID,
historyDataTypes.TRAKT_ID,
historyDataTypes.R_ID,
historyDataTypes.TVMAZE_ID,
historyDataTypes.SEASON,
historyDataTypes.EPISODE,
historyDataTypes.ARTIST,
historyDataTypes.ALBUM,
historyDataTypes.LABEL,
historyDataTypes.TRACK,
historyDataTypes.YEAR,
historyDataTypes.GENRE,
historyDataTypes.AUTHOR,
historyDataTypes.TITLE,
historyDataTypes.PUBLISHER
]
.reduce((acc, key) => {
if (key in data && data[key].length > 0) {
const value = data[key];
acc.push({ key, value });
}
return acc;
}, [])
.map((item) => `{${item.key}:${item.value}}`)
.join('')
;
if (searchParams.length > 0) {
searchQuery += `${searchParams}`;
}
this.props.onSearchPress(searchQuery, indexer.id, categories, queryType);
};
onDetailsPress = () => {
@@ -84,6 +143,8 @@ class HistoryRow extends Component {
return null;
}
const parameters = historyParameters.filter((parameter) => parameter.key in data && data[parameter.key]);
return (
<TableRow>
{
@@ -133,162 +194,19 @@ class HistoryRow extends Component {
if (name === 'parameters') {
return (
<TableRowCell
key={name}
className={styles.parameters}
>
{
data.imdbId ?
<HistoryRowParameter
title='IMDb'
value={data.imdbId}
/> :
null
}
{
data.tmdbId ?
<HistoryRowParameter
title='TMDb'
value={data.tmdbId}
/> :
null
}
{
data.tvdbId ?
<HistoryRowParameter
title='TVDb'
value={data.tvdbId}
/> :
null
}
{
data.traktId ?
<HistoryRowParameter
title='Trakt'
value={data.traktId}
/> :
null
}
{
data.rId ?
<HistoryRowParameter
title='TvRage'
value={data.rId}
/> :
null
}
{
data.tvMazeId ?
<HistoryRowParameter
title='TvMaze'
value={data.tvMazeId}
/> :
null
}
{
data.season ?
<HistoryRowParameter
title={translate('Season')}
value={data.season}
/> :
null
}
{
data.episode ?
<HistoryRowParameter
title={translate('Episode')}
value={data.episode}
/> :
null
}
{
data.artist ?
<HistoryRowParameter
title={translate('Artist')}
value={data.artist}
/> :
null
}
{
data.album ?
<HistoryRowParameter
title={translate('Album')}
value={data.album}
/> :
null
}
{
data.label ?
<HistoryRowParameter
title={translate('Label')}
value={data.label}
/> :
null
}
{
data.track ?
<HistoryRowParameter
title={translate('Track')}
value={data.track}
/> :
null
}
{
data.year ?
<HistoryRowParameter
title={translate('Year')}
value={data.year}
/> :
null
}
{
data.genre ?
<HistoryRowParameter
title={translate('Genre')}
value={data.genre}
/> :
null
}
{
data.author ?
<HistoryRowParameter
title={translate('Author')}
value={data.author}
/> :
null
}
{
data.bookTitle ?
<HistoryRowParameter
title={translate('Book')}
value={data.bookTitle}
/> :
null
}
{
data.publisher ?
<HistoryRowParameter
title={translate('Publisher')}
value={data.publisher}
/> :
null
}
<TableRowCell key={name}>
<div className={styles.parametersContent}>
{parameters.map((parameter) => {
return (
<HistoryRowParameter
key={parameter.key}
title={parameter.title}
value={data[parameter.key]}
/>
);
}
)}
</div>
</TableRowCell>
);
}
@@ -300,8 +218,25 @@ class HistoryRow extends Component {
className={styles.indexer}
>
{
data.title ?
data.title :
data.grabTitle ?
data.grabTitle :
null
}
</TableRowCell>
);
}
if (name === 'queryType') {
return (
<TableRowCell
key={name}
className={styles.query}
>
{
data.queryType ?
<Label kind={kinds.INFO}>
{data.queryType}
</Label> :
null
}
</TableRowCell>

View File

@@ -48,8 +48,8 @@ class HistoryRowConnector extends Component {
//
// Listeners
onSearchPress = (term, indexerId, categories) => {
this.props.setSearchDefault({ searchQuery: term, searchIndexerIds: [indexerId], searchCategories: categories });
onSearchPress = (term, indexerId, categories, type) => {
this.props.setSearchDefault({ searchQuery: term, searchIndexerIds: [indexerId], searchCategories: categories, searchType: type });
this.props.push(`${window.Prowlarr.urlBase}/search`);
};

View File

@@ -0,0 +1,17 @@
export const IMDB_ID = 'imdbId';
export const TMDB_ID = 'tmdbId';
export const TVDB_ID = 'tvdbId';
export const TRAKT_ID = 'traktId';
export const R_ID = 'rId';
export const TVMAZE_ID = 'tvMazeId';
export const SEASON = 'season';
export const EPISODE = 'episode';
export const ARTIST = 'artist';
export const ALBUM = 'album';
export const LABEL = 'label';
export const TRACK = 'track';
export const YEAR = 'year';
export const GENRE = 'genre';
export const AUTHOR = 'author';
export const TITLE = 'title';
export const PUBLISHER = 'publisher';

View File

@@ -58,6 +58,12 @@ export const defaultState = {
isSortable: false,
isVisible: false
},
{
name: 'queryType',
label: translate('QueryType'),
isSortable: false,
isVisible: false
},
{
name: 'categories',
label: translate('Categories'),

View File

@@ -121,6 +121,11 @@ namespace NzbDrone.Common.Serializer
return JsonConvert.SerializeObject(obj, SerializerSettings);
}
public static string ToJson(this object obj, Formatting formatting)
{
return JsonConvert.SerializeObject(obj, formatting, SerializerSettings);
}
public static void Serialize<TModel>(TModel model, TextWriter outputStream)
{
var jsonTextWriter = new JsonTextWriter(outputStream);

View File

@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class history_fix_data_titlesFixture : MigrationTest<history_fix_data_titles>
{
[Test]
public void should_update_data_for_book_search()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("History").Row(new
{
IndexerId = 1,
Date = DateTime.UtcNow,
Data = new
{
Author = "Fake Author",
BookTitle = "Fake Book Title",
Publisher = "",
Year = "",
Genre = "",
Query = "",
QueryType = "book",
Source = "Prowlarr",
Host = "localhost"
}.ToJson(),
EventType = 2,
Successful = true
});
});
var items = db.Query<HistoryDefinition34>("SELECT * FROM \"History\"");
items.Should().HaveCount(1);
items.First().Data.Should().NotContainKey("bookTitle");
items.First().Data.Should().ContainKey("title");
items.First().Data.GetValueOrDefault("title").Should().Be("Fake Book Title");
}
[Test]
public void should_update_data_for_release_grabbed()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("History").Row(new
{
IndexerId = 1,
Date = DateTime.UtcNow,
Data = new
{
GrabMethod = "Proxy",
Title = "Fake Release Title",
Source = "Prowlarr",
Host = "localhost"
}.ToJson(),
EventType = 1,
Successful = true
});
});
var items = db.Query<HistoryDefinition34>("SELECT * FROM \"History\"");
items.Should().HaveCount(1);
items.First().Data.Should().NotContainKey("title");
items.First().Data.Should().ContainKey("grabTitle");
items.First().Data.GetValueOrDefault("grabTitle").Should().Be("Fake Release Title");
}
}
public class HistoryDefinition34
{
public int Id { get; set; }
public int IndexerId { get; set; }
public DateTime Date { get; set; }
public Dictionary<string, string> Data { get; set; }
public int EventType { get; set; }
public string DownloadId { get; set; }
public bool Successful { get; set; }
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
@@ -32,10 +31,10 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
{
failures.AddIfNotNull(_lazyLibrarianV1Proxy.TestConnection(Settings));
}
catch (WebException ex)
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to LazyLibrarian"));
_logger.Error(ex, "Unable to complete application test");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to LazyLibrarian. {ex.Message}"));
}
return new ValidationResult(failures);

View File

@@ -44,7 +44,7 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
[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")]
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by LazyLibrarian in Settings/Web Interface")]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]

View File

@@ -3,9 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using FluentValidation.Results;
using Newtonsoft.Json;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Applications.LazyLibrarian
{
@@ -139,11 +139,11 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
return new ValidationFailure("ApiKey", status.Error.Message);
}
var indexers = GetIndexers(settings);
GetIndexers(settings);
}
catch (HttpException ex)
{
_logger.Error(ex, "Unable to send test message");
_logger.Error(ex, "Unable to complete application test");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}
catch (LazyLibrarianException ex)
@@ -153,8 +153,8 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("", "Unable to send test message");
_logger.Error(ex, "Unable to complete application test");
return new ValidationFailure("", $"Unable to send test message. {ex.Message}");
}
return null;
@@ -164,7 +164,9 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
{
var baseUrl = settings.BaseUrl.TrimEnd('/');
var requestBuilder = new HttpRequestBuilder(baseUrl).Resource(resource)
var requestBuilder = new HttpRequestBuilder(baseUrl)
.Resource(resource)
.Accept(HttpAccept.Json)
.AddQueryParam("cmd", command)
.AddQueryParam("apikey", settings.ApiKey);
@@ -191,9 +193,12 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
{
var response = _httpClient.Execute(request);
var results = JsonConvert.DeserializeObject<TResource>(response.Content);
if ((int)response.StatusCode >= 300)
{
throw new HttpException(response);
}
return results;
return Json.Deserialize<TResource>(response.Content);
}
private int CalculatePriority(int indexerPriority) => ProwlarrHighestPriority - indexerPriority + 1;

View File

@@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
@@ -48,9 +51,36 @@ namespace NzbDrone.Core.Applications.Lidarr
{
failures.AddIfNotNull(_lidarrV1Proxy.TestConnection(BuildLidarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (HttpException ex)
{
switch (ex.Response.StatusCode)
{
case HttpStatusCode.Unauthorized:
_logger.Error(ex, "API Key is invalid");
failures.AddIfNotNull(new ValidationFailure("ApiKey", "API Key is invalid"));
break;
case HttpStatusCode.BadRequest:
_logger.Error(ex, "Prowlarr URL is invalid");
failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Lidarr cannot connect to Prowlarr"));
break;
case HttpStatusCode.SeeOther:
_logger.Error(ex, "Lidarr returned redirect and is invalid");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Lidarr URL is invalid, Prowlarr cannot connect to Lidarr - are you missing a URL base?"));
break;
default:
_logger.Error(ex, "Unable to complete application test");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Lidarr. {ex.Message}"));
break;
}
}
catch (JsonReaderException ex)
{
_logger.Error(ex, "Unable to parse JSON response from application");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to parse JSON response from application. {ex.Message}"));
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
_logger.Error(ex, "Unable to complete application test");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Lidarr. {ex.Message}"));
}

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Lidarr
[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")]
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Lidarr in Settings/General")]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]

View File

@@ -85,13 +85,25 @@ namespace NzbDrone.Core.Applications.Lidarr
request.SetContent(indexer.ToJson());
return ExecuteIndexerRequest(request);
try
{
return ExecuteIndexerRequest(request);
}
catch (HttpException ex) when (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Debug("Retrying to add indexer forcefully");
request.Url = request.Url.AddQueryParam("forceSave", "true");
return ExecuteIndexerRequest(request);
}
}
public LidarrIndexer UpdateIndexer(LidarrIndexer indexer, LidarrSettings settings)
{
var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexer.Id}", HttpMethod.Put);
request.Url = request.Url.AddQueryParam("forceSave", "true");
request.SetContent(indexer.ToJson());
return ExecuteIndexerRequest(request);
@@ -103,47 +115,16 @@ namespace NzbDrone.Core.Applications.Lidarr
request.SetContent(indexer.ToJson());
try
var applicationVersion = _httpClient.Post(request).Headers.GetSingleValue("X-Application-Version");
if (applicationVersion == null)
{
var applicationVersion = _httpClient.Post<LidarrIndexer>(request).Headers.GetSingleValue("X-Application-Version");
if (applicationVersion == null)
{
return new ValidationFailure(string.Empty, "Failed to fetch Lidarr version");
}
if (new Version(applicationVersion) < MinimumApplicationVersion)
{
return new ValidationFailure(string.Empty, $"Lidarr version should be at least {MinimumApplicationVersion.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
}
return new ValidationFailure(string.Empty, "Failed to fetch Lidarr version");
}
catch (HttpException ex)
if (new Version(applicationVersion) < MinimumApplicationVersion)
{
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
{
_logger.Error(ex, "API Key is invalid");
return new ValidationFailure("ApiKey", "API Key is invalid");
}
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error(ex, "Prowlarr URL is invalid");
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Lidarr cannot connect to Prowlarr");
}
if (ex.Response.StatusCode == HttpStatusCode.SeeOther)
{
_logger.Error(ex, "Lidarr returned redirect and is invalid");
return new ValidationFailure("BaseUrl", "Lidarr url is invalid, Prowlarr cannot connect to Lidarr - are you missing a url base?");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("", "Unable to send test message");
return new ValidationFailure(string.Empty, $"Lidarr version should be at least {MinimumApplicationVersion.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
}
return null;
@@ -179,8 +160,10 @@ namespace NzbDrone.Core.Applications.Lidarr
break;
default:
_logger.Error(ex, "Unexpected response status code: {0}", ex.Response.StatusCode);
throw;
break;
}
throw;
}
catch (JsonReaderException ex)
{
@@ -192,15 +175,15 @@ namespace NzbDrone.Core.Applications.Lidarr
_logger.Error(ex, "Unable to add or update indexer");
throw;
}
return null;
}
private HttpRequest BuildRequest(LidarrSettings settings, string resource, HttpMethod method)
{
var baseUrl = settings.BaseUrl.TrimEnd('/');
var request = new HttpRequestBuilder(baseUrl).Resource(resource)
var request = new HttpRequestBuilder(baseUrl)
.Resource(resource)
.Accept(HttpAccept.Json)
.SetHeader("X-Api-Key", settings.ApiKey)
.Build();
@@ -217,9 +200,12 @@ namespace NzbDrone.Core.Applications.Lidarr
{
var response = _httpClient.Execute(request);
var results = JsonConvert.DeserializeObject<TResource>(response.Content);
if ((int)response.StatusCode >= 300)
{
throw new HttpException(response);
}
return results;
return Json.Deserialize<TResource>(response.Content);
}
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
@@ -32,10 +31,10 @@ namespace NzbDrone.Core.Applications.Mylar
{
failures.AddIfNotNull(_mylarV3Proxy.TestConnection(Settings));
}
catch (WebException ex)
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Mylar"));
_logger.Error(ex, "Unable to complete application test");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Mylar. {ex.Message}"));
}
return new ValidationResult(failures);

View File

@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Applications.Mylar
[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")]
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Mylar in Settings/Web Interface")]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]

View File

@@ -3,9 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using FluentValidation.Results;
using Newtonsoft.Json;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Applications.Mylar
{
@@ -135,11 +135,11 @@ namespace NzbDrone.Core.Applications.Mylar
return new ValidationFailure("ApiKey", status.Error.Message);
}
var indexers = GetIndexers(settings);
GetIndexers(settings);
}
catch (HttpException ex)
{
_logger.Error(ex, "Unable to send test message");
_logger.Error(ex, "Unable to complete application test");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}
catch (MylarException ex)
@@ -149,8 +149,8 @@ namespace NzbDrone.Core.Applications.Mylar
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("", "Unable to send test message");
_logger.Error(ex, "Unable to complete application test");
return new ValidationFailure("", $"Unable to send test message. {ex.Message}");
}
return null;
@@ -160,7 +160,9 @@ namespace NzbDrone.Core.Applications.Mylar
{
var baseUrl = settings.BaseUrl.TrimEnd('/');
var requestBuilder = new HttpRequestBuilder(baseUrl).Resource(resource)
var requestBuilder = new HttpRequestBuilder(baseUrl)
.Resource(resource)
.Accept(HttpAccept.Json)
.AddQueryParam("cmd", command)
.AddQueryParam("apikey", settings.ApiKey);
@@ -187,9 +189,12 @@ namespace NzbDrone.Core.Applications.Mylar
{
var response = _httpClient.Execute(request);
var results = JsonConvert.DeserializeObject<TResource>(response.Content);
if ((int)response.StatusCode >= 300)
{
throw new HttpException(response);
}
return results;
return Json.Deserialize<TResource>(response.Content);
}
}
}

View File

@@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
@@ -48,9 +51,36 @@ namespace NzbDrone.Core.Applications.Radarr
{
failures.AddIfNotNull(_radarrV3Proxy.TestConnection(BuildRadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (HttpException ex)
{
switch (ex.Response.StatusCode)
{
case HttpStatusCode.Unauthorized:
_logger.Error(ex, "API Key is invalid");
failures.AddIfNotNull(new ValidationFailure("ApiKey", "API Key is invalid"));
break;
case HttpStatusCode.BadRequest:
_logger.Error(ex, "Prowlarr URL is invalid");
failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Radarr cannot connect to Prowlarr"));
break;
case HttpStatusCode.SeeOther:
_logger.Error(ex, "Radarr returned redirect and is invalid");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Radarr URL is invalid, Prowlarr cannot connect to Radarr - are you missing a URL base?"));
break;
default:
_logger.Error(ex, "Unable to complete application test");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Radarr. {ex.Message}"));
break;
}
}
catch (JsonReaderException ex)
{
_logger.Error(ex, "Unable to parse JSON response from application");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to parse JSON response from application. {ex.Message}"));
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
_logger.Error(ex, "Unable to complete application test");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Radarr. {ex.Message}"));
}

View File

@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Applications.Radarr
[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")]
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Radarr in Settings/General")]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]

View File

@@ -86,13 +86,25 @@ namespace NzbDrone.Core.Applications.Radarr
request.SetContent(indexer.ToJson());
return ExecuteIndexerRequest(request);
try
{
return ExecuteIndexerRequest(request);
}
catch (HttpException ex) when (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Debug("Retrying to add indexer forcefully");
request.Url = request.Url.AddQueryParam("forceSave", "true");
return ExecuteIndexerRequest(request);
}
}
public RadarrIndexer UpdateIndexer(RadarrIndexer indexer, RadarrSettings settings)
{
var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexer.Id}", HttpMethod.Put);
request.Url = request.Url.AddQueryParam("forceSave", "true");
request.SetContent(indexer.ToJson());
return ExecuteIndexerRequest(request);
@@ -104,59 +116,28 @@ namespace NzbDrone.Core.Applications.Radarr
request.SetContent(indexer.ToJson());
try
var applicationVersion = _httpClient.Post(request).Headers.GetSingleValue("X-Application-Version");
if (applicationVersion == null)
{
var applicationVersion = _httpClient.Post<RadarrIndexer>(request).Headers.GetSingleValue("X-Application-Version");
return new ValidationFailure(string.Empty, "Failed to fetch Radarr version");
}
if (applicationVersion == null)
{
return new ValidationFailure(string.Empty, "Failed to fetch Radarr version");
}
var version = new Version(applicationVersion);
var version = new Version(applicationVersion);
if (version.Major == 3)
if (version.Major == 3)
{
if (version < MinimumApplicationV3Version)
{
if (version < MinimumApplicationV3Version)
{
return new ValidationFailure(string.Empty, $"Radarr version should be at least {MinimumApplicationV3Version.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
}
}
else
{
if (version < MinimumApplicationV4Version)
{
return new ValidationFailure(string.Empty, $"Radarr version should be at least {MinimumApplicationV4Version.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
}
return new ValidationFailure(string.Empty, $"Radarr version should be at least {MinimumApplicationV3Version.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
}
}
catch (HttpException ex)
else
{
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
if (version < MinimumApplicationV4Version)
{
_logger.Error(ex, "API Key is invalid");
return new ValidationFailure("ApiKey", "API Key is invalid");
return new ValidationFailure(string.Empty, $"Radarr version should be at least {MinimumApplicationV4Version.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
}
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error(ex, "Prowlarr URL is invalid");
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Radarr cannot connect to Prowlarr");
}
if (ex.Response.StatusCode == HttpStatusCode.SeeOther)
{
_logger.Error(ex, "Radarr returned redirect and is invalid");
return new ValidationFailure("BaseUrl", "Radarr url is invalid, Prowlarr cannot connect to Radarr - are you missing a url base?");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("", "Unable to send test message");
}
return null;
@@ -192,8 +173,10 @@ namespace NzbDrone.Core.Applications.Radarr
break;
default:
_logger.Error(ex, "Unexpected response status code: {0}", ex.Response.StatusCode);
throw;
break;
}
throw;
}
catch (JsonReaderException ex)
{
@@ -205,15 +188,15 @@ namespace NzbDrone.Core.Applications.Radarr
_logger.Error(ex, "Unable to add or update indexer");
throw;
}
return null;
}
private HttpRequest BuildRequest(RadarrSettings settings, string resource, HttpMethod method)
{
var baseUrl = settings.BaseUrl.TrimEnd('/');
var request = new HttpRequestBuilder(baseUrl).Resource(resource)
var request = new HttpRequestBuilder(baseUrl)
.Resource(resource)
.Accept(HttpAccept.Json)
.SetHeader("X-Api-Key", settings.ApiKey)
.Build();
@@ -230,9 +213,12 @@ namespace NzbDrone.Core.Applications.Radarr
{
var response = _httpClient.Execute(request);
var results = JsonConvert.DeserializeObject<TResource>(response.Content);
if ((int)response.StatusCode >= 300)
{
throw new HttpException(response);
}
return results;
return Json.Deserialize<TResource>(response.Content);
}
}
}

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
@@ -49,10 +51,37 @@ namespace NzbDrone.Core.Applications.Readarr
{
failures.AddIfNotNull(_readarrV1Proxy.TestConnection(BuildReadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
catch (HttpException ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Readarr"));
switch (ex.Response.StatusCode)
{
case HttpStatusCode.Unauthorized:
_logger.Error(ex, "API Key is invalid");
failures.AddIfNotNull(new ValidationFailure("ApiKey", "API Key is invalid"));
break;
case HttpStatusCode.BadRequest:
_logger.Error(ex, "Prowlarr URL is invalid");
failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Readarr cannot connect to Prowlarr"));
break;
case HttpStatusCode.SeeOther:
_logger.Error(ex, "Readarr returned redirect and is invalid");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Readarr URL is invalid, Prowlarr cannot connect to Readarr - are you missing a URL base?"));
break;
default:
_logger.Error(ex, "Unable to complete application test");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Readarr. {ex.Message}"));
break;
}
}
catch (JsonReaderException ex)
{
_logger.Error(ex, "Unable to parse JSON response from application");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to parse JSON response from application. {ex.Message}"));
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to complete application test");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Readarr. {ex.Message}"));
}
return new ValidationResult(failures);

View File

@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Applications.Readarr
[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")]
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Readarr in Settings/General")]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]

View File

@@ -82,13 +82,25 @@ namespace NzbDrone.Core.Applications.Readarr
request.SetContent(indexer.ToJson());
return ExecuteIndexerRequest(request);
try
{
return ExecuteIndexerRequest(request);
}
catch (HttpException ex) when (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Debug("Retrying to add indexer forcefully");
request.Url = request.Url.AddQueryParam("forceSave", "true");
return ExecuteIndexerRequest(request);
}
}
public ReadarrIndexer UpdateIndexer(ReadarrIndexer indexer, ReadarrSettings settings)
{
var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexer.Id}", HttpMethod.Put);
request.Url = request.Url.AddQueryParam("forceSave", "true");
request.SetContent(indexer.ToJson());
return ExecuteIndexerRequest(request);
@@ -100,38 +112,7 @@ namespace NzbDrone.Core.Applications.Readarr
request.SetContent(indexer.ToJson());
try
{
Execute<ReadarrIndexer>(request);
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
{
_logger.Error(ex, "API Key is invalid");
return new ValidationFailure("ApiKey", "API Key is invalid");
}
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error(ex, "Prowlarr URL is invalid");
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Readarr cannot connect to Prowlarr");
}
if (ex.Response.StatusCode == HttpStatusCode.SeeOther)
{
_logger.Error(ex, "Readarr returned redirect and is invalid");
return new ValidationFailure("BaseUrl", "Readarr url is invalid, Prowlarr cannot connect to Readarr - are you missing a url base?");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("", "Unable to send test message");
}
_httpClient.Post(request);
return null;
}
@@ -166,8 +147,10 @@ namespace NzbDrone.Core.Applications.Readarr
break;
default:
_logger.Error(ex, "Unexpected response status code: {0}", ex.Response.StatusCode);
throw;
break;
}
throw;
}
catch (JsonReaderException ex)
{
@@ -179,15 +162,15 @@ namespace NzbDrone.Core.Applications.Readarr
_logger.Error(ex, "Unable to add or update indexer");
throw;
}
return null;
}
private HttpRequest BuildRequest(ReadarrSettings settings, string resource, HttpMethod method)
{
var baseUrl = settings.BaseUrl.TrimEnd('/');
var request = new HttpRequestBuilder(baseUrl).Resource(resource)
var request = new HttpRequestBuilder(baseUrl)
.Resource(resource)
.Accept(HttpAccept.Json)
.SetHeader("X-Api-Key", settings.ApiKey)
.Build();
@@ -204,9 +187,12 @@ namespace NzbDrone.Core.Applications.Readarr
{
var response = _httpClient.Execute(request);
var results = JsonConvert.DeserializeObject<TResource>(response.Content);
if ((int)response.StatusCode >= 300)
{
throw new HttpException(response);
}
return results;
return Json.Deserialize<TResource>(response.Content);
}
}
}

View File

@@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
@@ -48,9 +51,40 @@ namespace NzbDrone.Core.Applications.Sonarr
{
failures.AddIfNotNull(_sonarrV3Proxy.TestConnection(BuildSonarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (HttpException ex)
{
switch (ex.Response.StatusCode)
{
case HttpStatusCode.Unauthorized:
_logger.Error(ex, "API Key is invalid");
failures.AddIfNotNull(new ValidationFailure("ApiKey", "API Key is invalid"));
break;
case HttpStatusCode.BadRequest:
_logger.Error(ex, "Prowlarr URL is invalid");
failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Sonarr cannot connect to Prowlarr"));
break;
case HttpStatusCode.SeeOther:
_logger.Error(ex, "Sonarr returned redirect and is invalid");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Sonarr URL is invalid, Prowlarr cannot connect to Sonarr - are you missing a URL base?"));
break;
case HttpStatusCode.NotFound:
_logger.Error(ex, "Sonarr not found");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Sonarr URL is invalid, Prowlarr cannot connect to Sonarr. Is Sonarr running and accessible? Sonarr v2 is not supported."));
break;
default:
_logger.Error(ex, "Unable to complete application test");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Sonarr. {ex.Message}"));
break;
}
}
catch (JsonReaderException ex)
{
_logger.Error(ex, "Unable to parse JSON response from application");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to parse JSON response from application. {ex.Message}"));
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
_logger.Error(ex, "Unable to complete application test");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Sonarr. {ex.Message}"));
}

View File

@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Applications.Sonarr
[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")]
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Sonarr in Settings/General")]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]

View File

@@ -85,13 +85,25 @@ namespace NzbDrone.Core.Applications.Sonarr
request.SetContent(indexer.ToJson());
return ExecuteIndexerRequest(request);
try
{
return ExecuteIndexerRequest(request);
}
catch (HttpException ex) when (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Debug("Retrying to add indexer forcefully");
request.Url = request.Url.AddQueryParam("forceSave", "true");
return ExecuteIndexerRequest(request);
}
}
public SonarrIndexer UpdateIndexer(SonarrIndexer indexer, SonarrSettings settings)
{
var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexer.Id}", HttpMethod.Put);
request.Url = request.Url.AddQueryParam("forceSave", "true");
request.SetContent(indexer.ToJson());
return ExecuteIndexerRequest(request);
@@ -103,53 +115,16 @@ namespace NzbDrone.Core.Applications.Sonarr
request.SetContent(indexer.ToJson());
try
var applicationVersion = _httpClient.Post(request).Headers.GetSingleValue("X-Application-Version");
if (applicationVersion == null)
{
var applicationVersion = _httpClient.Post<SonarrIndexer>(request).Headers.GetSingleValue("X-Application-Version");
if (applicationVersion == null)
{
return new ValidationFailure(string.Empty, "Failed to fetch Sonarr version");
}
if (new Version(applicationVersion) < MinimumApplicationVersion)
{
return new ValidationFailure(string.Empty, $"Sonarr version should be at least {MinimumApplicationVersion.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
}
return new ValidationFailure(string.Empty, "Failed to fetch Sonarr version");
}
catch (HttpException ex)
if (new Version(applicationVersion) < MinimumApplicationVersion)
{
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
{
_logger.Error(ex, "API Key is invalid");
return new ValidationFailure("ApiKey", "API Key is invalid");
}
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error(ex, "Prowlarr URL is invalid");
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Sonarr cannot connect to Prowlarr");
}
if (ex.Response.StatusCode == HttpStatusCode.SeeOther)
{
_logger.Error(ex, "Sonarr returned redirect and is invalid");
return new ValidationFailure("BaseUrl", "Sonarr url is invalid, Prowlarr cannot connect to Sonarr - are you missing a url base?");
}
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
{
_logger.Error(ex, "Sonarr not found");
return new ValidationFailure("BaseUrl", "Sonarr url is invalid, Prowlarr cannot connect to Sonarr. Is Sonarr running and accessible? Sonarr v2 is not supported.");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("", "Unable to send test message");
return new ValidationFailure(string.Empty, $"Sonarr version should be at least {MinimumApplicationVersion.ToString(3)}. Version reported is {applicationVersion}", applicationVersion);
}
return null;
@@ -185,8 +160,10 @@ namespace NzbDrone.Core.Applications.Sonarr
break;
default:
_logger.Error(ex, "Unexpected response status code: {0}", ex.Response.StatusCode);
throw;
break;
}
throw;
}
catch (JsonReaderException ex)
{
@@ -198,15 +175,15 @@ namespace NzbDrone.Core.Applications.Sonarr
_logger.Error(ex, "Unable to add or update indexer");
throw;
}
return null;
}
private HttpRequest BuildRequest(SonarrSettings settings, string resource, HttpMethod method)
{
var baseUrl = settings.BaseUrl.TrimEnd('/');
var request = new HttpRequestBuilder(baseUrl).Resource(resource)
var request = new HttpRequestBuilder(baseUrl)
.Resource(resource)
.Accept(HttpAccept.Json)
.SetHeader("X-Api-Key", settings.ApiKey)
.Build();
@@ -223,9 +200,12 @@ namespace NzbDrone.Core.Applications.Sonarr
{
var response = _httpClient.Execute(request);
var results = JsonConvert.DeserializeObject<TResource>(response.Content);
if ((int)response.StatusCode >= 300)
{
throw new HttpException(response);
}
return results;
return Json.Deserialize<TResource>(response.Content);
}
}
}

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
@@ -49,10 +51,37 @@ namespace NzbDrone.Core.Applications.Whisparr
{
failures.AddIfNotNull(_whisparrV3Proxy.TestConnection(BuildWhisparrIndexer(testIndexer, DownloadProtocol.Usenet), Settings));
}
catch (WebException ex)
catch (HttpException ex)
{
_logger.Error(ex, "Unable to send test message");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Whisparr"));
switch (ex.Response.StatusCode)
{
case HttpStatusCode.Unauthorized:
_logger.Error(ex, "API Key is invalid");
failures.AddIfNotNull(new ValidationFailure("ApiKey", "API Key is invalid"));
break;
case HttpStatusCode.BadRequest:
_logger.Error(ex, "Prowlarr URL is invalid");
failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Whisparr cannot connect to Prowlarr"));
break;
case HttpStatusCode.SeeOther:
_logger.Error(ex, "Whisparr returned redirect and is invalid");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Whisparr URL is invalid, Prowlarr cannot connect to Whisparr - are you missing a URL base?"));
break;
default:
_logger.Error(ex, "Unable to complete application test");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Whisparr. {ex.Message}"));
break;
}
}
catch (JsonReaderException ex)
{
_logger.Error(ex, "Unable to parse JSON response from application");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to parse JSON response from application. {ex.Message}"));
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to complete application test");
failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Whisparr. {ex.Message}"));
}
return new ValidationResult(failures);

View File

@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Applications.Whisparr
[FieldDefinition(1, Label = "Whisparr Server", HelpText = "URL used to connect to Whisparr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:6969")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Whisparr in Settings/General")]
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Whisparr in Settings/General")]
public string ApiKey { get; set; }
[FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")]

View File

@@ -82,13 +82,23 @@ namespace NzbDrone.Core.Applications.Whisparr
request.SetContent(indexer.ToJson());
return Execute<WhisparrIndexer>(request);
try
{
return ExecuteIndexerRequest(request);
}
catch (HttpException ex) when (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
request.Url = request.Url.AddQueryParam("forceSave", "true");
return ExecuteIndexerRequest(request);
}
}
public WhisparrIndexer UpdateIndexer(WhisparrIndexer indexer, WhisparrSettings settings)
{
var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexer.Id}", HttpMethod.Put);
request.Url = request.Url.AddQueryParam("forceSave", "true");
request.SetContent(indexer.ToJson());
return ExecuteIndexerRequest(request);
@@ -100,38 +110,7 @@ namespace NzbDrone.Core.Applications.Whisparr
request.SetContent(indexer.ToJson());
try
{
Execute<WhisparrIndexer>(request);
}
catch (HttpException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
{
_logger.Error(ex, "API Key is invalid");
return new ValidationFailure("ApiKey", "API Key is invalid");
}
if (ex.Response.StatusCode == HttpStatusCode.BadRequest)
{
_logger.Error(ex, "Prowlarr URL is invalid");
return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Whisparr cannot connect to Prowlarr");
}
if (ex.Response.StatusCode == HttpStatusCode.SeeOther)
{
_logger.Error(ex, "Whisparr returned redirect and is invalid");
return new ValidationFailure("BaseUrl", "Whisparr url is invalid, Prowlarr cannot connect to Whisparr - are you missing a url base?");
}
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("BaseUrl", "Unable to complete application test");
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("", "Unable to send test message");
}
_httpClient.Post(request);
return null;
}
@@ -166,8 +145,10 @@ namespace NzbDrone.Core.Applications.Whisparr
break;
default:
_logger.Error(ex, "Unexpected response status code: {0}", ex.Response.StatusCode);
throw;
break;
}
throw;
}
catch (JsonReaderException ex)
{
@@ -179,15 +160,15 @@ namespace NzbDrone.Core.Applications.Whisparr
_logger.Error(ex, "Unable to add or update indexer");
throw;
}
return null;
}
private HttpRequest BuildRequest(WhisparrSettings settings, string resource, HttpMethod method)
{
var baseUrl = settings.BaseUrl.TrimEnd('/');
var request = new HttpRequestBuilder(baseUrl).Resource(resource)
var request = new HttpRequestBuilder(baseUrl)
.Resource(resource)
.Accept(HttpAccept.Json)
.SetHeader("X-Api-Key", settings.ApiKey)
.Build();
@@ -204,9 +185,12 @@ namespace NzbDrone.Core.Applications.Whisparr
{
var response = _httpClient.Execute(request);
var results = JsonConvert.DeserializeObject<TResource>(response.Content);
if ((int)response.StatusCode >= 300)
{
throw new HttpException(response);
}
return results;
return Json.Deserialize<TResource>(response.Content);
}
}
}

View File

@@ -0,0 +1,72 @@
using System.Collections.Generic;
using System.Data;
using Dapper;
using FluentMigrator;
using Newtonsoft.Json.Linq;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(034)]
public class history_fix_data_titles : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(MigrateHistoryDataTitle);
}
private void MigrateHistoryDataTitle(IDbConnection conn, IDbTransaction tran)
{
var updatedHistory = new List<object>();
using (var selectCommand = conn.CreateCommand())
{
selectCommand.Transaction = tran;
selectCommand.CommandText = "SELECT \"Id\", \"Data\", \"EventType\" FROM \"History\" WHERE \"EventType\" != 3";
using var reader = selectCommand.ExecuteReader();
while (reader.Read())
{
var id = reader.GetInt32(0);
var data = reader.GetString(1);
var eventType = reader.GetInt32(2);
if (!string.IsNullOrWhiteSpace(data))
{
var jsonObject = Json.Deserialize<JObject>(data);
if (eventType == 1 && jsonObject.ContainsKey("title"))
{
jsonObject.Add("grabTitle", jsonObject.Value<string>("title"));
jsonObject.Remove("title");
}
if (eventType != 1 && jsonObject.ContainsKey("bookTitle"))
{
jsonObject.Add("title", jsonObject.Value<string>("bookTitle"));
jsonObject.Remove("bookTitle");
}
data = jsonObject.ToJson();
if (!jsonObject.ContainsKey("grabTitle") && !jsonObject.ContainsKey("title"))
{
continue;
}
updatedHistory.Add(new
{
Id = id,
Data = data
});
}
}
}
var updateHistorySql = "UPDATE \"History\" SET \"Data\" = @Data WHERE \"Id\" = @Id";
conn.Execute(updateHistorySql, updatedHistory, transaction: tran);
}
}
}

View File

@@ -128,52 +128,55 @@ namespace NzbDrone.Core.History
Successful = response?.StatusCode == HttpStatusCode.OK || (response is { Request: { SuppressHttpError: true, SuppressHttpErrorStatusCodes: not null } } && response.Request.SuppressHttpErrorStatusCodes.Contains(response.StatusCode))
};
if (message.Query is MovieSearchCriteria)
if (message.Query is MovieSearchCriteria movieSearchCriteria)
{
history.Data.Add("ImdbId", ((MovieSearchCriteria)message.Query).FullImdbId ?? string.Empty);
history.Data.Add("TmdbId", ((MovieSearchCriteria)message.Query).TmdbId?.ToString() ?? string.Empty);
history.Data.Add("TraktId", ((MovieSearchCriteria)message.Query).TraktId?.ToString() ?? string.Empty);
history.Data.Add("Year", ((MovieSearchCriteria)message.Query).Year?.ToString() ?? string.Empty);
history.Data.Add("Genre", ((MovieSearchCriteria)message.Query).Genre ?? string.Empty);
history.Data.Add("ImdbId", movieSearchCriteria.FullImdbId);
history.Data.Add("TmdbId", movieSearchCriteria.TmdbId?.ToString());
history.Data.Add("TraktId", movieSearchCriteria.TraktId?.ToString());
history.Data.Add("Year", movieSearchCriteria.Year?.ToString());
history.Data.Add("Genre", movieSearchCriteria.Genre);
}
if (message.Query is TvSearchCriteria)
if (message.Query is TvSearchCriteria tvSearchCriteria)
{
history.Data.Add("ImdbId", ((TvSearchCriteria)message.Query).FullImdbId ?? string.Empty);
history.Data.Add("TvdbId", ((TvSearchCriteria)message.Query).TvdbId?.ToString() ?? string.Empty);
history.Data.Add("TmdbId", ((TvSearchCriteria)message.Query).TmdbId?.ToString() ?? string.Empty);
history.Data.Add("TraktId", ((TvSearchCriteria)message.Query).TraktId?.ToString() ?? string.Empty);
history.Data.Add("RId", ((TvSearchCriteria)message.Query).RId?.ToString() ?? string.Empty);
history.Data.Add("TvMazeId", ((TvSearchCriteria)message.Query).TvMazeId?.ToString() ?? string.Empty);
history.Data.Add("Season", ((TvSearchCriteria)message.Query).Season?.ToString() ?? string.Empty);
history.Data.Add("Episode", ((TvSearchCriteria)message.Query).Episode ?? string.Empty);
history.Data.Add("Year", ((TvSearchCriteria)message.Query).Year?.ToString() ?? string.Empty);
history.Data.Add("Genre", ((TvSearchCriteria)message.Query).Genre ?? string.Empty);
history.Data.Add("ImdbId", tvSearchCriteria.FullImdbId);
history.Data.Add("TvdbId", tvSearchCriteria.TvdbId?.ToString());
history.Data.Add("TmdbId", tvSearchCriteria.TmdbId?.ToString());
history.Data.Add("TraktId", tvSearchCriteria.TraktId?.ToString());
history.Data.Add("RId", tvSearchCriteria.RId?.ToString());
history.Data.Add("TvMazeId", tvSearchCriteria.TvMazeId?.ToString());
history.Data.Add("Season", tvSearchCriteria.Season?.ToString());
history.Data.Add("Episode", tvSearchCriteria.Episode);
history.Data.Add("Year", tvSearchCriteria.Year?.ToString());
history.Data.Add("Genre", tvSearchCriteria.Genre);
}
if (message.Query is MusicSearchCriteria)
if (message.Query is MusicSearchCriteria musicSearchCriteria)
{
history.Data.Add("Artist", ((MusicSearchCriteria)message.Query).Artist ?? string.Empty);
history.Data.Add("Album", ((MusicSearchCriteria)message.Query).Album ?? string.Empty);
history.Data.Add("Track", ((MusicSearchCriteria)message.Query).Track ?? string.Empty);
history.Data.Add("Label", ((MusicSearchCriteria)message.Query).Label ?? string.Empty);
history.Data.Add("Year", ((MusicSearchCriteria)message.Query).Year?.ToString() ?? string.Empty);
history.Data.Add("Genre", ((MusicSearchCriteria)message.Query).Genre ?? string.Empty);
history.Data.Add("Artist", musicSearchCriteria.Artist);
history.Data.Add("Album", musicSearchCriteria.Album);
history.Data.Add("Track", musicSearchCriteria.Track);
history.Data.Add("Label", musicSearchCriteria.Label);
history.Data.Add("Year", musicSearchCriteria.Year?.ToString());
history.Data.Add("Genre", musicSearchCriteria.Genre);
}
if (message.Query is BookSearchCriteria)
if (message.Query is BookSearchCriteria bookSearchCriteria)
{
history.Data.Add("Author", ((BookSearchCriteria)message.Query).Author ?? string.Empty);
history.Data.Add("BookTitle", ((BookSearchCriteria)message.Query).Title ?? string.Empty);
history.Data.Add("Publisher", ((BookSearchCriteria)message.Query).Publisher ?? string.Empty);
history.Data.Add("Year", ((BookSearchCriteria)message.Query).Year?.ToString() ?? string.Empty);
history.Data.Add("Genre", ((BookSearchCriteria)message.Query).Genre ?? string.Empty);
history.Data.Add("Author", bookSearchCriteria.Author);
history.Data.Add("Title", bookSearchCriteria.Title);
history.Data.Add("Publisher", bookSearchCriteria.Publisher);
history.Data.Add("Year", bookSearchCriteria.Year?.ToString());
history.Data.Add("Genre", bookSearchCriteria.Genre);
}
// Clean empty data
history.Data = history.Data.Where(d => d.Value != null).ToDictionary(x => x.Key, x => x.Value);
history.Data.Add("ElapsedTime", message.QueryResult.Cached ? "0" : message.QueryResult.Response?.ElapsedTime.ToString() ?? string.Empty);
history.Data.Add("Query", message.Query.SearchTerm ?? string.Empty);
history.Data.Add("QueryType", message.Query.SearchType ?? string.Empty);
history.Data.Add("Categories", string.Join(",", message.Query.Categories) ?? string.Empty);
history.Data.Add("Categories", string.Join(",", message.Query.Categories ?? Array.Empty<int>()));
history.Data.Add("Source", message.Query.Source ?? string.Empty);
history.Data.Add("Host", message.Query.Host ?? string.Empty);
history.Data.Add("QueryResults", message.QueryResult.Releases?.Count.ToString() ?? string.Empty);
@@ -196,7 +199,7 @@ namespace NzbDrone.Core.History
history.Data.Add("Source", message.Source ?? string.Empty);
history.Data.Add("Host", message.Host ?? string.Empty);
history.Data.Add("GrabMethod", message.Redirect ? "Redirect" : "Proxy");
history.Data.Add("Title", message.Title);
history.Data.Add("GrabTitle", message.Title);
history.Data.Add("Url", message.Url ?? string.Empty);
_historyRepository.Insert(history);

View File

@@ -52,12 +52,14 @@ namespace NzbDrone.Core.IndexerStats
};
var sortedEvents = indexer.OrderBy(v => v.Date)
.ThenBy(v => v.Id)
.ToArray();
var temp = 0;
.ThenBy(v => v.Id)
.ToArray();
var elapsedTimeEvents = sortedEvents.Where(h => int.TryParse(h.Data.GetValueOrDefault("elapsedTime"), out temp))
.Select(h => temp);
var temp = 0;
var elapsedTimeEvents = sortedEvents
.Where(h => int.TryParse(h.Data.GetValueOrDefault("elapsedTime"), out temp) && h.Data.GetValueOrDefault("cached") != "1")
.Select(h => temp)
.ToArray();
indexerStats.AverageResponseTime = elapsedTimeEvents.Any() ? (int)elapsedTimeEvents.Average() : 0;

View File

@@ -25,8 +25,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class BeyondHD : TorrentIndexerBase<BeyondHDSettings>
{
public override string Name => "BeyondHD";
public override string[] IndexerUrls => new string[] { "https://beyond-hd.me/" };
public override string[] IndexerUrls => new[] { "https://beyond-hd.me/" };
public override string Description => "BeyondHD (BHD) is a Private Torrent Tracker for HD MOVIES / TV";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
@@ -38,12 +37,12 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new BeyondHDRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
return new BeyondHDRequestGenerator(Settings, Capabilities);
}
public override IParseIndexerResponse GetParser()
{
return new BeyondHDParser(Settings, Capabilities.Categories);
return new BeyondHDParser(Capabilities.Categories);
}
private IndexerCapabilities SetCapabilities()
@@ -51,13 +50,13 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
},
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
}
{
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies, "Movies");
@@ -69,15 +68,21 @@ namespace NzbDrone.Core.Indexers.Definitions
public class BeyondHDRequestGenerator : IIndexerRequestGenerator
{
public BeyondHDSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
private readonly BeyondHDSettings _settings;
private readonly IndexerCapabilities _capabilities;
public BeyondHDRequestGenerator(BeyondHDSettings settings, IndexerCapabilities capabilities)
{
_settings = settings;
_capabilities = capabilities;
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null, int tmdbId = 0)
{
var body = new Dictionary<string, object>
{
{ "action", "search" },
{ "rsskey", Settings.RssKey }
{ "rsskey", _settings.RssKey }
};
if (imdbId.IsNotNullOrWhiteSpace())
@@ -95,31 +100,34 @@ namespace NzbDrone.Core.Indexers.Definitions
body.Add("search", term);
}
var cats = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
var cats = _capabilities.Categories.MapTorznabCapsToTrackers(categories);
if (cats.Count > 0)
{
body.Add("categories", string.Join(",", cats));
}
var searchUrl = Settings.BaseUrl + "api/torrents/" + Settings.ApiKey;
var searchUrl = $"{_settings.BaseUrl}api/torrents/{_settings.ApiKey}";
var request = new HttpRequest(searchUrl, HttpAccept.Json);
request.Headers.ContentType = "application/json";
request.Method = HttpMethod.Post;
var request = new HttpRequest(searchUrl, HttpAccept.Json)
{
Headers =
{
ContentType = "application/json"
},
Method = HttpMethod.Post
};
request.SetContent(body.ToJson());
request.ContentSummary = body.ToJson(Formatting.None);
var indexerRequest = new IndexerRequest(request);
yield return indexerRequest;
yield return new IndexerRequest(request);
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.FullImdbId, searchCriteria.TmdbId.GetValueOrDefault()));
pageableRequests.Add(GetPagedRequests(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories, searchCriteria.FullImdbId, searchCriteria.TmdbId.GetValueOrDefault()));
return pageableRequests;
}
@@ -128,7 +136,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
pageableRequests.Add(GetPagedRequests(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories));
return pageableRequests;
}
@@ -137,7 +145,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories, searchCriteria.FullImdbId));
pageableRequests.Add(GetPagedRequests(searchCriteria.SanitizedTvSearchString, searchCriteria.Categories, searchCriteria.FullImdbId));
return pageableRequests;
}
@@ -146,7 +154,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
pageableRequests.Add(GetPagedRequests(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories));
return pageableRequests;
}
@@ -155,7 +163,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
pageableRequests.Add(GetPagedRequests(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories));
return pageableRequests;
}
@@ -166,19 +174,17 @@ namespace NzbDrone.Core.Indexers.Definitions
public class BeyondHDParser : IParseIndexerResponse
{
private readonly BeyondHDSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public BeyondHDParser(BeyondHDSettings settings, IndexerCapabilitiesCategories categories)
public BeyondHDParser(IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<TorrentInfo>();
var indexerHttpResponse = indexerResponse.HttpResponse;
if (indexerHttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerHttpResponse.StatusCode} code from indexer request");
@@ -201,6 +207,8 @@ namespace NzbDrone.Core.Indexers.Definitions
throw new IndexerException(indexerResponse, $"Indexer Error: {jsonResponse.Resource.StatusMessage}");
}
var releaseInfos = new List<ReleaseInfo>();
foreach (var row in jsonResponse.Resource.Results)
{
var details = row.InfoUrl;
@@ -231,11 +239,13 @@ namespace NzbDrone.Core.Indexers.Definitions
MinimumSeedTime = 172800, // 120 hours
};
torrentInfos.Add(release);
releaseInfos.Add(release);
}
// order by date
return torrentInfos.OrderByDescending(o => o.PublishDate).ToArray();
return releaseInfos
.OrderByDescending(o => o.PublishDate)
.ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }

View File

@@ -400,16 +400,18 @@ namespace NzbDrone.Core.Indexers.Definitions.Cardigann
if (login.Captcha != null)
{
var captcha = login.Captcha;
Settings.ExtraFieldData.TryGetValue("CAPTCHA", out var captchaText);
if (captchaText != null)
if (Settings.ExtraFieldData.TryGetValue("CAPTCHA", out var captchaText) && ((string)captchaText).IsNotNullOrWhiteSpace())
{
var input = captcha.Input;
if (login.Selectors)
{
var inputElement = landingResultDocument.QuerySelector(captcha.Input);
if (inputElement == null)
{
throw new CardigannConfigException(_definition, string.Format("Login failed: No captcha input found using {0}", captcha.Input));
throw new CardigannConfigException(_definition, $"Login failed: No captcha input found using {captcha.Input}");
}
input = inputElement.GetAttribute("name");

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using Newtonsoft.Json;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
@@ -64,6 +65,7 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
query.Medium = Settings.Mediums.ToArray();
request.SetContent(query.ToJson());
request.ContentSummary = query.ToJson(Formatting.None);
yield return new IndexerRequest(request);
}

View File

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

View File

@@ -338,6 +338,7 @@
"Query": "Query",
"QueryOptions": "Query Options",
"QueryResults": "Query Results",
"QueryType": "Query Type",
"Queue": "Queue",
"Queued": "Queued",
"RSS": "RSS",