Compare commits

...

89 Commits

Author SHA1 Message Date
Qstick 65e6aa05c3 Bump version to 1.4.0 2023-04-23 12:15:23 -05:00
Bogdan fb20b3e61b Fixed: (AnimeBytes) Add tests for season parsing 2023-04-23 14:55:50 +03:00
Bogdan b8a77830aa Fixed: (AnimeBytes) Parse response with STJson 2023-04-23 07:44:46 +03:00
Bogdan d2ba52cdce Fixed: (Indexers) Hide errors with SuppressHttpErrorStatusCodes 2023-04-23 07:44:16 +03:00
Bogdan 43f881c442 Fixed: (Nebulance) Don't parse invalid response as JSON 2023-04-23 06:40:26 +03:00
Bogdan 4a5e923999 Fixed: (AnimeBytes) Parse season only for category Anime 2023-04-22 20:37:09 +03:00
Bogdan 57e1b6b4a0 Fixed: (AnimeBytes) Improve season/episode detection 2023-04-22 20:19:44 +03:00
Bogdan 9cc60760c3 Fixed: (AnimeBytes) Exclude RAW only for category Anime 2023-04-22 20:17:36 +03:00
Weblate 2811feb14e Translated using Weblate (Chinese (Simplified) (zh_CN))
Currently translated at 99.7% (483 of 484 strings)

Translated using Weblate (Portuguese)

Currently translated at 78.3% (379 of 484 strings)

Co-authored-by: PedroBuffon <henriquebuffon@gmail.com>
Co-authored-by: SHUAI.W <x@ousui.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-04-22 14:21:43 +03:00
Bogdan 46af9223bc Fixed: (BakaBT) Update check if logged in
Resolves #1617
2023-04-22 07:47:14 +03:00
Bogdan 025156978b Fixed: (AnimeBytes) Ignore useless extensions to improve single file names 2023-04-22 03:13:56 +03:00
Bogdan d3ca861aea Fixed: (AnimeBytes) Remove The Movie from search term 2023-04-21 07:36:52 +03:00
Bogdan c9249ed583 Fixed: (UI) Typo in hover border colors 2023-04-20 11:51:44 +03:00
Bogdan 94cc56d0f6 Fixed: (AnimeBytes) Improve season/episode detection 2023-04-20 10:39:15 +03:00
Bogdan c8addc0d62 Rearrange params in Apprise 2023-04-20 08:10:05 +03:00
Bogdan 2015156061 New: (Apprise) Add notification type
Closes #1619
2023-04-20 07:45:51 +03:00
Bogdan 742c680014 Fixed: (AnimeBytes) Add Remux to release titles when possible 2023-04-20 03:08:13 +03:00
Bogdan b1add3f649 Fixed: (AnimeBytes) Parse M2TS property as BR-DISK 2023-04-20 01:54:18 +03:00
bakerboy448 65d6d518d7 New: Improve applications error reporting for requests (#1618)
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2023-04-19 03:44:22 +03:00
Cedric Lewe bc8ba5ca02 Fixed: (BakaBT) Update login check (#1617)
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2023-04-18 22:43:17 +03:00
Bogdan 6aebc4ee01 Revert properties order in SonarrSettings 2023-04-18 04:47:09 +03:00
Bogdan 9bbe51253b New: Add default urls to applications 2023-04-18 04:42:08 +03:00
Mark McDowall 88fbc30be2 New: Improved messaging when qBittorrent fails due to host header rejection
(cherry picked from commit 48b4cc5f3ffa0cb8eea6748db9091267216cef4f)
2023-04-18 03:42:48 +03:00
bakerboy448 5fdc6ee25d Fixed: (BroadcasTheNet) Improve season/episode/daily episode searches 2023-04-17 23:44:37 +03:00
Bogdan 4eb5a2d613 Fixed: (Cardigann) Simplify creating CardigannRequest 2023-04-17 21:42:05 +03:00
Bogdan 122883053a Fixed: (Cardigann) Respect Followredirect for login forms
Fixes #526
2023-04-17 19:38:37 +03:00
Bogdan 28d09cd384 Fixed: (Rarbg) Simplify retry fetching for expired tokens 2023-04-17 06:21:07 +03:00
Bogdan 17be8bb68a Add SuppressHttpErrorStatusCodes to HttpRequestBuilder 2023-04-17 06:21:07 +03:00
Qstick c5baded3d6 Simplify DatabaseType logic 2023-04-16 20:09:04 -05:00
Bogdan 349cfacdca Rename CC to Cc 2023-04-17 04:06:29 +03:00
Bogdan 788fa6d96a Fixed: (Database) Improve Version detection 2023-04-17 04:04:01 +03:00
Bogdan fbea5bbc06 Fixed: (CookieUtil) Add tests 2023-04-16 05:13:30 +03:00
Bogdan d667c7d853 Fixed: Use Array.Empty and fix a few multiple enumerations
(cherry picked from commit 11d91faaada0e70910c832ce405ddeed52a24172)
2023-04-16 05:09:12 +03:00
Qstick a9e1204a9b Fixed: Validate if equals or child for startup folder
(cherry picked from commit 0991cfe27efd6ddb533227b25754661e18d7e9ad)
2023-04-16 05:07:50 +03:00
Bogdan 88e3f86262 Fixed: Migrate to FluentValidation 9 2023-04-16 05:07:50 +03:00
Bogdan 1c173fc984 Fixed: (Cardigann) Update namespace and use nameof() 2023-04-15 07:04:43 +03:00
Bogdan 6e8f3d814a Fixed: (Cardigann) Log requests as debug 2023-04-15 06:54:44 +03:00
Bogdan 14e105e37e Fixed: (Redacted) Add tests 2023-04-14 23:50:33 +03:00
Bogdan 9e0deb8f74 Fixed: (TorrentInfo) Cleanup redundant Freeleech property 2023-04-14 23:13:21 +03:00
Bogdan 245e573089 Fixed: (TorrentPotato) Update namespace 2023-04-14 23:11:03 +03:00
Bogdan 5e8bfa2ffb Fixed: (RuTracker) Add new indexer url
Fixes #1610
2023-04-14 09:06:41 +03:00
Bogdan 555c924e50 New: Add version and timestamp to backup archive
(cherry picked from commit ed3d880974ae6a1430866eebaf72533f35258f6f)

Fixes #662
Closes #1600
2023-04-14 06:06:15 +03:00
Bogdan 8404b85624 Fixed: (AnimeBytes) API responds now with size as integer 2023-04-14 05:58:23 +03:00
Bogdan dc5e6d29e1 Bump dotnet to 6.0.16 2023-04-14 03:21:01 +03:00
Bogdan 8c42b7a69b Update DryIoc, Newtonsoft.Json, Sentry, SharpZipLib, MailKit 2023-04-14 01:56:21 +03:00
Weblate 3a6ebdef8a Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (484 of 484 strings)

Translated using Weblate (Spanish)

Currently translated at 77.0% (373 of 484 strings)

Translated using Weblate (German)

Currently translated at 95.8% (464 of 484 strings)

Co-authored-by: Deflector8249 <lh2jwko5@gomail.me>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: xuko <jorge.xuko@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-04-13 01:29:23 +03:00
Bogdan 5f57957462 Fixed: (AnimeBytes) Improve release group detection 2023-04-13 01:13:03 +03:00
Bogdan 12526c1bb3 Fixed: (AnimeBytes) Improve season detection 2023-04-12 23:01:24 +03:00
Bogdan 29f049f766 Fixed: (AnimeBytes) Fix tests for release title structure change 2023-04-12 04:19:37 +03:00
Bogdan 40f4e1b82a Fixed: (AnimeBytes) Change release title structure for movies 2023-04-12 03:35:28 +03:00
Bogdan 065fbb30bf Fixed: Support the old broken functionality in GetValueEnum
(cherry picked from commit 2c4e1be12ad5e3b8362f83b8185c143f8e66062b)

Fixes #1602
2023-04-11 21:42:20 +03:00
Bogdan ea24a81ef7 Fixed: (API) Log errors in Newznab response 2023-04-11 18:32:45 +03:00
Bogdan 451f60319f Fixed: (Cardigann) Add check for request.inputs, since are null when pathselector is used
Fixes #1158
2023-04-11 18:31:11 +03:00
Bogdan c6ed5d65e0 Fixed: (Core) Ensure default config file on starting app
Fixes #674
Fixes #1588
2023-04-11 17:22:48 +03:00
bakerboy448 4e5cd05bbd Fixed: Improve Indexer Tags Help Text 2023-04-10 22:47:27 +03:00
Bogdan 6b2b953686 Fixed: (Cardigann) Catch errors when search.rows.count is not present 2023-04-10 07:49:22 +03:00
Mark McDowall 31c05be9de Fixed: Prevent getting disk space from returning no information when it partially fails
(cherry picked from commit 2c65e4fa41418157d0d27b34c3bab80158cff219)
2023-04-09 22:16:51 -05:00
Mark McDowall bc852c0b55 Fixed: USB drives mounted to folders are treated as different mounts
(cherry picked from commit 75378f7bde90b9d3d9b72404c25c017da2cd147c)
2023-04-09 22:15:51 -05:00
Qstick 18651d8be1 Cleanup old Radarr parsing library conditional 2023-04-09 22:14:53 -05:00
Qstick 1608095345 Fixed: Cleanup TaskManager, add BackupInterval limits 2023-04-09 22:11:46 -05:00
Bogdan 7700014ceb Fixed: (PassThePopcorn) Disable grouping, add pagination and use STJson 2023-04-10 05:32:33 +03:00
Bogdan 3fbc2912f0 Fixed: (AnimeBytes) Add limit and refactor parser 2023-04-10 05:03:43 +03:00
Mark McDowall 3192990874 Fixed: Number input changing while scrolling
(cherry picked from commit cc46ed56b4b70fe1f1443c0a927383f19c989c47)
2023-04-09 20:19:02 -05:00
Bogdan fb908e8e19 Fixed: Use project name as relative path 2023-04-10 04:15:02 +03:00
Bogdan 8e60c707b2 Fixed: (Cardigann) Skip rows parsing on zero rows 2023-04-10 02:55:32 +03:00
Bogdan a184bb0784 Fixed: (Core) Use MinBy and MaxBy 2023-04-09 21:14:40 +03:00
bakerboy448 e5ccbaaf24 Update bug_report.yml - trace logs checkbox 2023-04-09 20:25:11 +03:00
Weblate 362e0acad1 Translated using Weblate (Czech)
Currently translated at 70.0% (339 of 484 strings)

Co-authored-by: tomas15420 <tomas15420@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
Translation: Servarr/Prowlarr
2023-04-09 20:23:39 +03:00
Bogdan 54d06460d0 Fixed: (Avistaz/SecretCinema) Fix tests 2023-04-09 16:49:56 +03:00
Bogdan c11bcf4c41 Fixed: (SecretCinema) Fix PublishDate timezone 2023-04-09 16:18:05 +03:00
Bogdan 2e58583263 Fixed: (Avistaz) Fix PublishDate timezone 2023-04-09 16:14:09 +03:00
Bogdan bf7f769f13 Fixed: (AvistaZ) Don't log http errors like 404 for imdb/tmdb/tvdb searches 2023-04-08 22:55:05 +03:00
Bogdan 7820a83a5d Fixed: (Indexers) Include exception message in ValidationFailure 2023-04-08 22:49:53 +03:00
Bogdan d937bdac69 Fixed: (NZBIndex) Request generator cleanup 2023-04-08 22:17:29 +03:00
Qstick ebca32af46 Fixed: (NzbIndex) Paging starts at 0
Fixes #1586
2023-04-08 09:52:17 -05:00
Bogdan 21bda07510 Fixed: (Toloka) Add authors.gif to check for FL
Co-authored-by: odayny <odayny@users.noreply.github.com>
2023-04-08 01:11:37 +03:00
Bogdan f638cf34d1 Fixed: (UI) Fix search sorting by empty categories 2023-04-08 00:52:30 +03:00
Bogdan b7fcdb5356 Fixed: (AnimeBytes) Add search by year 2023-04-06 17:23:54 +03:00
bakerboy448 2e4fa9d06d Fixed: (AnimeBytes) Change RateLimit to 4s (#1580) 2023-04-06 04:32:58 +03:00
bakerboy448 9b50fc40ca Fix: (MaM) Improve No results logic (#1578) 2023-04-05 22:21:25 +03:00
bakerboy448 3c60159df0 Fixed: (AnimeBytes) RateLimit 1req per 10s
Fixes #1572
2023-04-05 20:53:04 +03:00
Bogdan e075003c8b Fixed: (FileList) Change TZ to account DST 2023-04-05 06:28:07 +03:00
Servarr b19202d9f5 Automated API Docs update 2023-04-05 05:45:34 +03:00
Bogdan 2784ee8ce6 Fixed: (UI) Update frontend packages 2023-04-05 05:14:08 +03:00
Bogdan 5aa4a5faaa Fixed: (Tags) Show applications in tag details 2023-04-05 05:12:56 +03:00
Weblate 1d00b40f90 Translated using Weblate (Portuguese)
Currently translated at 77.6% (376 of 484 strings)

Translated using Weblate (Hebrew)

Currently translated at 80.3% (389 of 484 strings)

Translated using Weblate (Chinese (Simplified) (zh_CN))

Currently translated at 100.0% (484 of 484 strings)

Co-authored-by: Cassio Rizzi <clrizzi@gmail.com>
Co-authored-by: Nir Israel Hen <nirisraelh@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: 雨 <625250353@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-04-03 19:54:21 -05:00
Qstick 93dd378ade Bump version to 1.3.3 2023-04-02 15:14:06 -05:00
Bogdan 534ca73bf8 Fixed: (Toloka) Add FreeleechOnly setting 2023-04-01 21:12:23 +03:00
Bogdan bceebc34c1 New: (Cardigann) Bump to v9 (#1551)
* New: (Cardigann) Add MissingAttributeEqualsNoResults support

(cherry picked from commit 4e8bb37a5c)

* New: (Cardigann) Add AllowEmptyInputs

* New: (Cardigann) Bump to v9

* New: (Cardigann) Add default value for fields
2023-03-30 14:57:04 +03:00
157 changed files with 5070 additions and 2030 deletions
+7
View File
@@ -71,3 +71,10 @@ body:
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering! Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
validations: validations:
required: true required: true
- type: checkboxes
attributes:
label: Trace Logs have been provided as applicable. Reports may be closed if the required logs are not provided.
description: Trace logs are generally required for all bug reports
options:
- label: I have followed the steps in the wiki link above and provided the required trace logs that are relevant and show this issue.
required: true
+2 -2
View File
@@ -9,13 +9,13 @@ variables:
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '1.3.2' majorVersion: '1.4.0'
minorVersion: $[counter('minorVersion', 1)] minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)' prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)' buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
sentryOrg: 'servarr' sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com' sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.405' dotnetVersion: '6.0.408'
innoVersion: '6.2.0' innoVersion: '6.2.0'
nodeVersion: '16.x' nodeVersion: '16.x'
windowsImage: 'windows-2022' windowsImage: 'windows-2022'
@@ -112,6 +112,12 @@ class TextInput extends Component {
this._isMouseTarget = false; this._isMouseTarget = false;
}; };
onWheel = () => {
if (this.props.type === 'number') {
this._input.blur();
}
};
// //
// Render // Render
@@ -161,6 +167,7 @@ class TextInput extends Component {
onKeyUp={this.onKeyUp} onKeyUp={this.onKeyUp}
onMouseDown={this.onMouseDown} onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp} onMouseUp={this.onMouseUp}
onWheel={this.onWheel}
/> />
); );
} }
@@ -17,6 +17,7 @@ function TagDetailsModalContent(props) {
indexers, indexers,
notifications, notifications,
indexerProxies, indexerProxies,
applications,
onModalClose, onModalClose,
onDeleteTagPress onDeleteTagPress
} = props; } = props;
@@ -79,6 +80,21 @@ function TagDetailsModalContent(props) {
} }
</FieldSet> </FieldSet>
} }
{
!!applications.length &&
<FieldSet legend={translate('Applications')}>
{
applications.map((item) => {
return (
<div key={item.id}>
{item.name}
</div>
);
})
}
</FieldSet>
}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
@@ -110,6 +126,7 @@ TagDetailsModalContent.propTypes = {
indexers: PropTypes.arrayOf(PropTypes.object).isRequired, indexers: PropTypes.arrayOf(PropTypes.object).isRequired,
notifications: PropTypes.arrayOf(PropTypes.object).isRequired, notifications: PropTypes.arrayOf(PropTypes.object).isRequired,
indexerProxies: PropTypes.arrayOf(PropTypes.object).isRequired, indexerProxies: PropTypes.arrayOf(PropTypes.object).isRequired,
applications: PropTypes.arrayOf(PropTypes.object).isRequired,
onModalClose: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired,
onDeleteTagPress: PropTypes.func.isRequired onDeleteTagPress: PropTypes.func.isRequired
}; };
@@ -18,16 +18,24 @@ function createMatchingIndexersSelector() {
function createMatchingIndexerProxiesSelector() { function createMatchingIndexerProxiesSelector() {
return createSelector( return createSelector(
(state, { notificationIds }) => notificationIds, (state, { indexerProxyIds }) => indexerProxyIds,
(state) => state.settings.notifications.items, (state) => state.settings.indexerProxies.items,
findMatchingItems findMatchingItems
); );
} }
function createMatchingNotificationsSelector() { function createMatchingNotificationsSelector() {
return createSelector( return createSelector(
(state, { indexerProxyIds }) => indexerProxyIds, (state, { notificationIds }) => notificationIds,
(state) => state.settings.indexerProxies.items, (state) => state.settings.notifications.items,
findMatchingItems
);
}
function createMatchingApplicationsSelector() {
return createSelector(
(state, { applicationIds }) => applicationIds,
(state) => state.settings.applications.items,
findMatchingItems findMatchingItems
); );
} }
@@ -37,11 +45,13 @@ function createMapStateToProps() {
createMatchingIndexersSelector(), createMatchingIndexersSelector(),
createMatchingIndexerProxiesSelector(), createMatchingIndexerProxiesSelector(),
createMatchingNotificationsSelector(), createMatchingNotificationsSelector(),
(indexers, indexerProxies, notifications) => { createMatchingApplicationsSelector(),
(indexers, indexerProxies, notifications, applications) => {
return { return {
indexers, indexers,
indexerProxies, indexerProxies,
notifications notifications,
applications
}; };
} }
); );
+15 -3
View File
@@ -55,7 +55,8 @@ class Tag extends Component {
label, label,
notificationIds, notificationIds,
indexerIds, indexerIds,
indexerProxyIds indexerProxyIds,
applicationIds
} = this.props; } = this.props;
const { const {
@@ -66,7 +67,8 @@ class Tag extends Component {
const isTagUsed = !!( const isTagUsed = !!(
indexerIds.length || indexerIds.length ||
notificationIds.length || notificationIds.length ||
indexerProxyIds.length indexerProxyIds.length ||
applicationIds.length
); );
return ( return (
@@ -102,6 +104,13 @@ class Tag extends Component {
{indexerProxyIds.length} {indexerProxyIds.length > 1 ? translate('IndexerProxies') : translate('IndexerProxy')} {indexerProxyIds.length} {indexerProxyIds.length > 1 ? translate('IndexerProxies') : translate('IndexerProxy')}
</div> </div>
} }
{
!!applicationIds.length &&
<div>
{applicationIds.length} {applicationIds.length > 1 ? translate('Applications') : translate('Application')}
</div>
}
</div> </div>
} }
@@ -118,6 +127,7 @@ class Tag extends Component {
indexerIds={indexerIds} indexerIds={indexerIds}
notificationIds={notificationIds} notificationIds={notificationIds}
indexerProxyIds={indexerProxyIds} indexerProxyIds={indexerProxyIds}
applicationIds={applicationIds}
isOpen={isDetailsModalOpen} isOpen={isDetailsModalOpen}
onModalClose={this.onDetailsModalClose} onModalClose={this.onDetailsModalClose}
onDeleteTagPress={this.onDeleteTagPress} onDeleteTagPress={this.onDeleteTagPress}
@@ -143,13 +153,15 @@ Tag.propTypes = {
notificationIds: PropTypes.arrayOf(PropTypes.number).isRequired, notificationIds: PropTypes.arrayOf(PropTypes.number).isRequired,
indexerIds: PropTypes.arrayOf(PropTypes.number).isRequired, indexerIds: PropTypes.arrayOf(PropTypes.number).isRequired,
indexerProxyIds: PropTypes.arrayOf(PropTypes.number).isRequired, indexerProxyIds: PropTypes.arrayOf(PropTypes.number).isRequired,
applicationIds: PropTypes.arrayOf(PropTypes.number).isRequired,
onConfirmDeleteTag: PropTypes.func.isRequired onConfirmDeleteTag: PropTypes.func.isRequired
}; };
Tag.defaultProps = { Tag.defaultProps = {
indexerIds: [], indexerIds: [],
notificationIds: [], notificationIds: [],
indexerProxyIds: [] indexerProxyIds: [],
applicationIds: []
}; };
export default Tag; export default Tag;
+8 -4
View File
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { fetchIndexerProxies, fetchNotifications } from 'Store/Actions/settingsActions'; import { fetchApplications, fetchIndexerProxies, fetchNotifications } from 'Store/Actions/settingsActions';
import { fetchTagDetails } from 'Store/Actions/tagActions'; import { fetchTagDetails } from 'Store/Actions/tagActions';
import Tags from './Tags'; import Tags from './Tags';
@@ -27,7 +27,8 @@ function createMapStateToProps() {
const mapDispatchToProps = { const mapDispatchToProps = {
dispatchFetchTagDetails: fetchTagDetails, dispatchFetchTagDetails: fetchTagDetails,
dispatchFetchNotifications: fetchNotifications, dispatchFetchNotifications: fetchNotifications,
dispatchFetchIndexerProxies: fetchIndexerProxies dispatchFetchIndexerProxies: fetchIndexerProxies,
dispatchFetchApplications: fetchApplications
}; };
class MetadatasConnector extends Component { class MetadatasConnector extends Component {
@@ -39,12 +40,14 @@ class MetadatasConnector extends Component {
const { const {
dispatchFetchTagDetails, dispatchFetchTagDetails,
dispatchFetchNotifications, dispatchFetchNotifications,
dispatchFetchIndexerProxies dispatchFetchIndexerProxies,
dispatchFetchApplications
} = this.props; } = this.props;
dispatchFetchTagDetails(); dispatchFetchTagDetails();
dispatchFetchNotifications(); dispatchFetchNotifications();
dispatchFetchIndexerProxies(); dispatchFetchIndexerProxies();
dispatchFetchApplications();
} }
// //
@@ -62,7 +65,8 @@ class MetadatasConnector extends Component {
MetadatasConnector.propTypes = { MetadatasConnector.propTypes = {
dispatchFetchTagDetails: PropTypes.func.isRequired, dispatchFetchTagDetails: PropTypes.func.isRequired,
dispatchFetchNotifications: PropTypes.func.isRequired, dispatchFetchNotifications: PropTypes.func.isRequired,
dispatchFetchIndexerProxies: PropTypes.func.isRequired dispatchFetchIndexerProxies: PropTypes.func.isRequired,
dispatchFetchApplications: PropTypes.func.isRequired
}; };
export default connect(createMapStateToProps, mapDispatchToProps)(MetadatasConnector); export default connect(createMapStateToProps, mapDispatchToProps)(MetadatasConnector);
+1 -1
View File
@@ -144,7 +144,7 @@ export const defaultState = {
}, },
category: function(item) { category: function(item) {
if (item.categories.length > 0) { if (item.categories !== undefined && item.categories.length > 0) {
const sortedCats = item.categories.filter((cat) => cat.name !== undefined).sort((c) => c.id); const sortedCats = item.categories.filter((cat) => cat.name !== undefined).sort((c) => c.id);
const firstCat = sortedCats[0]; const firstCat = sortedCats[0];
+5 -5
View File
@@ -76,27 +76,27 @@ module.exports = {
defaultButtonBackgroundColor: '#333', defaultButtonBackgroundColor: '#333',
defaultBorderColor: '#eaeaea', defaultBorderColor: '#eaeaea',
defaultHoverBackgroundColor: '#444', defaultHoverBackgroundColor: '#444',
defaultHoverBorderColor: '#d6d6d6;', defaultHoverBorderColor: '#d6d6d6',
primaryBackgroundColor: '#5d9cec', primaryBackgroundColor: '#5d9cec',
primaryBorderColor: '#5899eb', primaryBorderColor: '#5899eb',
primaryHoverBackgroundColor: '#4b91ea', primaryHoverBackgroundColor: '#4b91ea',
primaryHoverBorderColor: '#3483e7;', primaryHoverBorderColor: '#3483e7',
successBackgroundColor: '#27c24c', successBackgroundColor: '#27c24c',
successBorderColor: '#26be4a', successBorderColor: '#26be4a',
successHoverBackgroundColor: '#24b145', successHoverBackgroundColor: '#24b145',
successHoverBorderColor: '#1f9c3d;', successHoverBorderColor: '#1f9c3d',
warningBackgroundColor: '#ff902b', warningBackgroundColor: '#ff902b',
warningBorderColor: '#ff8d26', warningBorderColor: '#ff8d26',
warningHoverBackgroundColor: '#ff8517', warningHoverBackgroundColor: '#ff8517',
warningHoverBorderColor: '#fc7800;', warningHoverBorderColor: '#fc7800',
dangerBackgroundColor: '#f05050', dangerBackgroundColor: '#f05050',
dangerBorderColor: '#f04b4b', dangerBorderColor: '#f04b4b',
dangerHoverBackgroundColor: '#ee3d3d', dangerHoverBackgroundColor: '#ee3d3d',
dangerHoverBorderColor: '#ec2626;', dangerHoverBorderColor: '#ec2626',
iconButtonDisabledColor: '#7a7a7a', iconButtonDisabledColor: '#7a7a7a',
iconButtonHoverColor: '#666', iconButtonHoverColor: '#666',
+5 -5
View File
@@ -76,27 +76,27 @@ module.exports = {
defaultButtonBackgroundColor: '#fff', defaultButtonBackgroundColor: '#fff',
defaultBorderColor: '#eaeaea', defaultBorderColor: '#eaeaea',
defaultHoverBackgroundColor: '#f5f5f5', defaultHoverBackgroundColor: '#f5f5f5',
defaultHoverBorderColor: '#d6d6d6;', defaultHoverBorderColor: '#d6d6d6',
primaryBackgroundColor: '#5d9cec', primaryBackgroundColor: '#5d9cec',
primaryBorderColor: '#5899eb', primaryBorderColor: '#5899eb',
primaryHoverBackgroundColor: '#4b91ea', primaryHoverBackgroundColor: '#4b91ea',
primaryHoverBorderColor: '#3483e7;', primaryHoverBorderColor: '#3483e7',
successBackgroundColor: '#27c24c', successBackgroundColor: '#27c24c',
successBorderColor: '#26be4a', successBorderColor: '#26be4a',
successHoverBackgroundColor: '#24b145', successHoverBackgroundColor: '#24b145',
successHoverBorderColor: '#1f9c3d;', successHoverBorderColor: '#1f9c3d',
warningBackgroundColor: '#ff902b', warningBackgroundColor: '#ff902b',
warningBorderColor: '#ff8d26', warningBorderColor: '#ff8d26',
warningHoverBackgroundColor: '#ff8517', warningHoverBackgroundColor: '#ff8517',
warningHoverBorderColor: '#fc7800;', warningHoverBorderColor: '#fc7800',
dangerBackgroundColor: '#f05050', dangerBackgroundColor: '#f05050',
dangerBorderColor: '#f04b4b', dangerBorderColor: '#f04b4b',
dangerHoverBackgroundColor: '#ee3d3d', dangerHoverBackgroundColor: '#ee3d3d',
dangerHoverBorderColor: '#ec2626;', dangerHoverBorderColor: '#ec2626',
iconButtonDisabledColor: '#7a7a7a', iconButtonDisabledColor: '#7a7a7a',
iconButtonHoverColor: '#666', iconButtonHoverColor: '#666',
+45 -46
View File
@@ -5,7 +5,7 @@
"scripts": { "scripts": {
"build": "webpack --config ./frontend/build/webpack.config.js", "build": "webpack --config ./frontend/build/webpack.config.js",
"prebuild": "yarn clean", "prebuild": "yarn clean",
"clean": "rimraf ./_output/UI && rimraf \"**/*.js.map\"", "clean": "rimraf ./_output/UI && rimraf -g \"**/*.js.map\"",
"start": "webpack --watch --config ./frontend/build/webpack.config.js", "start": "webpack --watch --config ./frontend/build/webpack.config.js",
"watch": "webpack --watch --config ./frontend/build/webpack.config.js", "watch": "webpack --watch --config ./frontend/build/webpack.config.js",
"lint": "eslint --config frontend/.eslintrc.js --ignore-path frontend/.eslintignore frontend/", "lint": "eslint --config frontend/.eslintrc.js --ignore-path frontend/.eslintignore frontend/",
@@ -26,36 +26,36 @@
"not chrome < 60" "not chrome < 60"
], ],
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "6.2.1", "@fortawesome/fontawesome-free": "6.4.0",
"@fortawesome/fontawesome-svg-core": "6.2.1", "@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-regular-svg-icons": "6.2.1", "@fortawesome/free-regular-svg-icons": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.2.1", "@fortawesome/free-solid-svg-icons": "6.4.0",
"@fortawesome/react-fontawesome": "0.2.0", "@fortawesome/react-fontawesome": "0.2.0",
"@juggle/resize-observer": "3.4.0", "@juggle/resize-observer": "3.4.0",
"@microsoft/signalr": "6.0.13", "@microsoft/signalr": "6.0.16",
"@sentry/browser": "7.28.0", "@sentry/browser": "7.46.0",
"@sentry/integrations": "7.28.0", "@sentry/integrations": "7.46.0",
"@types/jest": "29.2.5", "@types/jest": "29.5.0",
"@types/node": "18.11.18", "@types/node": "18.15.11",
"@types/react": "18.0.26", "@types/react": "18.0.31",
"@types/react-dom": "18.0.10", "@types/react-dom": "18.0.11",
"chart.js": "4.1.1", "chart.js": "4.2.1",
"classnames": "2.3.2", "classnames": "2.3.2",
"clipboard": "2.0.11", "clipboard": "2.0.11",
"connected-react-router": "6.9.3", "connected-react-router": "6.9.3",
"element-class": "0.2.2", "element-class": "0.2.2",
"filesize": "10.0.6", "filesize": "10.0.7",
"history": "4.10.1", "history": "4.10.1",
"https-browserify": "1.0.0", "https-browserify": "1.0.0",
"jdu": "1.0.0", "jdu": "1.0.0",
"jquery": "3.6.2", "jquery": "3.6.4",
"lodash": "4.17.21", "lodash": "4.17.21",
"mobile-detect": "1.4.5", "mobile-detect": "1.4.5",
"moment": "2.29.4", "moment": "2.29.4",
"mousetrap": "1.6.5", "mousetrap": "1.6.5",
"normalize.css": "8.0.1", "normalize.css": "8.0.1",
"prop-types": "15.8.1", "prop-types": "15.8.1",
"qs": "6.11.0", "qs": "6.11.1",
"react": "17.0.2", "react": "17.0.2",
"react-addons-shallow-compare": "15.6.3", "react-addons-shallow-compare": "15.6.3",
"react-async-script": "1.2.0", "react-async-script": "1.2.0",
@@ -67,7 +67,7 @@
"react-dnd-touch-backend": "14.1.1", "react-dnd-touch-backend": "14.1.1",
"react-document-title": "2.0.3", "react-document-title": "2.0.3",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-focus-lock": "2.9.2", "react-focus-lock": "2.9.4",
"react-google-recaptcha": "2.1.0", "react-google-recaptcha": "2.1.0",
"react-lazyload": "3.2.0", "react-lazyload": "3.2.0",
"react-measure": "1.4.7", "react-measure": "1.4.7",
@@ -77,78 +77,77 @@
"react-router-dom": "5.2.0", "react-router-dom": "5.2.0",
"react-text-truncate": "0.19.0", "react-text-truncate": "0.19.0",
"react-use-measure": "2.1.1", "react-use-measure": "2.1.1",
"react-virtualized": "9.21.1", "react-virtualized": "9.22.3",
"react-window": "1.8.8", "react-window": "1.8.8",
"redux": "4.2.0", "redux": "4.2.1",
"redux-actions": "2.6.5", "redux-actions": "2.6.5",
"redux-batched-actions": "0.5.0", "redux-batched-actions": "0.5.0",
"redux-localstorage": "0.4.1", "redux-localstorage": "0.4.1",
"redux-thunk": "2.4.2", "redux-thunk": "2.4.2",
"reselect": "4.1.7", "reselect": "4.1.7",
"stacktrace-js": "2.0.2", "stacktrace-js": "2.0.2",
"typescript": "4.9.4" "typescript": "5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.20.5", "@babel/core": "7.21.3",
"@babel/eslint-parser": "7.19.1", "@babel/eslint-parser": "7.21.3",
"@babel/plugin-proposal-class-properties": "7.18.6", "@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-decorators": "7.20.5", "@babel/plugin-proposal-decorators": "7.21.0",
"@babel/plugin-proposal-export-default-from": "7.18.10", "@babel/plugin-proposal-export-default-from": "7.18.10",
"@babel/plugin-proposal-export-namespace-from": "7.18.9", "@babel/plugin-proposal-export-namespace-from": "7.18.9",
"@babel/plugin-proposal-function-sent": "7.18.6", "@babel/plugin-proposal-function-sent": "7.18.6",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6", "@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
"@babel/plugin-proposal-numeric-separator": "7.18.6", "@babel/plugin-proposal-numeric-separator": "7.18.6",
"@babel/plugin-proposal-optional-chaining": "7.18.9", "@babel/plugin-proposal-optional-chaining": "7.21.0",
"@babel/plugin-proposal-throw-expressions": "7.18.6", "@babel/plugin-proposal-throw-expressions": "7.18.6",
"@babel/plugin-syntax-dynamic-import": "7.8.3", "@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.20.2", "@babel/preset-env": "7.20.2",
"@babel/preset-react": "7.18.6", "@babel/preset-react": "7.18.6",
"@babel/preset-typescript": "7.18.6", "@babel/preset-typescript": "7.21.0",
"@types/react-window": "1.8.5", "@types/react-window": "1.8.5",
"@typescript-eslint/eslint-plugin": "5.48.1", "@typescript-eslint/eslint-plugin": "5.57.0",
"@typescript-eslint/parser": "5.48.0", "@typescript-eslint/parser": "5.57.0",
"are-you-es5": "2.1.2", "are-you-es5": "2.1.2",
"autoprefixer": "10.4.13", "autoprefixer": "10.4.14",
"babel-eslint": "10.1.0", "babel-loader": "9.1.2",
"babel-loader": "9.1.0",
"babel-plugin-inline-classnames": "2.0.1", "babel-plugin-inline-classnames": "2.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24", "babel-plugin-transform-react-remove-prop-types": "0.4.24",
"core-js": "3.26.1", "core-js": "3.29.1",
"css-loader": "6.7.3", "css-loader": "6.7.3",
"css-modules-typescript-loader": "4.0.1", "css-modules-typescript-loader": "4.0.1",
"eslint": "8.30.0", "eslint": "8.37.0",
"eslint-config-prettier": "8.6.0", "eslint-config-prettier": "8.8.0",
"eslint-plugin-filenames": "1.3.2", "eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.26.0", "eslint-plugin-import": "2.27.5",
"eslint-plugin-prettier": "4.2.1", "eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.31.11", "eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-simple-import-sort": "8.0.0", "eslint-plugin-simple-import-sort": "10.0.0",
"file-loader": "6.2.0", "file-loader": "6.2.0",
"filemanager-webpack-plugin": "8.0.0", "filemanager-webpack-plugin": "8.0.0",
"fork-ts-checker-webpack-plugin": "7.2.14", "fork-ts-checker-webpack-plugin": "8.0.0",
"html-webpack-plugin": "5.5.0", "html-webpack-plugin": "5.5.0",
"loader-utils": "^3.2.1", "loader-utils": "^3.2.1",
"mini-css-extract-plugin": "2.7.2", "mini-css-extract-plugin": "2.7.5",
"postcss": "8.4.20", "postcss": "8.4.21",
"postcss-color-function": "4.1.0", "postcss-color-function": "4.1.0",
"postcss-loader": "7.0.2", "postcss-loader": "7.1.0",
"postcss-mixins": "9.0.4", "postcss-mixins": "9.0.4",
"postcss-nested": "6.0.0", "postcss-nested": "6.0.1",
"postcss-simple-vars": "7.0.1", "postcss-simple-vars": "7.0.1",
"postcss-url": "10.1.3", "postcss-url": "10.1.3",
"prettier": "2.8.2", "prettier": "2.8.7",
"require-nocache": "1.0.0", "require-nocache": "1.0.0",
"rimraf": "3.0.2", "rimraf": "4.4.1",
"run-sequence": "2.2.1", "run-sequence": "2.2.1",
"streamqueue": "1.1.2", "streamqueue": "1.1.2",
"style-loader": "3.3.1", "style-loader": "3.3.2",
"stylelint": "14.16.0", "stylelint": "14.16.0",
"stylelint-order": "5.0.0", "stylelint-order": "5.0.0",
"ts-loader": "9.4.2", "ts-loader": "9.4.2",
"typescript-plugin-css-modules": "4.1.1", "typescript-plugin-css-modules": "5.0.0",
"url-loader": "4.1.1", "url-loader": "4.1.1",
"webpack": "5.75.0", "webpack": "5.77.0",
"webpack-cli": "5.0.1", "webpack-cli": "5.0.1",
"webpack-livereload-plugin": "3.0.2" "webpack-livereload-plugin": "3.0.2"
} }
+2
View File
@@ -74,6 +74,8 @@
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<Deterministic Condition="$(AssemblyVersion.EndsWith('*'))">False</Deterministic> <Deterministic Condition="$(AssemblyVersion.EndsWith('*'))">False</Deterministic>
<PathMap>$(MSBuildProjectDirectory)=./$(MSBuildProjectName)/</PathMap>
</PropertyGroup> </PropertyGroup>
<!-- Set the AssemblyConfiguration attribute for projects --> <!-- Set the AssemblyConfiguration attribute for projects -->
@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using NUnit.Framework;
using NzbDrone.Common.Http;
namespace NzbDrone.Common.Test.Http
{
[TestFixture]
public class CookieUtilFixture
{
[Test]
public void CookieHeaderToDictionaryGood()
{
// valid cookies with non-alpha characters in the value
var cookieHeader = "__cfduid=d6237f041586694295; __cf_bm=TlOng/xyqckk-TMen38z+0RFYA7YA=";
var expectedCookieDictionary = new Dictionary<string, string>
{
{ "__cfduid", "d6237f041586694295" },
{ "__cf_bm", "TlOng/xyqckk-TMen38z+0RFYA7YA=" }
};
CollectionAssert.AreEqual(expectedCookieDictionary, CookieUtil.CookieHeaderToDictionary(cookieHeader));
}
[Test]
public void CookieHeaderToDictionaryDuplicateKeys()
{
// cookie with duplicate keys and whitespace separator instead of ;
var cookieHeader = "__cfduid=d6237f041586694295; __cf_bm=TlOng/xyqckk-TMen38z+0RFYA7YA= __cf_bm=test";
var expectedCookieDictionary = new Dictionary<string, string>
{
{ "__cfduid", "d6237f041586694295" },
{ "__cf_bm", "test" } // we always assume the latest value is the most recent
};
CollectionAssert.AreEqual(expectedCookieDictionary, CookieUtil.CookieHeaderToDictionary(cookieHeader));
}
[Test]
public void CookieHeaderToDictionaryMalformed()
{
// malformed cookies
var cookieHeader = "__cfduidd6237f041586694295; __cf_;bm TlOng; good_cookie=value";
var expectedCookieDictionary = new Dictionary<string, string> { { "good_cookie", "value" }, };
CollectionAssert.AreEqual(expectedCookieDictionary, CookieUtil.CookieHeaderToDictionary(cookieHeader));
}
[Test]
[SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")]
public void CookieHeaderToDictionaryNull()
{
// null cookie header
var expectedCookieDictionary = new Dictionary<string, string>();
CollectionAssert.AreEqual(expectedCookieDictionary, CookieUtil.CookieHeaderToDictionary(null));
}
[Test]
public void CookieDictionaryToHeaderGood()
{
// valid cookies with non-alpha characters in the value
var cookieDictionary = new Dictionary<string, string>
{
{ "__cfduid", "d6237f041586694295" },
{ "__cf_bm", "TlOng/xyqckk-TMen38z+0RFYA7YA=" }
};
var expectedCookieHeader = "__cfduid=d6237f041586694295; __cf_bm=TlOng/xyqckk-TMen38z+0RFYA7YA=";
CollectionAssert.AreEqual(expectedCookieHeader, CookieUtil.CookieDictionaryToHeader(cookieDictionary));
}
[Test]
public void CookieDictionaryToHeaderMalformed1()
{
// malformed key
var cookieDictionary = new Dictionary<string, string>
{
{ "__cf_=bm", "34234234" }
};
var ex = Assert.Throws<FormatException>(() => CookieUtil.CookieDictionaryToHeader(cookieDictionary));
Assert.AreEqual("The cookie '__cf_=bm=34234234' is malformed.", ex.Message);
}
[Test]
public void CookieDictionaryToHeaderMalformed2()
{
// malformed value
var cookieDictionary = new Dictionary<string, string>
{
{ "__cf_bm", "34234 234" }
};
var ex = Assert.Throws<FormatException>(() => CookieUtil.CookieDictionaryToHeader(cookieDictionary));
Assert.AreEqual("The cookie '__cf_bm=34234 234' is malformed.", ex.Message);
}
[Test]
public void CookieDictionaryToHeaderNull()
{
// null cookie dictionary
var expectedCookieHeader = "";
CollectionAssert.AreEqual(expectedCookieHeader, CookieUtil.CookieDictionaryToHeader(null));
}
}
}
@@ -5,8 +5,6 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Text;
using System.Threading.Tasks;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common.Composition namespace NzbDrone.Common.Composition
@@ -19,16 +17,17 @@ namespace NzbDrone.Common.Composition
RegisterSQLiteResolver(); RegisterSQLiteResolver();
} }
public static IEnumerable<Assembly> Load(IEnumerable<string> assemblies) public static IList<Assembly> Load(IList<string> assemblyNames)
{ {
var toLoad = assemblies.ToList(); var toLoad = assemblyNames.ToList();
toLoad.Add("Prowlarr.Common"); toLoad.Add("Prowlarr.Common");
toLoad.Add(OsInfo.IsWindows ? "Prowlarr.Windows" : "Prowlarr.Mono"); toLoad.Add(OsInfo.IsWindows ? "Prowlarr.Windows" : "Prowlarr.Mono");
var startupPath = AppDomain.CurrentDomain.BaseDirectory; var startupPath = AppDomain.CurrentDomain.BaseDirectory;
return toLoad.Select(x => return toLoad
AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(startupPath, $"{x}.dll"))); .Select(x => AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(startupPath, $"{x}.dll")))
.ToList();
} }
private static Assembly ContainerResolveEventHandler(object sender, ResolveEventArgs args) private static Assembly ContainerResolveEventHandler(object sender, ResolveEventArgs args)
+2 -3
View File
@@ -351,7 +351,7 @@ namespace NzbDrone.Common.Disk
} }
} }
public string GetPathRoot(string path) public virtual string GetPathRoot(string path)
{ {
Ensure.That(path, () => path).IsValidPath(); Ensure.That(path, () => path).IsValidPath();
@@ -478,8 +478,7 @@ namespace NzbDrone.Common.Disk
return mounts.Where(drive => drive.RootDirectory.PathEquals(path) || return mounts.Where(drive => drive.RootDirectory.PathEquals(path) ||
drive.RootDirectory.IsParentPath(path)) drive.RootDirectory.IsParentPath(path))
.OrderByDescending(drive => drive.RootDirectory.Length) .MaxBy(drive => drive.RootDirectory.Length);
.FirstOrDefault();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -16,18 +16,7 @@ namespace NzbDrone.Common.Extensions
return false; return false;
} }
Uri uri; return Uri.TryCreate(path, UriKind.Absolute, out var uri) && uri.IsWellFormedOriginalString();
if (!Uri.TryCreate(path, UriKind.Absolute, out uri))
{
return false;
}
if (!uri.IsWellFormedOriginalString())
{
return false;
}
return true;
} }
} }
} }
+2 -2
View File
@@ -9,7 +9,7 @@ namespace NzbDrone.Common.Http
{ {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
// NOTE: we are not checking non-ascii characters and we should // NOTE: we are not checking non-ascii characters and we should
private static readonly Regex _CookieRegex = new Regex(@"([^\(\)<>@,;:\\""/\[\]\?=\{\}\s]+)=([^,;\\""\s]+)"); private static readonly Regex CookieRegex = new (@"([^\(\)<>@,;:\\""/\[\]\?=\{\}\s]+)=([^,;\\""\s]+)");
private static readonly string[] FilterProps = { "COMMENT", "COMMENTURL", "DISCORD", "DOMAIN", "EXPIRES", "MAX-AGE", "PATH", "PORT", "SECURE", "VERSION", "HTTPONLY", "SAMESITE" }; private static readonly string[] FilterProps = { "COMMENT", "COMMENTURL", "DISCORD", "DOMAIN", "EXPIRES", "MAX-AGE", "PATH", "PORT", "SECURE", "VERSION", "HTTPONLY", "SAMESITE" };
private static readonly char[] InvalidKeyChars = { '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t', '\n' }; private static readonly char[] InvalidKeyChars = { '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t', '\n' };
private static readonly char[] InvalidValueChars = { '"', ',', ';', '\\', ' ', '\t', '\n' }; private static readonly char[] InvalidValueChars = { '"', ',', ';', '\\', ' ', '\t', '\n' };
@@ -22,7 +22,7 @@ namespace NzbDrone.Common.Http
return cookieDictionary; return cookieDictionary;
} }
var matches = _CookieRegex.Match(cookieHeader); var matches = CookieRegex.Match(cookieHeader);
while (matches.Success) while (matches.Success)
{ {
if (matches.Groups.Count > 2 && !FilterProps.Contains(matches.Groups[1].Value.ToUpperInvariant())) if (matches.Groups.Count > 2 && !FilterProps.Contains(matches.Groups[1].Value.ToUpperInvariant()))
@@ -21,6 +21,7 @@ namespace NzbDrone.Common.Http
public Dictionary<string, string> Segments { get; private set; } public Dictionary<string, string> Segments { get; private set; }
public HttpHeader Headers { get; private set; } public HttpHeader Headers { get; private set; }
public bool SuppressHttpError { get; set; } public bool SuppressHttpError { get; set; }
public IEnumerable<HttpStatusCode> SuppressHttpErrorStatusCodes { get; set; }
public bool LogHttpError { get; set; } public bool LogHttpError { get; set; }
public bool UseSimplifiedUserAgent { get; set; } public bool UseSimplifiedUserAgent { get; set; }
public bool AllowAutoRedirect { get; set; } public bool AllowAutoRedirect { get; set; }
@@ -108,6 +109,7 @@ namespace NzbDrone.Common.Http
request.Method = Method; request.Method = Method;
request.Encoding = Encoding; request.Encoding = Encoding;
request.SuppressHttpError = SuppressHttpError; request.SuppressHttpError = SuppressHttpError;
request.SuppressHttpErrorStatusCodes = SuppressHttpErrorStatusCodes;
request.LogHttpError = LogHttpError; request.LogHttpError = LogHttpError;
request.UseSimplifiedUserAgent = UseSimplifiedUserAgent; request.UseSimplifiedUserAgent = UseSimplifiedUserAgent;
request.AllowAutoRedirect = AllowAutoRedirect; request.AllowAutoRedirect = AllowAutoRedirect;
@@ -14,14 +14,14 @@ namespace NzbDrone.Common.OAuth
{ {
get get
{ {
var parameters = this.Where(p => p.Name.Equals(name)); var parameters = this.Where(p => p.Name.Equals(name)).ToArray();
if (!parameters.Any()) if (!parameters.Any())
{ {
return null; return null;
} }
if (parameters.Count() == 1) if (parameters.Length == 1)
{ {
return parameters.Single(); return parameters.Single();
} }
+4 -4
View File
@@ -4,16 +4,16 @@
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants> <DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.3.3" /> <PackageReference Include="DryIoc.dll" Version="5.3.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.1.0" /> <PackageReference Include="NLog" Version="5.1.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.0" /> <PackageReference Include="NLog.Extensions.Logging" Version="5.2.0" />
<PackageReference Include="Npgsql" Version="5.0.11" /> <PackageReference Include="Npgsql" Version="5.0.11" />
<PackageReference Include="Sentry" Version="3.24.1" /> <PackageReference Include="Sentry" Version="3.29.1" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" /> <PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="SharpZipLib" Version="1.3.3" /> <PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" /> <PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" /> <PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
@@ -0,0 +1,826 @@
{
"Results": 9999,
"Pagination": {
"Current": 1,
"Max": 99,
"Limit": {
"Min": 15,
"Coerced": 15,
"Max": 50
}
},
"Matches": 2,
"Groups": [
{
"ID": 575,
"CategoryName": "Anime",
"FullName": "Cowboy Bebop: Tengoku no Tobira - Movie&nbsp;&nbsp;[2001]",
"GroupName": "Movie",
"SeriesID": "141",
"SeriesName": "Cowboy Bebop: Tengoku no Tobira",
"Artists": null,
"Year": "2001",
"Image": "https://mei.animebytes.tv/2bac1a04148be77ce41251d3cb44bbd5.jpg",
"Synonymns": [
"カウボーイビバップ天国の扉",
"Cowboy Bebop: Knockin' on Heaven's Door"
],
"SynonymnsV2": {
"Japanese": "カウボーイビバップ天国の扉",
"Romaji": "",
"Alternative": "Cowboy Bebop: Knockin' on Heaven's Door"
},
"Snatched": 4900,
"Comments": 11,
"Links": {
"AniDB": "https://anidb.net/anime/219",
"ANN": "https://www.animenewsnetwork.com/encyclopedia/anime.php?id=353",
"Wikipedia": "https://en.wikipedia.org/wiki/Cowboy_Bebop:_The_Movie",
"MAL": "https://myanimelist.net/anime/5"
},
"Votes": 572,
"AvgVote": 8.3,
"Associations": null,
"Description": "Mars is under siege! Just before Halloween 2071, a terrorist bomb destroys a tanker truck on Highway One, close to the densely-populated crater city. There are casualties up to half a mile from the blast — 500 killed or injured by what appears to be a biochemical weapon. The reward for the bomber's capture is a massive 300,000,000 woolongs... and there are four humans and a dog who really need the money. Down on their luck as usual, the crew of the Bebop get on the case.\r\n\r\n[i]Note: The movie takes place between (in the time period of) the Cowboy Bebop episodes 22 and 23.[/i]",
"DescriptionHTML": "Mars is under siege! Just before Halloween 2071, a terrorist bomb destroys a tanker truck on Highway One, close to the densely-populated crater city. There are casualties up to half a mile from the blast — 500 killed or injured by what appears to be a biochemical weapon. The reward for the bomber&#039;s capture is a massive 300,000,000 woolongs... and there are four humans and a dog who really need the money. Down on their luck as usual, the crew of the Bebop get on the case.<br />\r\n<br />\r\n<em>Note: The movie takes place between (in the time period of) the Cowboy Bebop episodes 22 and 23.</em>",
"EpCount": 0,
"StudioList": "Sunrise///28|BONES///35",
"PastWeek": 2,
"Incomplete": false,
"Ongoing": false,
"Tags": [
"adventure",
"comedy",
"drama",
"scifi",
"seinen",
"action"
],
"Torrents": [
{
"ID": 959397,
"EditionData": {
"EditionTitle": ""
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/959397/download/somepass",
"Property": "Blu-ray | MKV | h265 10-bit | 1080p | Opus 5.1 | Softsubs (Polarwindz) | Freeleech",
"Snatched": 16,
"Seeders": 5,
"Leechers": 1,
"Status": 0,
"Size": 13090646841,
"FileCount": 1,
"FileList": [
{
"filename": "[Polarwindz] Cowboy Bebop The Movie - Knockin' on Heaven's Door [BD 1080p x265 10bit Opus 5.1].mkv",
"size": 13090646841
}
],
"UploadTime": "2023-04-02 05:00:43"
},
{
"ID": 909565,
"EditionData": {
"EditionTitle": ""
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/909565/download/somepass",
"Property": "Blu-ray | MKV | h265 10-bit | 1080p | Opus 5.1 | Dual Audio | Softsubs (Yūrei) | Freeleech",
"Snatched": 29,
"Seeders": 6,
"Leechers": 0,
"Status": 0,
"Size": 15717521349,
"FileCount": 1,
"FileList": [
{
"filename": "Cowboy Bebop - Tengoku no Tobira.mkv",
"size": 15717521349
}
],
"UploadTime": "2020-09-03 18:04:38"
}
]
},
{
"ID": 2709,
"CategoryName": "Anime",
"FullName": "BLEACH - TV Series&nbsp;&nbsp;[2004]",
"GroupName": "TV Series",
"SeriesID": "191",
"SeriesName": "BLEACH",
"Artists": null,
"Year": "2004",
"Image": "https://mei.animebytes.tv/997c8ec3ca0e70254b182b0a176f0161.jpg",
"Synonymns": [
"ブリーチ"
],
"SynonymnsV2": {
"Japanese": "ブリーチ",
"Romaji": "",
"Alternative": ""
},
"Snatched": 22653,
"Comments": 51,
"Links": {
"AniDB": "https://anidb.net/anime/2369",
"ANN": "https://www.animenewsnetwork.com/encyclopedia/anime.php?id=4240",
"Wikipedia": "https://en.wikipedia.org/wiki/Bleach_(anime)",
"MAL": "https://myanimelist.net/anime/269"
},
"Votes": 530,
"AvgVote": 7.3,
"Associations": null,
"Description": "Kurosaki Ichigo is a teenager gifted with the ability to see spirits. His life is drastically changed by the sudden appearance of a Shinigami (literally, Death god) - one who governs the flow of souls between the human world and the afterlife - named Kuchiki Rukia, who arrives in search of a Hollow, a dangerous lost soul. When Rukia is severely wounded while trying to defeat the Hollow, she attempts to transfer half of her Reiatsu (literally, Spiritual pressure) energy to Ichigo so that he can defeat the Hollow. However, Ichigo takes almost all of her energy, transforming into a Shinigami and allowing him to defeat the Hollow with ease. With her powers diminished, Rukia is left stranded in the human world until she can recover her strength. In the meantime, Ichigo must take over Rukia's role as a Shinigami, battling Hollows and guiding souls to the afterlife realm known as the Soul Society.",
"DescriptionHTML": "Kurosaki Ichigo is a teenager gifted with the ability to see spirits. His life is drastically changed by the sudden appearance of a Shinigami (literally, Death god) - one who governs the flow of souls between the human world and the afterlife - named Kuchiki Rukia, who arrives in search of a Hollow, a dangerous lost soul. When Rukia is severely wounded while trying to defeat the Hollow, she attempts to transfer half of her Reiatsu (literally, Spiritual pressure) energy to Ichigo so that he can defeat the Hollow. However, Ichigo takes almost all of her energy, transforming into a Shinigami and allowing him to defeat the Hollow with ease. With her powers diminished, Rukia is left stranded in the human world until she can recover her strength. In the meantime, Ichigo must take over Rukia&#039;s role as a Shinigami, battling Hollows and guiding souls to the afterlife realm known as the Soul Society.",
"EpCount": 366,
"StudioList": "Studio Pierrot///45",
"PastWeek": 26,
"Incomplete": false,
"Ongoing": false,
"Tags": [
"adventure",
"comedy",
"fantasy",
"martial.arts",
"school.life",
"shounen",
"super.power",
"contemporary.fantasy",
"swordplay",
"action",
"supernatural"
],
"Torrents": [
{
"ID": 1031199,
"EditionData": {
"EditionTitle": "Season 02: The Entry (021-041)"
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/1031199/download/somepass",
"Property": "Blu-ray | MKV | h265 10-bit | 1080p | AAC 2.0 | Dual Audio | Softsubs (GHOST) | Freeleech",
"Snatched": 20,
"Seeders": 24,
"Leechers": 0,
"Status": 0,
"Size": 19584943785,
"FileCount": 21,
"FileList": [
{
"filename": "[GHOST][1080p] Bleach - 021 [BD HEVC 10bit Dual Audio AC3][035452C3].mkv",
"size": 880693454
},
{
"filename": "[GHOST][1080p] Bleach - 022 [BD HEVC 10bit Dual Audio AC3][0E923AAD].mkv",
"size": 851531918
},
{
"filename": "[GHOST][1080p] Bleach - 023 [BD HEVC 10bit Dual Audio AC3][604A0EC6].mkv",
"size": 882518038
},
{
"filename": "[GHOST][1080p] Bleach - 024 [BD HEVC 10bit Dual Audio AC3][ABABE9B3].mkv",
"size": 837335522
},
{
"filename": "[GHOST][1080p] Bleach - 025 [BD HEVC 10bit Dual Audio AC3][17AEB0C1].mkv",
"size": 933034706
},
{
"filename": "[GHOST][1080p] Bleach - 026 [BD HEVC 10bit Dual Audio AC3][E6F0017C].mkv",
"size": 891916996
},
{
"filename": "[GHOST][1080p] Bleach - 027 [BD HEVC 10bit Dual Audio AC3][317791C7].mkv",
"size": 896856044
},
{
"filename": "[GHOST][1080p] Bleach - 028 [BD HEVC 10bit Dual Audio AC3][5CA4DF06].mkv",
"size": 1010510386
},
{
"filename": "[GHOST][1080p] Bleach - 029 [BD HEVC 10bit Dual Audio AC3][549CF25A].mkv",
"size": 995648398
},
{
"filename": "[GHOST][1080p] Bleach - 030 [BD HEVC 10bit Dual Audio AC3][CED479F9].mkv",
"size": 991633973
},
{
"filename": "[GHOST][1080p] Bleach - 031 [BD HEVC 10bit Dual Audio AC3][1EEFAB0E].mkv",
"size": 950195341
},
{
"filename": "[GHOST][1080p] Bleach - 032 [BD HEVC 10bit Dual Audio AC3][C6F3C39A].mkv",
"size": 859218784
},
{
"filename": "[GHOST][1080p] Bleach - 033 [BD HEVC 10bit Dual Audio AC3][A8424897].mkv",
"size": 933230823
},
{
"filename": "[GHOST][1080p] Bleach - 034 [BD HEVC 10bit Dual Audio AC3][96C94DB8].mkv",
"size": 818179634
},
{
"filename": "[GHOST][1080p] Bleach - 035 [BD HEVC 10bit Dual Audio AC3][A07831AE].mkv",
"size": 882457138
},
{
"filename": "[GHOST][1080p] Bleach - 036 [BD HEVC 10bit Dual Audio AC3][D984C169].mkv",
"size": 895683290
},
{
"filename": "[GHOST][1080p] Bleach - 037 [BD HEVC 10bit Dual Audio AC3][32C05A7E].mkv",
"size": 970033716
},
{
"filename": "[GHOST][1080p] Bleach - 038 [BD HEVC 10bit Dual Audio AC3][824B3EEC].mkv",
"size": 956573899
},
{
"filename": "[GHOST][1080p] Bleach - 039 [BD HEVC 10bit Dual Audio AC3][A45233DF].mkv",
"size": 1238240707
},
{
"filename": "[GHOST][1080p] Bleach - 040 [BD HEVC 10bit Dual Audio AC3][5334A7F1].mkv",
"size": 894491562
},
{
"filename": "[GHOST][1080p] Bleach - 041 [BD HEVC 10bit Dual Audio AC3][A4F17363].mkv",
"size": 1014959456
}
],
"UploadTime": "2023-03-03 03:03:17"
},
{
"ID": 1031203,
"EditionData": {
"EditionTitle": "Season 03: The Rescue (042-063)"
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/1031203/download/somepass",
"Property": "Blu-ray | MKV | h265 10-bit | 1080p | AC3 2.0 | Dual Audio | Softsubs (GHOST) | Freeleech",
"Snatched": 6,
"Seeders": 12,
"Leechers": 2,
"Status": 0,
"Size": 24498538059,
"FileCount": 22,
"FileList": [
{
"filename": "[GHOST][1080p] Bleach - 042 [BD HEVC 10bit Dual Audio AC3][55763BF6].mkv",
"size": 899825828
},
{
"filename": "[GHOST][1080p] Bleach - 043 [BD HEVC 10bit Dual Audio AC3][70B71ECC].mkv",
"size": 902256094
},
{
"filename": "[GHOST][1080p] Bleach - 044 [BD HEVC 10bit Dual Audio AC3][35F5526B].mkv",
"size": 885323344
},
{
"filename": "[GHOST][1080p] Bleach - 045 [BD HEVC 10bit Dual Audio AC3][9C1DAE4E].mkv",
"size": 1082465042
},
{
"filename": "[GHOST][1080p] Bleach - 046 [BD HEVC 10bit Dual Audio AC3][869EF5B6].mkv",
"size": 957433930
},
{
"filename": "[GHOST][1080p] Bleach - 047 [BD HEVC 10bit Dual Audio AC3][890DA7CE].mkv",
"size": 997124540
},
{
"filename": "[GHOST][1080p] Bleach - 048 [BD HEVC 10bit Dual Audio AC3][39064E08].mkv",
"size": 1026751383
},
{
"filename": "[GHOST][1080p] Bleach - 049 [BD HEVC 10bit Dual Audio AC3][C536D3DB].mkv",
"size": 994918391
},
{
"filename": "[GHOST][1080p] Bleach - 050 [BD HEVC 10bit Dual Audio AC3][A7A0CB24].mkv",
"size": 1141146920
},
{
"filename": "[GHOST][1080p] Bleach - 051 [BD HEVC 10bit Dual Audio AC3][25C06D9D].mkv",
"size": 951751791
},
{
"filename": "[GHOST][1080p] Bleach - 052 [BD HEVC 10bit Dual Audio AC3][FB506194].mkv",
"size": 1131756065
},
{
"filename": "[GHOST][1080p] Bleach - 053 [BD HEVC 10bit Dual Audio AC3][4A76C66D].mkv",
"size": 1076952986
},
{
"filename": "[GHOST][1080p] Bleach - 054 [BD HEVC 10bit Dual Audio AC3][51D8E5F8].mkv",
"size": 1369454462
},
{
"filename": "[GHOST][1080p] Bleach - 055 [BD HEVC 10bit Dual Audio AC3][DCF20007].mkv",
"size": 1428073116
},
{
"filename": "[GHOST][1080p] Bleach - 056 [BD HEVC 10bit Dual Audio AC3][34A28687].mkv",
"size": 1304804717
},
{
"filename": "[GHOST][1080p] Bleach - 057 [BD HEVC 10bit Dual Audio AC3][D1D5FE29].mkv",
"size": 1056220730
},
{
"filename": "[GHOST][1080p] Bleach - 058 [BD HEVC 10bit Dual Audio AC3][C6EAC278].mkv",
"size": 1551455953
},
{
"filename": "[GHOST][1080p] Bleach - 059 [BD HEVC 10bit Dual Audio AC3][E7B25869].mkv",
"size": 1026910923
},
{
"filename": "[GHOST][1080p] Bleach - 060 [BD HEVC 10bit Dual Audio AC3][C9D257D4].mkv",
"size": 1059631177
},
{
"filename": "[GHOST][1080p] Bleach - 061 [BD HEVC 10bit Dual Audio AC3][0521C8D3].mkv",
"size": 1457893287
},
{
"filename": "[GHOST][1080p] Bleach - 062 [BD HEVC 10bit Dual Audio AC3][65CBA616].mkv",
"size": 1262863946
},
{
"filename": "[GHOST][1080p] Bleach - 063 [BD HEVC 10bit Dual Audio AC3][CF63E244].mkv",
"size": 933523434
}
],
"UploadTime": "2023-04-03 03:14:35"
}
]
},
{
"ID": 81926,
"CategoryName": "Anime",
"FullName": "Dr. STONE: NEW WORLD - TV Series&nbsp;&nbsp;[2023]",
"GroupName": "TV Series",
"SeriesID": "79217",
"SeriesName": "Dr. STONE: NEW WORLD",
"Artists": null,
"Year": "2023",
"Image": "https://mei.animebytes.tv/Tu0p0k56514.jpg",
"Synonymns": {
"0": "ドクターストーン NEW WORLD",
"2": "Dr. STONE S3, Dr. STONE Season 3, Dr.STONE 3rd Season"
},
"SynonymnsV2": {
"Japanese": "ドクターストーン NEW WORLD",
"Romaji": "",
"Alternative": "Dr. STONE S3, Dr. STONE Season 3, Dr.STONE 3rd Season"
},
"Snatched": 1870,
"Comments": 0,
"Links": {
"AniDB": "https://anidb.net/anime/17053",
"ANN": "https://www.animenewsnetwork.com/encyclopedia/anime.php?id=25068",
"Wikipedia": "https://en.wikipedia.org/wiki/Dr._Stone",
"MAL": "https://myanimelist.net/anime/48549"
},
"Votes": 0,
"AvgVote": 0,
"Associations": null,
"Description": "Third season of [i]Dr. STONE[/i].\r\n\r\nWith the Stone Wars over, the former members of Tsukasa's Empire of Might join forces with the Kingdom of Science to build a ship capable of sailing across the open ocean to seek answers to the mystery of global petrification. However, before they can begin their voyage Senku and his friends need to find some key resources and push some new scientific advancements to build the type of vessel they need.\r\n\r\n* Based on an adventure sci-fi shounen manga series written by Inagaki Riichirou and illustrated by Boichi.\r\n\r\n[i]Note: The first episode received an early screening at a special event on March 12, 2023 at Iino Hall in Tokyo. The regular TV broadcast started on April 6, 2023.[/i]",
"DescriptionHTML": "Third season of <em>Dr. STONE</em>.<br />\r\n<br />\r\nWith the Stone Wars over, the former members of Tsukasa&#039;s Empire of Might join forces with the Kingdom of Science to build a ship capable of sailing across the open ocean to seek answers to the mystery of global petrification. However, before they can begin their voyage Senku and his friends need to find some key resources and push some new scientific advancements to build the type of vessel they need.<br />\r\n<br />\r\n* Based on an adventure sci-fi shounen manga series written by Inagaki Riichirou and illustrated by Boichi.<br />\r\n<br />\r\n<em>Note: The first episode received an early screening at a special event on March 12, 2023 at Iino Hall in Tokyo. The regular TV broadcast started on April 6, 2023.</em>",
"EpCount": 0,
"StudioList": "TMS Entertainment///11",
"PastWeek": 6,
"Incomplete": false,
"Ongoing": false,
"Tags": [
"adventure",
"scifi",
"shounen",
"post.apocalyptic"
],
"Torrents": [
{
"ID": 1041495,
"EditionData": {
"EditionTitle": "Episode 3"
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/1041495/download/somepass",
"Property": "Web | MKV | h264 | 720p | AAC 2.0 | Softsubs (SubsPlease) | Episode 3 | Freeleech",
"Snatched": 165,
"Seeders": 137,
"Leechers": 3,
"Status": 0,
"Size": 748209543,
"FileCount": 1,
"FileList": [
{
"filename": "[SubsPlease] Dr. Stone S3 - 03 (720p) [DAC92E18].mkv",
"size": 748209543
}
],
"UploadTime": "2023-04-20 14:32:29"
},
{
"ID": 1037731,
"EditionData": {
"EditionTitle": "Episode 2"
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/1037731/download/somepass",
"Property": "Web | MKV | h264 | 720p | AAC 2.0 | Softsubs (SubsPlease) | Episode 2 | Freeleech",
"Snatched": 174,
"Seeders": 122,
"Leechers": 1,
"Status": 0,
"Size": 748808730,
"FileCount": 1,
"FileList": [
{
"filename": "[SubsPlease] Dr. Stone S3 - 02 (720p) [AE2DA9AB].mkv",
"size": 748808730
}
],
"UploadTime": "2023-04-13 14:34:16"
}
]
},
{
"ID": 69267,
"CategoryName": "Anime",
"FullName": "Dr. STONE: STONE WARS - TV Series&nbsp;&nbsp;[2021]",
"GroupName": "TV Series",
"SeriesID": "67161",
"SeriesName": "Dr. STONE: STONE WARS",
"Artists": null,
"Year": "2021",
"Image": "https://mei.animebytes.tv/6pqXEK82OfD.jpg",
"Synonymns": {
"0": "ドクターストーン STONE WARS",
"2": "Dr. STONE S2, Dr. STONE Season 2, Dr.STONE 2nd Season"
},
"SynonymnsV2": {
"Japanese": "ドクターストーン STONE WARS",
"Romaji": "",
"Alternative": "Dr. STONE S2, Dr. STONE Season 2, Dr.STONE 2nd Season"
},
"Snatched": 1181,
"Comments": 4,
"Links": {
"AniDB": "https://anidb.net/anime/15305",
"ANN": "https://www.animenewsnetwork.com/encyclopedia/anime.php?id=22942",
"Wikipedia": "https://en.wikipedia.org/wiki/Dr._Stone",
"MAL": "https://myanimelist.net/anime/40852/Dr_Stone__Stone_Wars"
},
"Votes": 23,
"AvgVote": 7.8,
"Associations": null,
"Description": "* Based on an adventure sci-fi shounen manga series written by Inagaki Riichirou and illustrated by Boichi.\r\n\r\nSenkuu, Chrome and the other villagers are in a battle of wits and brawn against the Tsukasa Empire after the revelation that Senkuu's father left behind a lasting message.\r\n\r\nWith a plan to build a phone that could be used to take down the bad guys from within, Senkuu will need to finally enlist the help of his friends who are a part of the Tsukasa Empire.",
"DescriptionHTML": "* Based on an adventure sci-fi shounen manga series written by Inagaki Riichirou and illustrated by Boichi.<br />\r\n<br />\r\nSenkuu, Chrome and the other villagers are in a battle of wits and brawn against the Tsukasa Empire after the revelation that Senkuu&#039;s father left behind a lasting message.<br />\r\n<br />\r\nWith a plan to build a phone that could be used to take down the bad guys from within, Senkuu will need to finally enlist the help of his friends who are a part of the Tsukasa Empire.",
"EpCount": 11,
"StudioList": "TMS Entertainment///11",
"PastWeek": 0,
"Incomplete": false,
"Ongoing": false,
"Tags": [
"adventure",
"scifi",
"shounen",
"post.apocalyptic"
],
"Torrents": [
{
"ID": 944509,
"EditionData": {
"EditionTitle": ""
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/944509/download/somepass",
"Property": "Web | MKV | h264 | 1080p | AAC 2.0 | Dual Audio | Softsubs (-ZR-) | Freeleech",
"Snatched": 188,
"Seeders": 31,
"Leechers": 1,
"Status": 0,
"Size": 16611719364,
"FileCount": 11,
"FileList": [
{
"filename": "Dr. Stone S02E01v2 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1512195256
},
{
"filename": "Dr. Stone S02E02 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1507917714
},
{
"filename": "Dr. Stone S02E03 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1510054199
},
{
"filename": "Dr. Stone S02E04 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1507100461
},
{
"filename": "Dr. Stone S02E05 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1507258273
},
{
"filename": "Dr. Stone S02E06 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1511039711
},
{
"filename": "Dr. Stone S02E07 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1507219047
},
{
"filename": "Dr. Stone S02E08 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1510996213
},
{
"filename": "Dr. Stone S02E09 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1512785600
},
{
"filename": "Dr. Stone S02E10 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1511889715
},
{
"filename": "Dr. Stone S02E11 2021 1080p WEB-DL AVC AAC 2.0 Dual Audio -ZR-.mkv",
"size": 1513263175
}
],
"UploadTime": "2021-06-03 20:30:00"
}
]
},
{
"ID": 60598,
"CategoryName": "Anime",
"FullName": "Dr. STONE - TV Series&nbsp;&nbsp;[2019]",
"GroupName": "TV Series",
"SeriesID": "56617",
"SeriesName": "Dr. STONE",
"Artists": null,
"Year": "2019",
"Image": "https://mei.animebytes.tv/SaFez5XG8T3.jpg",
"Synonymns": [
"Dr.STONE [ドクターストーン]"
],
"SynonymnsV2": {
"Japanese": "Dr.STONE [ドクターストーン]",
"Romaji": "",
"Alternative": ""
},
"Snatched": 2174,
"Comments": 8,
"Links": {
"AniDB": "https://anidb.net/anime/14491",
"ANN": "https://www.animenewsnetwork.com/encyclopedia/anime.php?id=21703",
"Wikipedia": "https://en.wikipedia.org/wiki/Dr._Stone",
"MAL": "https://myanimelist.net/anime/38691"
},
"Votes": 68,
"AvgVote": 7.9,
"Associations": null,
"Description": "Several thousand years after a mysterious phenomenon that turns all of humanity to stone, the extraordinarily intelligent, science-driven boy, Senku Ishigami, awakens. Facing a world of stone and the total collapse of civilization, Senku makes up his mind to use science to rebuild the world. Starting with his super strong childhood friend Taiju Oki, who awakened at the same time, they will begin to rebuild civilization from nothing... Depicting two million years of scientific history from the Stone Age to present day, the unprecedented crafting adventure story is about to begin!\r\n\r\n* Based on an adventure sci-fi shounen manga series written by Inagaki Riichirou and illustrated by Boichi.",
"DescriptionHTML": "Several thousand years after a mysterious phenomenon that turns all of humanity to stone, the extraordinarily intelligent, science-driven boy, Senku Ishigami, awakens. Facing a world of stone and the total collapse of civilization, Senku makes up his mind to use science to rebuild the world. Starting with his super strong childhood friend Taiju Oki, who awakened at the same time, they will begin to rebuild civilization from nothing... Depicting two million years of scientific history from the Stone Age to present day, the unprecedented crafting adventure story is about to begin!<br />\r\n<br />\r\n* Based on an adventure sci-fi shounen manga series written by Inagaki Riichirou and illustrated by Boichi.",
"EpCount": 24,
"StudioList": "TMS Entertainment///11|8PAN///6344",
"PastWeek": 0,
"Incomplete": false,
"Ongoing": false,
"Tags": [
"adventure",
"comedy",
"scifi",
"shounen"
],
"Torrents": [
{
"ID": 430074,
"EditionData": {
"EditionTitle": ""
},
"RawDownMultiplier": 1,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/430074/download/somepass",
"Property": "Web | MKV | h264 | 720p | AAC 2.0 | Softsubs (HorribleSubs)",
"Snatched": 108,
"Seeders": 33,
"Leechers": 1,
"Status": 0,
"Size": 16366224176,
"FileCount": 24,
"FileList": [
{
"filename": "[HorribleSubs] Dr. Stone - 01 [720p].mkv",
"size": 477027555
},
{
"filename": "[HorribleSubs] Dr. Stone - 02 [720p].mkv",
"size": 489436551
},
{
"filename": "[HorribleSubs] Dr. Stone - 03 [720p].mkv",
"size": 503786828
},
{
"filename": "[HorribleSubs] Dr. Stone - 04 [720p].mkv",
"size": 442977598
},
{
"filename": "[HorribleSubs] Dr. Stone - 05 [720p].mkv",
"size": 523531555
},
{
"filename": "[HorribleSubs] Dr. Stone - 06 [720p].mkv",
"size": 506742468
},
{
"filename": "[HorribleSubs] Dr. Stone - 07 [720p].mkv",
"size": 746577276
},
{
"filename": "[HorribleSubs] Dr. Stone - 08 [720p].mkv",
"size": 745942485
},
{
"filename": "[HorribleSubs] Dr. Stone - 09 [720p].mkv",
"size": 746035250
},
{
"filename": "[HorribleSubs] Dr. Stone - 10 [720p].mkv",
"size": 746001386
},
{
"filename": "[HorribleSubs] Dr. Stone - 11 [720p].mkv",
"size": 746155088
},
{
"filename": "[HorribleSubs] Dr. Stone - 12 [720p].mkv",
"size": 746560710
},
{
"filename": "[HorribleSubs] Dr. Stone - 13 [720p].mkv",
"size": 745880614
},
{
"filename": "[HorribleSubs] Dr. Stone - 14 [720p].mkv",
"size": 744563919
},
{
"filename": "[HorribleSubs] Dr. Stone - 15 [720p].mkv",
"size": 745303312
},
{
"filename": "[HorribleSubs] Dr. Stone - 16 [720p].mkv",
"size": 746850910
},
{
"filename": "[HorribleSubs] Dr. Stone - 17 [720p].mkv",
"size": 744188496
},
{
"filename": "[HorribleSubs] Dr. Stone - 18 [720p].mkv",
"size": 746212236
},
{
"filename": "[HorribleSubs] Dr. Stone - 19 [720p].mkv",
"size": 744840131
},
{
"filename": "[HorribleSubs] Dr. Stone - 20 [720p].mkv",
"size": 746380081
},
{
"filename": "[HorribleSubs] Dr. Stone - 21 [720p].mkv",
"size": 744975636
},
{
"filename": "[HorribleSubs] Dr. Stone - 22 [720p].mkv",
"size": 746214757
},
{
"filename": "[HorribleSubs] Dr. Stone - 23 [720p].mkv",
"size": 744924693
},
{
"filename": "[HorribleSubs] Dr. Stone - 24 [720p].mkv",
"size": 745114641
}
],
"UploadTime": "2019-12-13 17:02:48"
}
]
},
{
"ID": 41952,
"CategoryName": "Anime",
"FullName": "One Piece - TV Series&nbsp;&nbsp;[2019]",
"GroupName": "TV Series",
"SeriesID": "114",
"SeriesName": "One Piece",
"Artists": null,
"Year": "2019",
"Image": "https://mei.animebytes.tv/cQieN6oZ6Ft.jpg",
"Synonymns": {
"0": "ワンピース",
"2": "One Piece: The Great Gold Pirate"
},
"SynonymnsV2": {
"Japanese": "ワンピース",
"Romaji": "",
"Alternative": "One Piece: The Great Gold Pirate"
},
"Snatched": 100700,
"Comments": 3,
"Links": {
"AniDB": "https://anidb.net/anime/69",
"ANN": "https://www.animenewsnetwork.com/encyclopedia/anime.php?id=836",
"Wikipedia": "https://en.wikipedia.org/wiki/One_Piece",
"MAL": "https://myanimelist.net/anime/21/One_Piece"
},
"Votes": 89,
"AvgVote": 8.8,
"Associations": null,
"Description": "The 20th season of One Piece. This represents episode 892 to current.",
"DescriptionHTML": "The 20th season of One Piece. This represents episode 892 to current.",
"EpCount": 0,
"StudioList": null,
"PastWeek": 10,
"Incomplete": false,
"Ongoing": false,
"Tags": [
"adventure",
"fantasy",
"martial.arts",
"shounen",
"super.power",
"action"
],
"Torrents": [
{
"ID": 1043925,
"EditionData": {
"EditionTitle": "Episode 1059"
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/1043925/download/somepass",
"Property": "Web | MKV | h264 | 720p | AAC 2.0 | Softsubs (SubsPlease) | Episode 1059 | Freeleech",
"Snatched": 125,
"Seeders": 114,
"Leechers": 1,
"Status": 0,
"Size": 743629489,
"FileCount": 1,
"FileList": [
{
"filename": "[SubsPlease] One Piece - 1059 (720p) [B347D9DE].mkv",
"size": 743629489
}
],
"UploadTime": "2023-04-23 02:06:08"
},
{
"ID": 1039046,
"EditionData": {
"EditionTitle": "Episode 1058"
},
"RawDownMultiplier": 0,
"RawUpMultiplier": 1,
"Link": "https://animebytes.tv/torrent/1039046/download/somepass",
"Property": "Web | MKV | h264 | 1080p | AAC 2.0 | Softsubs (SubsPlease) | Episode 1058 | Freeleech",
"Snatched": 290,
"Seeders": 232,
"Leechers": 2,
"Status": 0,
"Size": 1453835224,
"FileCount": 1,
"FileList": [
{
"filename": "[SubsPlease] One Piece - 1058 (1080p) [E4094B4A].mkv",
"size": 1453835224
}
],
"UploadTime": "2023-04-16 02:07:42"
}
]
}
]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,160 @@
using System;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.AnimeBytesTests
{
[TestFixture]
public class AnimeBytesFixture : CoreTest<AnimeBytes>
{
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition
{
Name = "AnimeBytes",
Settings = new AnimeBytesSettings
{
BaseUrl = "https://animebytes.tv/",
Username = "someuser",
Passkey = "somepass"
}
};
}
[Test]
public async Task should_parse_recent_feed_from_animebytes()
{
var recentFeed = ReadAllText(@"Files/Indexers/AnimeBytes/recentfeed.json");
Mocker.GetMock<IIndexerHttpClient>()
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new[] { 2000, 5000 } })).Releases;
releases.Should().HaveCount(33);
releases.First().Should().BeOfType<TorrentInfo>();
var firstTorrentInfo = releases.ElementAt(2) as TorrentInfo;
firstTorrentInfo.Title.Should().Be("[SubsPlease] One Piece: The Great Gold Pirate - 1059 [Web][MKV][h264][720p][AAC 2.0][Softsubs (SubsPlease)][Episode 1059]");
firstTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
firstTorrentInfo.DownloadUrl.Should().Be("https://animebytes.tv/torrent/1043925/download/somepass");
firstTorrentInfo.InfoUrl.Should().Be("https://animebytes.tv/torrent/1043925/group");
firstTorrentInfo.Guid.Should().Be("https://animebytes.tv/torrent/1043925/group?nh=0F6BB43603CC07F4C804B9A29139F852");
firstTorrentInfo.CommentUrl.Should().BeNullOrEmpty();
firstTorrentInfo.Indexer.Should().Be(Subject.Definition.Name);
firstTorrentInfo.PublishDate.Should().Be(DateTime.Parse("2023-04-23 02:06:08", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal));
firstTorrentInfo.Size.Should().Be(743629489);
firstTorrentInfo.InfoHash.Should().Be(null);
firstTorrentInfo.MagnetUrl.Should().Be(null);
firstTorrentInfo.Peers.Should().Be(1 + 114);
firstTorrentInfo.Seeders.Should().Be(114);
firstTorrentInfo.Files.Should().Be(1);
firstTorrentInfo.MinimumSeedTime.Should().Be(259200);
var secondTorrentInfo = releases.ElementAt(16) as TorrentInfo;
secondTorrentInfo.Title.Should().Be("[GHOST] BLEACH S03 [Blu-ray][MKV][h265 10-bit][1080p][AC3 2.0][Dual Audio][Softsubs (GHOST)]");
secondTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
secondTorrentInfo.DownloadUrl.Should().Be("https://animebytes.tv/torrent/1031203/download/somepass");
secondTorrentInfo.InfoUrl.Should().Be("https://animebytes.tv/torrent/1031203/group");
secondTorrentInfo.Guid.Should().Be("https://animebytes.tv/torrent/1031203/group?nh=F7C73EF631FE269D3A7F10BD12EC99A1");
secondTorrentInfo.CommentUrl.Should().BeNullOrEmpty();
secondTorrentInfo.Indexer.Should().Be(Subject.Definition.Name);
secondTorrentInfo.PublishDate.Should().Be(DateTime.Parse("2023-04-03 03:14:35", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal));
secondTorrentInfo.Size.Should().Be(24498538059);
secondTorrentInfo.InfoHash.Should().Be(null);
secondTorrentInfo.MagnetUrl.Should().Be(null);
secondTorrentInfo.Peers.Should().Be(2 + 12);
secondTorrentInfo.Seeders.Should().Be(12);
secondTorrentInfo.Files.Should().Be(22);
secondTorrentInfo.MinimumSeedTime.Should().Be(655200);
var thirdTorrentInfo = releases.ElementAt(18) as TorrentInfo;
thirdTorrentInfo.Title.Should().Be("[Polarwindz] Cowboy Bebop: Tengoku no Tobira 2001 [Blu-ray][MKV][h265 10-bit][1080p][Opus 5.1][Softsubs (Polarwindz)]");
thirdTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
thirdTorrentInfo.DownloadUrl.Should().Be("https://animebytes.tv/torrent/959397/download/somepass");
thirdTorrentInfo.InfoUrl.Should().Be("https://animebytes.tv/torrent/959397/group");
thirdTorrentInfo.Guid.Should().Be("https://animebytes.tv/torrent/959397/group?nh=D63895DA87A25239C11F9823F46000E1");
thirdTorrentInfo.CommentUrl.Should().BeNullOrEmpty();
thirdTorrentInfo.Indexer.Should().Be(Subject.Definition.Name);
thirdTorrentInfo.PublishDate.Should().Be(DateTime.Parse("2023-04-02 05:00:43", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal));
thirdTorrentInfo.Size.Should().Be(13090646841);
thirdTorrentInfo.InfoHash.Should().Be(null);
thirdTorrentInfo.MagnetUrl.Should().Be(null);
thirdTorrentInfo.Peers.Should().Be(1 + 5);
thirdTorrentInfo.Seeders.Should().Be(5);
thirdTorrentInfo.Files.Should().Be(1);
thirdTorrentInfo.MinimumSeedTime.Should().Be(475200);
var fourthTorrentInfo = releases.ElementAt(3) as TorrentInfo;
fourthTorrentInfo.Title.Should().Be("[SubsPlease] Dr. STONE: NEW WORLD S03E03 - 03 [Web][MKV][h264][720p][AAC 2.0][Softsubs (SubsPlease)][Episode 3]");
fourthTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
fourthTorrentInfo.DownloadUrl.Should().Be("https://animebytes.tv/torrent/1041495/download/somepass");
fourthTorrentInfo.InfoUrl.Should().Be("https://animebytes.tv/torrent/1041495/group");
fourthTorrentInfo.Guid.Should().Be("https://animebytes.tv/torrent/1041495/group?nh=8B78B0DD3BCC6068BFCD927E4AC674F6");
fourthTorrentInfo.CommentUrl.Should().BeNullOrEmpty();
fourthTorrentInfo.Indexer.Should().Be(Subject.Definition.Name);
fourthTorrentInfo.PublishDate.Should().Be(DateTime.Parse("2023-04-20 14:32:29", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal));
fourthTorrentInfo.Size.Should().Be(748209543);
fourthTorrentInfo.InfoHash.Should().Be(null);
fourthTorrentInfo.MagnetUrl.Should().Be(null);
fourthTorrentInfo.Peers.Should().Be(3 + 137);
fourthTorrentInfo.Seeders.Should().Be(137);
fourthTorrentInfo.Files.Should().Be(1);
fourthTorrentInfo.MinimumSeedTime.Should().Be(259200);
var fifthTorrentInfo = releases.ElementAt(23) as TorrentInfo;
fifthTorrentInfo.Title.Should().Be("[-ZR-] Dr. STONE: STONE WARS S02 [Web][MKV][h264][1080p][AAC 2.0][Dual Audio][Softsubs (-ZR-)]");
fifthTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
fifthTorrentInfo.DownloadUrl.Should().Be("https://animebytes.tv/torrent/944509/download/somepass");
fifthTorrentInfo.InfoUrl.Should().Be("https://animebytes.tv/torrent/944509/group");
fifthTorrentInfo.Guid.Should().Be("https://animebytes.tv/torrent/944509/group?nh=FDCAA1EAB36D7C802F1E4B13DAE5EED7");
fifthTorrentInfo.CommentUrl.Should().BeNullOrEmpty();
fifthTorrentInfo.Indexer.Should().Be(Subject.Definition.Name);
fifthTorrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-06-03 20:30:00", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal));
fifthTorrentInfo.Size.Should().Be(16611719364);
fifthTorrentInfo.InfoHash.Should().Be(null);
fifthTorrentInfo.MagnetUrl.Should().Be(null);
fifthTorrentInfo.Peers.Should().Be(1 + 31);
fifthTorrentInfo.Seeders.Should().Be(31);
fifthTorrentInfo.Files.Should().Be(11);
fifthTorrentInfo.MinimumSeedTime.Should().Be(529200);
var sixthTorrentInfo = releases.ElementAt(31) as TorrentInfo;
sixthTorrentInfo.Title.Should().Be("[HorribleSubs] Dr. STONE S01 [Web][MKV][h264][720p][AAC 2.0][Softsubs (HorribleSubs)]");
sixthTorrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
sixthTorrentInfo.DownloadUrl.Should().Be("https://animebytes.tv/torrent/430074/download/somepass");
sixthTorrentInfo.InfoUrl.Should().Be("https://animebytes.tv/torrent/430074/group");
sixthTorrentInfo.Guid.Should().Be("https://animebytes.tv/torrent/430074/group?nh=32279E138015D8718B2B4B49AEF64574");
sixthTorrentInfo.CommentUrl.Should().BeNullOrEmpty();
sixthTorrentInfo.Indexer.Should().Be(Subject.Definition.Name);
sixthTorrentInfo.PublishDate.Should().Be(DateTime.Parse("2019-12-13 17:02:48", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal));
sixthTorrentInfo.Size.Should().Be(16366224176);
sixthTorrentInfo.InfoHash.Should().Be(null);
sixthTorrentInfo.MagnetUrl.Should().Be(null);
sixthTorrentInfo.Peers.Should().Be(1 + 33);
sixthTorrentInfo.Seeders.Should().Be(33);
sixthTorrentInfo.Files.Should().Be(24);
sixthTorrentInfo.MinimumSeedTime.Should().Be(529200);
}
}
}
@@ -22,10 +22,10 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
Subject.Definition = new IndexerDefinition() Subject.Definition = new IndexerDefinition
{ {
Name = "AvistaZ", Name = "AvistaZ",
Settings = new AvistazSettings() { Username = "someuser", Password = "somepass", Pid = "somepid" } Settings = new AvistazSettings { Username = "someuser", Password = "somepass", Pid = "somepid" }
}; };
} }
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition)) .Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed))); .Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases; var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new[] { 2000 } })).Releases;
releases.Should().HaveCount(100); releases.Should().HaveCount(100);
releases.First().Should().BeOfType<TorrentInfo>(); releases.First().Should().BeOfType<TorrentInfo>();
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://avistaz.to/torrent/187240-japan-sinks-people-of-hope-2021-s01e05-720p-nf-web-dl-ddp20-x264-seikel"); torrentInfo.InfoUrl.Should().Be("https://avistaz.to/torrent/187240-japan-sinks-people-of-hope-2021-s01e05-720p-nf-web-dl-ddp20-x264-seikel");
torrentInfo.CommentUrl.Should().BeNullOrEmpty(); torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name); torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-14 22:26:21")); torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-14 21:26:21"));
torrentInfo.Size.Should().Be(935127615); torrentInfo.Size.Should().Be(935127615);
torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2"); torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
torrentInfo.MagnetUrl.Should().Be(null); torrentInfo.MagnetUrl.Should().Be(null);
@@ -22,10 +22,10 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
Subject.Definition = new IndexerDefinition() Subject.Definition = new IndexerDefinition
{ {
Name = "ExoticaZ", Name = "ExoticaZ",
Settings = new AvistazSettings() { Username = "someuser", Password = "somepass", Pid = "somepid" } Settings = new AvistazSettings { Username = "someuser", Password = "somepass", Pid = "somepid" }
}; };
} }
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition)) .Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed))); .Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases; var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new[] { 2000 } })).Releases;
releases.Should().HaveCount(100); releases.Should().HaveCount(100);
releases.First().Should().BeOfType<TorrentInfo>(); releases.First().Should().BeOfType<TorrentInfo>();
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://exoticaz.to/torrent/64040-ssis-419-my-first-experience-is-yua-mikami-from-the-day-i-lost-my-virginity-i-was-devoted-to-sex"); torrentInfo.InfoUrl.Should().Be("https://exoticaz.to/torrent/64040-ssis-419-my-first-experience-is-yua-mikami-from-the-day-i-lost-my-virginity-i-was-devoted-to-sex");
torrentInfo.CommentUrl.Should().BeNullOrEmpty(); torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name); torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-06-11 16:04:50")); torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-06-11 15:04:50"));
torrentInfo.Size.Should().Be(7085405541); torrentInfo.Size.Should().Be(7085405541);
torrentInfo.InfoHash.Should().Be("asdjfiasdf54asd7f4a2sdf544asdf"); torrentInfo.InfoHash.Should().Be("asdjfiasdf54asd7f4a2sdf544asdf");
torrentInfo.MagnetUrl.Should().Be(null); torrentInfo.MagnetUrl.Should().Be(null);
@@ -22,10 +22,10 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
Subject.Definition = new IndexerDefinition() Subject.Definition = new IndexerDefinition
{ {
Name = "PrivateHD", Name = "PrivateHD",
Settings = new AvistazSettings() { Username = "someuser", Password = "somepass", Pid = "somepid" } Settings = new AvistazSettings { Username = "someuser", Password = "somepass", Pid = "somepid" }
}; };
} }
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition)) .Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed))); .Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new int[] { 2000 } })).Releases; var releases = (await Subject.Fetch(new MovieSearchCriteria { Categories = new[] { 2000 } })).Releases;
releases.Should().HaveCount(100); releases.Should().HaveCount(100);
releases.First().Should().BeOfType<TorrentInfo>(); releases.First().Should().BeOfType<TorrentInfo>();
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://privatehd.to/torrent/78506-godzilla-2014-2160p-uhd-bluray-remux-hdr-hevc-atmos-triton"); torrentInfo.InfoUrl.Should().Be("https://privatehd.to/torrent/78506-godzilla-2014-2160p-uhd-bluray-remux-hdr-hevc-atmos-triton");
torrentInfo.CommentUrl.Should().BeNullOrEmpty(); torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name); torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-03-21 05:24:49")); torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-03-21 04:24:49"));
torrentInfo.Size.Should().Be(69914591044); torrentInfo.Size.Should().Be(69914591044);
torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2"); torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
torrentInfo.MagnetUrl.Should().Be(null); torrentInfo.MagnetUrl.Should().Be(null);
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Indexers.Cardigann; using NzbDrone.Core.Indexers.Definitions.Cardigann;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.CardigannTests namespace NzbDrone.Core.Test.IndexerTests.CardigannTests
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
torrentInfo.InfoUrl.Should().Be("https://filelist.io/details.php?id=665873"); torrentInfo.InfoUrl.Should().Be("https://filelist.io/details.php?id=665873");
torrentInfo.CommentUrl.Should().BeNullOrEmpty(); torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name); torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 20:20:19")); torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 19:20:19"));
torrentInfo.Size.Should().Be(8300512414); torrentInfo.Size.Should().Be(8300512414);
torrentInfo.InfoHash.Should().Be(null); torrentInfo.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null); torrentInfo.MagnetUrl.Should().Be(null);
@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
Subject.Definition = new IndexerDefinition() Subject.Definition = new IndexerDefinition
{ {
Name = "Orpheus", Name = "Orpheus",
Settings = new OrpheusSettings { Apikey = "somekey" } Settings = new OrpheusSettings { Apikey = "somekey" }
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition)) .Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed))); .Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new[] { 2000 } })).Releases; var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new[] { 3000 } })).Releases;
releases.Should().HaveCount(65); releases.Should().HaveCount(65);
releases.First().Should().BeOfType<GazelleInfo>(); releases.First().Should().BeOfType<GazelleInfo>();
@@ -56,6 +56,7 @@ namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
torrentInfo.MagnetUrl.Should().Be(null); torrentInfo.MagnetUrl.Should().Be(null);
torrentInfo.Peers.Should().Be(0); torrentInfo.Peers.Should().Be(0);
torrentInfo.Seeders.Should().Be(0); torrentInfo.Seeders.Should().Be(0);
torrentInfo.Files.Should().Be(18);
torrentInfo.ImdbId.Should().Be(0); torrentInfo.ImdbId.Should().Be(0);
torrentInfo.TmdbId.Should().Be(0); torrentInfo.TmdbId.Should().Be(0);
torrentInfo.TvdbId.Should().Be(0); torrentInfo.TvdbId.Should().Be(0);
@@ -0,0 +1,69 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Definitions;
using NzbDrone.Core.Indexers.Definitions.Gazelle;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.RedactedTests
{
[TestFixture]
public class RedactedFixture : CoreTest<Redacted>
{
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition
{
Name = "Redacted",
Settings = new RedactedSettings { Apikey = "somekey" }
};
}
[Test]
public async Task should_parse_recent_feed_from_Redacted()
{
var recentFeed = ReadAllText(@"Files/Indexers/Redacted/recentfeed.json");
Mocker.GetMock<IIndexerHttpClient>()
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new[] { 3000 } })).Releases;
releases.Should().HaveCount(39);
releases.First().Should().BeOfType<GazelleInfo>();
var torrentInfo = releases.First() as GazelleInfo;
torrentInfo.Title.Should().Be("Red Hot Chili Peppers - Californication [1999] [Album] [US / Reissue 2020] [FLAC 24bit Lossless] [Vinyl]");
torrentInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
torrentInfo.DownloadUrl.Should().Be("https://redacted.ch/ajax.php?action=download&id=3892313&usetoken=0");
torrentInfo.InfoUrl.Should().Be("https://redacted.ch/torrents.php?id=16720&torrentid=3892313");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-12-17 08:02:35"));
torrentInfo.Size.Should().Be(1247137236);
torrentInfo.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null);
torrentInfo.Peers.Should().Be(4);
torrentInfo.Seeders.Should().Be(4);
torrentInfo.Files.Should().Be(23);
torrentInfo.ImdbId.Should().Be(0);
torrentInfo.TmdbId.Should().Be(0);
torrentInfo.TvdbId.Should().Be(0);
torrentInfo.Languages.Should().HaveCount(0);
torrentInfo.Subs.Should().HaveCount(0);
torrentInfo.DownloadVolumeFactor.Should().Be(1);
torrentInfo.UploadVolumeFactor.Should().Be(1);
}
}
}
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Test.IndexerTests.SecretCinemaTests
Subject.Definition = new IndexerDefinition() Subject.Definition = new IndexerDefinition()
{ {
Name = "SecretCinema", Name = "SecretCinema",
Settings = new GazelleSettings() { Username = "somekey", Password = "somekey" } Settings = new GazelleSettings { Username = "somekey", Password = "somekey" }
}; };
} }
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Test.IndexerTests.SecretCinemaTests
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition)) .Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed))); .Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new int[] { 2000 } })).Releases; var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new[] { 2000 } })).Releases;
releases.Should().HaveCount(3); releases.Should().HaveCount(3);
releases.First().Should().BeOfType<GazelleInfo>(); releases.First().Should().BeOfType<GazelleInfo>();
@@ -50,7 +50,7 @@ namespace NzbDrone.Core.Test.IndexerTests.SecretCinemaTests
torrentInfo.InfoUrl.Should().Be("https://secret-cinema.pw/torrents.php?id=2497&torrentid=45068"); torrentInfo.InfoUrl.Should().Be("https://secret-cinema.pw/torrents.php?id=2497&torrentid=45068");
torrentInfo.CommentUrl.Should().BeNullOrEmpty(); torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name); torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-12-15 19:37:29")); torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-12-15 17:37:29"));
torrentInfo.Size.Should().Be(57473058680); torrentInfo.Size.Should().Be(57473058680);
torrentInfo.InfoHash.Should().Be(null); torrentInfo.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null); torrentInfo.MagnetUrl.Should().Be(null);
@@ -19,10 +19,12 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
public class LazyLibrarianSettings : IApplicationSettings public class LazyLibrarianSettings : IApplicationSettings
{ {
private static readonly LazyLibrarianSettingsValidator Validator = new LazyLibrarianSettingsValidator(); private static readonly LazyLibrarianSettingsValidator Validator = new ();
public LazyLibrarianSettings() public LazyLibrarianSettings()
{ {
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:5299";
SyncCategories = new[] SyncCategories = new[]
{ {
NewznabStandardCategory.AudioAudiobook.Id, NewznabStandardCategory.AudioAudiobook.Id,
@@ -18,10 +18,12 @@ namespace NzbDrone.Core.Applications.Lidarr
public class LidarrSettings : IApplicationSettings public class LidarrSettings : IApplicationSettings
{ {
private static readonly LidarrSettingsValidator Validator = new LidarrSettingsValidator(); private static readonly LidarrSettingsValidator Validator = new ();
public LidarrSettings() public LidarrSettings()
{ {
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8686";
SyncCategories = new[] { 3000, 3010, 3030, 3040, 3050, 3060 }; SyncCategories = new[] { 3000, 3010, 3030, 3040, 3050, 3060 };
} }
@@ -23,6 +23,8 @@ namespace NzbDrone.Core.Applications.Lidarr
public class LidarrV1Proxy : ILidarrV1Proxy public class LidarrV1Proxy : ILidarrV1Proxy
{ {
private const string AppApiRoute = "/api/v1";
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly Logger _logger; private readonly Logger _logger;
@@ -34,13 +36,13 @@ namespace NzbDrone.Core.Applications.Lidarr
public LidarrStatus GetStatus(LidarrSettings settings) public LidarrStatus GetStatus(LidarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v1/system/status", HttpMethod.Get); var request = BuildRequest(settings, $"{AppApiRoute}/system/status", HttpMethod.Get);
return Execute<LidarrStatus>(request); return Execute<LidarrStatus>(request);
} }
public List<LidarrIndexer> GetIndexers(LidarrSettings settings) public List<LidarrIndexer> GetIndexers(LidarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v1/indexer", HttpMethod.Get); var request = BuildRequest(settings, $"{AppIndexerApiRoute}", HttpMethod.Get);
return Execute<List<LidarrIndexer>>(request); return Execute<List<LidarrIndexer>>(request);
} }
@@ -48,7 +50,7 @@ namespace NzbDrone.Core.Applications.Lidarr
{ {
try try
{ {
var request = BuildRequest(settings, $"/api/v1/indexer/{indexerId}", HttpMethod.Get); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Get);
return Execute<LidarrIndexer>(request); return Execute<LidarrIndexer>(request);
} }
catch (HttpException ex) catch (HttpException ex)
@@ -64,37 +66,37 @@ namespace NzbDrone.Core.Applications.Lidarr
public void RemoveIndexer(int indexerId, LidarrSettings settings) public void RemoveIndexer(int indexerId, LidarrSettings settings)
{ {
var request = BuildRequest(settings, $"/api/v1/indexer/{indexerId}", HttpMethod.Delete); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Delete);
_httpClient.Execute(request); _httpClient.Execute(request);
} }
public List<LidarrIndexer> GetIndexerSchema(LidarrSettings settings) public List<LidarrIndexer> GetIndexerSchema(LidarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v1/indexer/schema", HttpMethod.Get); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/schema", HttpMethod.Get);
return Execute<List<LidarrIndexer>>(request); return Execute<List<LidarrIndexer>>(request);
} }
public LidarrIndexer AddIndexer(LidarrIndexer indexer, LidarrSettings settings) public LidarrIndexer AddIndexer(LidarrIndexer indexer, LidarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v1/indexer", HttpMethod.Post); var request = BuildRequest(settings, $"{AppIndexerApiRoute}", HttpMethod.Post);
request.SetContent(indexer.ToJson()); request.SetContent(indexer.ToJson());
return Execute<LidarrIndexer>(request); return ExecuteIndexerRequest(request);
} }
public LidarrIndexer UpdateIndexer(LidarrIndexer indexer, LidarrSettings settings) public LidarrIndexer UpdateIndexer(LidarrIndexer indexer, LidarrSettings settings)
{ {
var request = BuildRequest(settings, $"/api/v1/indexer/{indexer.Id}", HttpMethod.Put); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexer.Id}", HttpMethod.Put);
request.SetContent(indexer.ToJson()); request.SetContent(indexer.ToJson());
return Execute<LidarrIndexer>(request); return ExecuteIndexerRequest(request);
} }
public ValidationFailure TestConnection(LidarrIndexer indexer, LidarrSettings settings) public ValidationFailure TestConnection(LidarrIndexer indexer, LidarrSettings settings)
{ {
var request = BuildRequest(settings, $"/api/v1/indexer/test", HttpMethod.Post); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/test", HttpMethod.Post);
request.SetContent(indexer.ToJson()); request.SetContent(indexer.ToJson());
@@ -134,6 +136,53 @@ namespace NzbDrone.Core.Applications.Lidarr
return null; return null;
} }
private LidarrIndexer ExecuteIndexerRequest(HttpRequest request)
{
try
{
return Execute<LidarrIndexer>(request);
}
catch (HttpException ex)
{
switch (ex.Response.StatusCode)
{
case HttpStatusCode.Unauthorized:
_logger.Error(ex, "API Key is invalid");
break;
case HttpStatusCode.BadRequest:
if (ex.Response.Content.Contains("Query successful, but no results in the configured categories were returned from your indexer.", StringComparison.InvariantCultureIgnoreCase))
{
_logger.Error(ex, "No Results in configured categories. See FAQ Entry: Prowlarr will not sync X Indexer to App");
break;
}
_logger.Error(ex, "Invalid Request");
break;
case HttpStatusCode.SeeOther:
_logger.Error(ex, "App returned redirect and is invalid. Check App URL");
break;
case HttpStatusCode.NotFound:
_logger.Error(ex, "Remote indexer not found");
break;
default:
_logger.Error(ex, "Unexpected response status code: {0}", ex.Response.StatusCode);
throw;
}
}
catch (JsonReaderException ex)
{
_logger.Error(ex, "Unable to parse JSON response from application");
throw;
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to add or update indexer");
throw;
}
return null;
}
private HttpRequest BuildRequest(LidarrSettings settings, string resource, HttpMethod method) private HttpRequest BuildRequest(LidarrSettings settings, string resource, HttpMethod method)
{ {
var baseUrl = settings.BaseUrl.TrimEnd('/'); var baseUrl = settings.BaseUrl.TrimEnd('/');
@@ -19,10 +19,12 @@ namespace NzbDrone.Core.Applications.Mylar
public class MylarSettings : IApplicationSettings public class MylarSettings : IApplicationSettings
{ {
private static readonly MylarSettingsValidator Validator = new MylarSettingsValidator(); private static readonly MylarSettingsValidator Validator = new ();
public MylarSettings() public MylarSettings()
{ {
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8090";
SyncCategories = new[] { NewznabStandardCategory.BooksComics.Id }; SyncCategories = new[] { NewznabStandardCategory.BooksComics.Id };
} }
@@ -19,10 +19,12 @@ namespace NzbDrone.Core.Applications.Radarr
public class RadarrSettings : IApplicationSettings public class RadarrSettings : IApplicationSettings
{ {
private static readonly RadarrSettingsValidator Validator = new RadarrSettingsValidator(); private static readonly RadarrSettingsValidator Validator = new ();
public RadarrSettings() public RadarrSettings()
{ {
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:7878";
SyncCategories = new[] { 2000, 2010, 2020, 2030, 2040, 2045, 2050, 2060, 2070, 2080 }; SyncCategories = new[] { 2000, 2010, 2020, 2030, 2040, 2045, 2050, 2060, 2070, 2080 };
} }
@@ -23,6 +23,8 @@ namespace NzbDrone.Core.Applications.Radarr
public class RadarrV3Proxy : IRadarrV3Proxy public class RadarrV3Proxy : IRadarrV3Proxy
{ {
private const string AppApiRoute = "/api/v3";
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly Logger _logger; private readonly Logger _logger;
@@ -34,13 +36,13 @@ namespace NzbDrone.Core.Applications.Radarr
public RadarrStatus GetStatus(RadarrSettings settings) public RadarrStatus GetStatus(RadarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v3/system/status", HttpMethod.Get); var request = BuildRequest(settings, $"{AppApiRoute}/system/status", HttpMethod.Get);
return Execute<RadarrStatus>(request); return Execute<RadarrStatus>(request);
} }
public List<RadarrIndexer> GetIndexers(RadarrSettings settings) public List<RadarrIndexer> GetIndexers(RadarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v3/indexer", HttpMethod.Get); var request = BuildRequest(settings, $"{AppIndexerApiRoute}", HttpMethod.Get);
return Execute<List<RadarrIndexer>>(request); return Execute<List<RadarrIndexer>>(request);
} }
@@ -48,7 +50,7 @@ namespace NzbDrone.Core.Applications.Radarr
{ {
try try
{ {
var request = BuildRequest(settings, $"/api/v3/indexer/{indexerId}", HttpMethod.Get); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Get);
return Execute<RadarrIndexer>(request); return Execute<RadarrIndexer>(request);
} }
catch (HttpException ex) catch (HttpException ex)
@@ -64,37 +66,37 @@ namespace NzbDrone.Core.Applications.Radarr
public void RemoveIndexer(int indexerId, RadarrSettings settings) public void RemoveIndexer(int indexerId, RadarrSettings settings)
{ {
var request = BuildRequest(settings, $"/api/v3/indexer/{indexerId}", HttpMethod.Delete); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Delete);
_httpClient.Execute(request); _httpClient.Execute(request);
} }
public List<RadarrIndexer> GetIndexerSchema(RadarrSettings settings) public List<RadarrIndexer> GetIndexerSchema(RadarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v3/indexer/schema", HttpMethod.Get); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/schema", HttpMethod.Get);
return Execute<List<RadarrIndexer>>(request); return Execute<List<RadarrIndexer>>(request);
} }
public RadarrIndexer AddIndexer(RadarrIndexer indexer, RadarrSettings settings) public RadarrIndexer AddIndexer(RadarrIndexer indexer, RadarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v3/indexer", HttpMethod.Post); var request = BuildRequest(settings, $"{AppIndexerApiRoute}", HttpMethod.Post);
request.SetContent(indexer.ToJson()); request.SetContent(indexer.ToJson());
return Execute<RadarrIndexer>(request); return ExecuteIndexerRequest(request);
} }
public RadarrIndexer UpdateIndexer(RadarrIndexer indexer, RadarrSettings settings) public RadarrIndexer UpdateIndexer(RadarrIndexer indexer, RadarrSettings settings)
{ {
var request = BuildRequest(settings, $"/api/v3/indexer/{indexer.Id}", HttpMethod.Put); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexer.Id}", HttpMethod.Put);
request.SetContent(indexer.ToJson()); request.SetContent(indexer.ToJson());
return Execute<RadarrIndexer>(request); return ExecuteIndexerRequest(request);
} }
public ValidationFailure TestConnection(RadarrIndexer indexer, RadarrSettings settings) public ValidationFailure TestConnection(RadarrIndexer indexer, RadarrSettings settings)
{ {
var request = BuildRequest(settings, $"/api/v3/indexer/test", HttpMethod.Post); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/test", HttpMethod.Post);
request.SetContent(indexer.ToJson()); request.SetContent(indexer.ToJson());
@@ -134,6 +136,53 @@ namespace NzbDrone.Core.Applications.Radarr
return null; return null;
} }
private RadarrIndexer ExecuteIndexerRequest(HttpRequest request)
{
try
{
return Execute<RadarrIndexer>(request);
}
catch (HttpException ex)
{
switch (ex.Response.StatusCode)
{
case HttpStatusCode.Unauthorized:
_logger.Error(ex, "API Key is invalid");
break;
case HttpStatusCode.BadRequest:
if (ex.Response.Content.Contains("Query successful, but no results in the configured categories were returned from your indexer.", StringComparison.InvariantCultureIgnoreCase))
{
_logger.Error(ex, "No Results in configured categories. See FAQ Entry: Prowlarr will not sync X Indexer to App");
break;
}
_logger.Error(ex, "Invalid Request");
break;
case HttpStatusCode.SeeOther:
_logger.Error(ex, "App returned redirect and is invalid. Check App URL");
break;
case HttpStatusCode.NotFound:
_logger.Error(ex, "Remote indexer not found");
break;
default:
_logger.Error(ex, "Unexpected response status code: {0}", ex.Response.StatusCode);
throw;
}
}
catch (JsonReaderException ex)
{
_logger.Error(ex, "Unable to parse JSON response from application");
throw;
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to add or update indexer");
throw;
}
return null;
}
private HttpRequest BuildRequest(RadarrSettings settings, string resource, HttpMethod method) private HttpRequest BuildRequest(RadarrSettings settings, string resource, HttpMethod method)
{ {
var baseUrl = settings.BaseUrl.TrimEnd('/'); var baseUrl = settings.BaseUrl.TrimEnd('/');
@@ -19,10 +19,12 @@ namespace NzbDrone.Core.Applications.Readarr
public class ReadarrSettings : IApplicationSettings public class ReadarrSettings : IApplicationSettings
{ {
private static readonly ReadarrSettingsValidator Validator = new ReadarrSettingsValidator(); private static readonly ReadarrSettingsValidator Validator = new ();
public ReadarrSettings() public ReadarrSettings()
{ {
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8787";
SyncCategories = new[] { 3030, 7000, 7010, 7020, 7030, 7040, 7050, 7060 }; SyncCategories = new[] { 3030, 7000, 7010, 7020, 7030, 7040, 7050, 7060 };
} }
@@ -23,6 +23,8 @@ namespace NzbDrone.Core.Applications.Readarr
public class ReadarrV1Proxy : IReadarrV1Proxy public class ReadarrV1Proxy : IReadarrV1Proxy
{ {
private const string AppApiRoute = "/api/v1";
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly Logger _logger; private readonly Logger _logger;
@@ -34,13 +36,13 @@ namespace NzbDrone.Core.Applications.Readarr
public ReadarrStatus GetStatus(ReadarrSettings settings) public ReadarrStatus GetStatus(ReadarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v1/system/status", HttpMethod.Get); var request = BuildRequest(settings, $"{AppApiRoute}/system/status", HttpMethod.Get);
return Execute<ReadarrStatus>(request); return Execute<ReadarrStatus>(request);
} }
public List<ReadarrIndexer> GetIndexers(ReadarrSettings settings) public List<ReadarrIndexer> GetIndexers(ReadarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v1/indexer", HttpMethod.Get); var request = BuildRequest(settings, $"{AppIndexerApiRoute}", HttpMethod.Get);
return Execute<List<ReadarrIndexer>>(request); return Execute<List<ReadarrIndexer>>(request);
} }
@@ -48,7 +50,7 @@ namespace NzbDrone.Core.Applications.Readarr
{ {
try try
{ {
var request = BuildRequest(settings, $"/api/v1/indexer/{indexerId}", HttpMethod.Get); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Get);
return Execute<ReadarrIndexer>(request); return Execute<ReadarrIndexer>(request);
} }
catch (HttpException ex) catch (HttpException ex)
@@ -64,37 +66,37 @@ namespace NzbDrone.Core.Applications.Readarr
public void RemoveIndexer(int indexerId, ReadarrSettings settings) public void RemoveIndexer(int indexerId, ReadarrSettings settings)
{ {
var request = BuildRequest(settings, $"/api/v1/indexer/{indexerId}", HttpMethod.Delete); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Delete);
_httpClient.Execute(request); _httpClient.Execute(request);
} }
public List<ReadarrIndexer> GetIndexerSchema(ReadarrSettings settings) public List<ReadarrIndexer> GetIndexerSchema(ReadarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v1/indexer/schema", HttpMethod.Get); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/schema", HttpMethod.Get);
return Execute<List<ReadarrIndexer>>(request); return Execute<List<ReadarrIndexer>>(request);
} }
public ReadarrIndexer AddIndexer(ReadarrIndexer indexer, ReadarrSettings settings) public ReadarrIndexer AddIndexer(ReadarrIndexer indexer, ReadarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v1/indexer", HttpMethod.Post); var request = BuildRequest(settings, $"{AppIndexerApiRoute}", HttpMethod.Post);
request.SetContent(indexer.ToJson()); request.SetContent(indexer.ToJson());
return Execute<ReadarrIndexer>(request); return ExecuteIndexerRequest(request);
} }
public ReadarrIndexer UpdateIndexer(ReadarrIndexer indexer, ReadarrSettings settings) public ReadarrIndexer UpdateIndexer(ReadarrIndexer indexer, ReadarrSettings settings)
{ {
var request = BuildRequest(settings, $"/api/v1/indexer/{indexer.Id}", HttpMethod.Put); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexer.Id}", HttpMethod.Put);
request.SetContent(indexer.ToJson()); request.SetContent(indexer.ToJson());
return Execute<ReadarrIndexer>(request); return ExecuteIndexerRequest(request);
} }
public ValidationFailure TestConnection(ReadarrIndexer indexer, ReadarrSettings settings) public ValidationFailure TestConnection(ReadarrIndexer indexer, ReadarrSettings settings)
{ {
var request = BuildRequest(settings, $"/api/v1/indexer/test", HttpMethod.Post); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/test", HttpMethod.Post);
request.SetContent(indexer.ToJson()); request.SetContent(indexer.ToJson());
@@ -134,6 +136,53 @@ namespace NzbDrone.Core.Applications.Readarr
return null; return null;
} }
private ReadarrIndexer ExecuteIndexerRequest(HttpRequest request)
{
try
{
return Execute<ReadarrIndexer>(request);
}
catch (HttpException ex)
{
switch (ex.Response.StatusCode)
{
case HttpStatusCode.Unauthorized:
_logger.Error(ex, "API Key is invalid");
break;
case HttpStatusCode.BadRequest:
if (ex.Response.Content.Contains("Query successful, but no results in the configured categories were returned from your indexer.", StringComparison.InvariantCultureIgnoreCase))
{
_logger.Error(ex, "No Results in configured categories. See FAQ Entry: Prowlarr will not sync X Indexer to App");
break;
}
_logger.Error(ex, "Invalid Request");
break;
case HttpStatusCode.SeeOther:
_logger.Error(ex, "App returned redirect and is invalid. Check App URL");
break;
case HttpStatusCode.NotFound:
_logger.Error(ex, "Remote indexer not found");
break;
default:
_logger.Error(ex, "Unexpected response status code: {0}", ex.Response.StatusCode);
throw;
}
}
catch (JsonReaderException ex)
{
_logger.Error(ex, "Unable to parse JSON response from application");
throw;
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to add or update indexer");
throw;
}
return null;
}
private HttpRequest BuildRequest(ReadarrSettings settings, string resource, HttpMethod method) private HttpRequest BuildRequest(ReadarrSettings settings, string resource, HttpMethod method)
{ {
var baseUrl = settings.BaseUrl.TrimEnd('/'); var baseUrl = settings.BaseUrl.TrimEnd('/');
@@ -18,10 +18,12 @@ namespace NzbDrone.Core.Applications.Sonarr
public class SonarrSettings : IApplicationSettings public class SonarrSettings : IApplicationSettings
{ {
private static readonly SonarrSettingsValidator Validator = new SonarrSettingsValidator(); private static readonly SonarrSettingsValidator Validator = new ();
public SonarrSettings() public SonarrSettings()
{ {
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8989";
SyncCategories = new[] { 5000, 5010, 5020, 5030, 5040, 5045, 5050 }; SyncCategories = new[] { 5000, 5010, 5020, 5030, 5040, 5045, 5050 };
AnimeSyncCategories = new[] { 5070 }; AnimeSyncCategories = new[] { 5070 };
} }
@@ -23,6 +23,8 @@ namespace NzbDrone.Core.Applications.Sonarr
public class SonarrV3Proxy : ISonarrV3Proxy public class SonarrV3Proxy : ISonarrV3Proxy
{ {
private const string AppApiRoute = "/api/v3";
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly Logger _logger; private readonly Logger _logger;
@@ -34,13 +36,13 @@ namespace NzbDrone.Core.Applications.Sonarr
public SonarrStatus GetStatus(SonarrSettings settings) public SonarrStatus GetStatus(SonarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v3/system/status", HttpMethod.Get); var request = BuildRequest(settings, $"{AppApiRoute}/system/status", HttpMethod.Get);
return Execute<SonarrStatus>(request); return Execute<SonarrStatus>(request);
} }
public List<SonarrIndexer> GetIndexers(SonarrSettings settings) public List<SonarrIndexer> GetIndexers(SonarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v3/indexer", HttpMethod.Get); var request = BuildRequest(settings, $"{AppIndexerApiRoute}", HttpMethod.Get);
return Execute<List<SonarrIndexer>>(request); return Execute<List<SonarrIndexer>>(request);
} }
@@ -48,7 +50,7 @@ namespace NzbDrone.Core.Applications.Sonarr
{ {
try try
{ {
var request = BuildRequest(settings, $"/api/v3/indexer/{indexerId}", HttpMethod.Get); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Get);
return Execute<SonarrIndexer>(request); return Execute<SonarrIndexer>(request);
} }
catch (HttpException ex) catch (HttpException ex)
@@ -64,37 +66,37 @@ namespace NzbDrone.Core.Applications.Sonarr
public void RemoveIndexer(int indexerId, SonarrSettings settings) public void RemoveIndexer(int indexerId, SonarrSettings settings)
{ {
var request = BuildRequest(settings, $"/api/v3/indexer/{indexerId}", HttpMethod.Delete); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Delete);
_httpClient.Execute(request); _httpClient.Execute(request);
} }
public List<SonarrIndexer> GetIndexerSchema(SonarrSettings settings) public List<SonarrIndexer> GetIndexerSchema(SonarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v3/indexer/schema", HttpMethod.Get); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/schema", HttpMethod.Get);
return Execute<List<SonarrIndexer>>(request); return Execute<List<SonarrIndexer>>(request);
} }
public SonarrIndexer AddIndexer(SonarrIndexer indexer, SonarrSettings settings) public SonarrIndexer AddIndexer(SonarrIndexer indexer, SonarrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v3/indexer", HttpMethod.Post); var request = BuildRequest(settings, $"{AppIndexerApiRoute}", HttpMethod.Post);
request.SetContent(indexer.ToJson()); request.SetContent(indexer.ToJson());
return Execute<SonarrIndexer>(request); return ExecuteIndexerRequest(request);
} }
public SonarrIndexer UpdateIndexer(SonarrIndexer indexer, SonarrSettings settings) public SonarrIndexer UpdateIndexer(SonarrIndexer indexer, SonarrSettings settings)
{ {
var request = BuildRequest(settings, $"/api/v3/indexer/{indexer.Id}", HttpMethod.Put); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexer.Id}", HttpMethod.Put);
request.SetContent(indexer.ToJson()); request.SetContent(indexer.ToJson());
return Execute<SonarrIndexer>(request); return ExecuteIndexerRequest(request);
} }
public ValidationFailure TestConnection(SonarrIndexer indexer, SonarrSettings settings) public ValidationFailure TestConnection(SonarrIndexer indexer, SonarrSettings settings)
{ {
var request = BuildRequest(settings, $"/api/v3/indexer/test", HttpMethod.Post); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/test", HttpMethod.Post);
request.SetContent(indexer.ToJson()); request.SetContent(indexer.ToJson());
@@ -140,6 +142,53 @@ namespace NzbDrone.Core.Applications.Sonarr
return null; return null;
} }
private SonarrIndexer ExecuteIndexerRequest(HttpRequest request)
{
try
{
return Execute<SonarrIndexer>(request);
}
catch (HttpException ex)
{
switch (ex.Response.StatusCode)
{
case HttpStatusCode.Unauthorized:
_logger.Error(ex, "API Key is invalid");
break;
case HttpStatusCode.BadRequest:
if (ex.Response.Content.Contains("Query successful, but no results in the configured categories were returned from your indexer.", StringComparison.InvariantCultureIgnoreCase))
{
_logger.Error(ex, "No Results in configured categories. See FAQ Entry: Prowlarr will not sync X Indexer to App");
break;
}
_logger.Error(ex, "Invalid Request");
break;
case HttpStatusCode.SeeOther:
_logger.Error(ex, "App returned redirect and is invalid. Check App URL");
break;
case HttpStatusCode.NotFound:
_logger.Error(ex, "Remote indexer not found");
break;
default:
_logger.Error(ex, "Unexpected response status code: {0}", ex.Response.StatusCode);
throw;
}
}
catch (JsonReaderException ex)
{
_logger.Error(ex, "Unable to parse JSON response from application");
throw;
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to add or update indexer");
throw;
}
return null;
}
private HttpRequest BuildRequest(SonarrSettings settings, string resource, HttpMethod method) private HttpRequest BuildRequest(SonarrSettings settings, string resource, HttpMethod method)
{ {
var baseUrl = settings.BaseUrl.TrimEnd('/'); var baseUrl = settings.BaseUrl.TrimEnd('/');
@@ -19,10 +19,12 @@ namespace NzbDrone.Core.Applications.Whisparr
public class WhisparrSettings : IApplicationSettings public class WhisparrSettings : IApplicationSettings
{ {
private static readonly WhisparrSettingsValidator Validator = new WhisparrSettingsValidator(); private static readonly WhisparrSettingsValidator Validator = new ();
public WhisparrSettings() public WhisparrSettings()
{ {
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:6969";
SyncCategories = new[] { 6000, 6010, 6020, 6030, 6040, 6045, 6050, 6070, 6080, 6090 }; SyncCategories = new[] { 6000, 6010, 6020, 6030, 6040, 6045, 6050, 6070, 6080, 6090 };
} }
@@ -23,6 +23,8 @@ namespace NzbDrone.Core.Applications.Whisparr
public class WhisparrV3Proxy : IWhisparrV3Proxy public class WhisparrV3Proxy : IWhisparrV3Proxy
{ {
private const string AppApiRoute = "/api/v3";
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly Logger _logger; private readonly Logger _logger;
@@ -34,13 +36,13 @@ namespace NzbDrone.Core.Applications.Whisparr
public WhisparrStatus GetStatus(WhisparrSettings settings) public WhisparrStatus GetStatus(WhisparrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v3/system/status", HttpMethod.Get); var request = BuildRequest(settings, $"{AppApiRoute}/system/status", HttpMethod.Get);
return Execute<WhisparrStatus>(request); return Execute<WhisparrStatus>(request);
} }
public List<WhisparrIndexer> GetIndexers(WhisparrSettings settings) public List<WhisparrIndexer> GetIndexers(WhisparrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v3/indexer", HttpMethod.Get); var request = BuildRequest(settings, $"{AppIndexerApiRoute}", HttpMethod.Get);
return Execute<List<WhisparrIndexer>>(request); return Execute<List<WhisparrIndexer>>(request);
} }
@@ -48,7 +50,7 @@ namespace NzbDrone.Core.Applications.Whisparr
{ {
try try
{ {
var request = BuildRequest(settings, $"/api/v3/indexer/{indexerId}", HttpMethod.Get); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Get);
return Execute<WhisparrIndexer>(request); return Execute<WhisparrIndexer>(request);
} }
catch (HttpException ex) catch (HttpException ex)
@@ -64,19 +66,19 @@ namespace NzbDrone.Core.Applications.Whisparr
public void RemoveIndexer(int indexerId, WhisparrSettings settings) public void RemoveIndexer(int indexerId, WhisparrSettings settings)
{ {
var request = BuildRequest(settings, $"/api/v3/indexer/{indexerId}", HttpMethod.Delete); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Delete);
_httpClient.Execute(request); _httpClient.Execute(request);
} }
public List<WhisparrIndexer> GetIndexerSchema(WhisparrSettings settings) public List<WhisparrIndexer> GetIndexerSchema(WhisparrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v3/indexer/schema", HttpMethod.Get); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/schema", HttpMethod.Get);
return Execute<List<WhisparrIndexer>>(request); return Execute<List<WhisparrIndexer>>(request);
} }
public WhisparrIndexer AddIndexer(WhisparrIndexer indexer, WhisparrSettings settings) public WhisparrIndexer AddIndexer(WhisparrIndexer indexer, WhisparrSettings settings)
{ {
var request = BuildRequest(settings, "/api/v3/indexer", HttpMethod.Post); var request = BuildRequest(settings, $"{AppIndexerApiRoute}", HttpMethod.Post);
request.SetContent(indexer.ToJson()); request.SetContent(indexer.ToJson());
@@ -85,16 +87,16 @@ namespace NzbDrone.Core.Applications.Whisparr
public WhisparrIndexer UpdateIndexer(WhisparrIndexer indexer, WhisparrSettings settings) public WhisparrIndexer UpdateIndexer(WhisparrIndexer indexer, WhisparrSettings settings)
{ {
var request = BuildRequest(settings, $"/api/v3/indexer/{indexer.Id}", HttpMethod.Put); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexer.Id}", HttpMethod.Put);
request.SetContent(indexer.ToJson()); request.SetContent(indexer.ToJson());
return Execute<WhisparrIndexer>(request); return ExecuteIndexerRequest(request);
} }
public ValidationFailure TestConnection(WhisparrIndexer indexer, WhisparrSettings settings) public ValidationFailure TestConnection(WhisparrIndexer indexer, WhisparrSettings settings)
{ {
var request = BuildRequest(settings, $"/api/v3/indexer/test", HttpMethod.Post); var request = BuildRequest(settings, $"{AppIndexerApiRoute}/test", HttpMethod.Post);
request.SetContent(indexer.ToJson()); request.SetContent(indexer.ToJson());
@@ -134,6 +136,53 @@ namespace NzbDrone.Core.Applications.Whisparr
return null; return null;
} }
private WhisparrIndexer ExecuteIndexerRequest(HttpRequest request)
{
try
{
return Execute<WhisparrIndexer>(request);
}
catch (HttpException ex)
{
switch (ex.Response.StatusCode)
{
case HttpStatusCode.Unauthorized:
_logger.Error(ex, "API Key is invalid");
break;
case HttpStatusCode.BadRequest:
if (ex.Response.Content.Contains("Query successful, but no results in the configured categories were returned from your indexer.", StringComparison.InvariantCultureIgnoreCase))
{
_logger.Error(ex, "No Results in configured categories. See FAQ Entry: Prowlarr will not sync X Indexer to App");
break;
}
_logger.Error(ex, "Invalid Request");
break;
case HttpStatusCode.SeeOther:
_logger.Error(ex, "App returned redirect and is invalid. Check App URL");
break;
case HttpStatusCode.NotFound:
_logger.Error(ex, "Remote indexer not found");
break;
default:
_logger.Error(ex, "Unexpected response status code: {0}", ex.Response.StatusCode);
throw;
}
}
catch (JsonReaderException ex)
{
_logger.Error(ex, "Unable to parse JSON response from application");
throw;
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to add or update indexer");
throw;
}
return null;
}
private HttpRequest BuildRequest(WhisparrSettings settings, string resource, HttpMethod method) private HttpRequest BuildRequest(WhisparrSettings settings, string resource, HttpMethod method)
{ {
var baseUrl = settings.BaseUrl.TrimEnd('/'); var baseUrl = settings.BaseUrl.TrimEnd('/');
+10 -5
View File
@@ -69,7 +69,8 @@ namespace NzbDrone.Core.Backup
_diskProvider.EnsureFolder(_backupTempFolder); _diskProvider.EnsureFolder(_backupTempFolder);
_diskProvider.EnsureFolder(GetBackupFolder(backupType)); _diskProvider.EnsureFolder(GetBackupFolder(backupType));
var backupFilename = string.Format("prowlarr_backup_v{0}_{1:yyyy.MM.dd_HH.mm.ss}.zip", BuildInfo.Version, DateTime.Now); var dateNow = DateTime.Now;
var backupFilename = $"prowlarr_backup_v{BuildInfo.Version}_{dateNow:yyyy.MM.dd_HH.mm.ss}.zip";
var backupPath = Path.Combine(GetBackupFolder(backupType), backupFilename); var backupPath = Path.Combine(GetBackupFolder(backupType), backupFilename);
Cleanup(); Cleanup();
@@ -81,7 +82,7 @@ namespace NzbDrone.Core.Backup
BackupConfigFile(); BackupConfigFile();
BackupDatabase(); BackupDatabase();
CreateVersionInfo(); CreateVersionInfo(dateNow);
_logger.ProgressDebug("Creating backup zip"); _logger.ProgressDebug("Creating backup zip");
@@ -208,11 +209,15 @@ namespace NzbDrone.Core.Backup
_diskTransferService.TransferFile(configFile, tempConfigFile, TransferMode.Copy); _diskTransferService.TransferFile(configFile, tempConfigFile, TransferMode.Copy);
} }
private void CreateVersionInfo() private void CreateVersionInfo(DateTime dateNow)
{ {
var builder = new StringBuilder(); var tempFile = Path.Combine(_backupTempFolder, "INFO");
builder.AppendLine(BuildInfo.Version.ToString()); var builder = new StringBuilder();
builder.AppendLine($"v{BuildInfo.Version}");
builder.AppendLine($"{dateNow:yyyy-MM-dd HH:mm:ss}");
_diskProvider.WriteAllText(tempFile, builder.ToString());
} }
private void CleanupOldBackups(BackupType backupType) private void CleanupOldBackups(BackupType backupType)
@@ -25,6 +25,7 @@ namespace NzbDrone.Core.Configuration
{ {
Dictionary<string, object> GetConfigDictionary(); Dictionary<string, object> GetConfigDictionary();
void SaveConfigDictionary(Dictionary<string, object> configValues); void SaveConfigDictionary(Dictionary<string, object> configValues);
void EnsureDefaultConfigFile();
string BindAddress { get; } string BindAddress { get; }
int Port { get; } int Port { get; }
@@ -258,7 +259,7 @@ namespace NzbDrone.Core.Configuration
public T GetValueEnum<T>(string key, T defaultValue, bool persist = true) public T GetValueEnum<T>(string key, T defaultValue, bool persist = true)
{ {
return (T)Enum.Parse(typeof(T), GetValue(key, defaultValue), persist); return (T)Enum.Parse(typeof(T), GetValue(key, defaultValue, persist), true);
} }
public string GetValue(string key, object defaultValue, bool persist = true) public string GetValue(string key, object defaultValue, bool persist = true)
@@ -317,7 +318,7 @@ namespace NzbDrone.Core.Configuration
SetValue(key, value.ToString().ToLower()); SetValue(key, value.ToString().ToLower());
} }
private void EnsureDefaultConfigFile() public void EnsureDefaultConfigFile()
{ {
if (!File.Exists(_configFile)) if (!File.Exists(_configFile))
{ {
@@ -103,7 +103,7 @@ namespace NzbDrone.Core.Datastore
{ {
if (!ids.Any()) if (!ids.Any())
{ {
return new List<TModel>(); return Array.Empty<TModel>();
} }
var result = Query(x => ids.Contains(x.Id)); var result = Query(x => ids.Contains(x.Id));
+9 -28
View File
@@ -1,5 +1,7 @@
using System; using System;
using System.Data; using System.Data;
using System.Data.Common;
using System.Data.SQLite;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Dapper; using Dapper;
using NLog; using NLog;
@@ -38,17 +40,9 @@ namespace NzbDrone.Core.Datastore
{ {
get get
{ {
using (var db = _datamapperFactory()) using var db = _datamapperFactory();
{
if (db.ConnectionString.Contains(".db")) return db is SQLiteConnection ? DatabaseType.SQLite : DatabaseType.PostgreSQL;
{
return DatabaseType.SQLite;
}
else
{
return DatabaseType.PostgreSQL;
}
}
} }
} }
@@ -56,24 +50,11 @@ namespace NzbDrone.Core.Datastore
{ {
get get
{ {
using (var db = _datamapperFactory()) using var db = _datamapperFactory();
{ var dbConnection = db as DbConnection;
string version; var version = Regex.Replace(dbConnection.ServerVersion, @"\(.*?\)", "");
try return new Version(version);
{
version = db.QueryFirstOrDefault<string>("SHOW server_version");
//Postgres can return extra info about operating system on version call, ignore this
version = Regex.Replace(version, @"\(.*?\)", "");
}
catch
{
version = db.QueryFirstOrDefault<string>("SELECT sqlite_version()");
}
return new Version(version);
}
} }
} }
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using NLog; using NLog;
@@ -64,7 +65,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
catch (DownloadClientException e) catch (DownloadClientException e)
{ {
_logger.Error(e); _logger.Error(e);
return new List<DownloadStationTask>(); return Array.Empty<DownloadStationTask>();
} }
} }
@@ -45,6 +45,11 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
return true; return true;
} }
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
throw new DownloadClientException("Failed to connect to qBittorrent. Check your settings and qBittorrent configuration.", new HttpException(response));
}
if (response.HasHttpError) if (response.HasHttpError)
{ {
throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", new HttpException(response)); throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", new HttpException(response));
@@ -16,12 +16,12 @@ namespace NzbDrone.Core.Download.Clients.rTorrent
public RTorrentDirectoryValidator(PathExistsValidator pathExistsValidator, public RTorrentDirectoryValidator(PathExistsValidator pathExistsValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator) MappedNetworkDriveValidator mappedNetworkDriveValidator)
{ {
RuleFor(c => c.Directory).Cascade(CascadeMode.StopOnFirstFailure) RuleFor(c => c.Directory).Cascade(CascadeMode.Stop)
.IsValidPath() .IsValidPath()
.SetValidator(mappedNetworkDriveValidator) .SetValidator(mappedNetworkDriveValidator)
.SetValidator(pathExistsValidator) .SetValidator(pathExistsValidator)
.When(c => c.Directory.IsNotNullOrWhiteSpace()) .When(c => c.Directory.IsNotNullOrWhiteSpace())
.When(c => c.Host == "localhost" || c.Host == "127.0.0.1"); .When(c => c.Host == "localhost" || c.Host == "127.0.0.1");
} }
} }
} }
+1 -4
View File
@@ -2,9 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
#if !LIBRARY
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
#endif
namespace NzbDrone.Core namespace NzbDrone.Core
{ {
@@ -12,9 +10,8 @@ namespace NzbDrone.Core
{ {
public static string WithDefault(this string actual, object defaultValue) public static string WithDefault(this string actual, object defaultValue)
{ {
#if !LIBRARY
Ensure.That(defaultValue, () => defaultValue).IsNotNull(); Ensure.That(defaultValue, () => defaultValue).IsNotNull();
#endif
if (string.IsNullOrWhiteSpace(actual)) if (string.IsNullOrWhiteSpace(actual))
{ {
return defaultValue.ToString(); return defaultValue.ToString();
@@ -1,7 +1,6 @@
using System.Linq; using System.Linq;
using NLog;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Cardigann; using NzbDrone.Core.Indexers.Definitions.Cardigann;
using NzbDrone.Core.IndexerVersions; using NzbDrone.Core.IndexerVersions;
using NzbDrone.Core.Localization; using NzbDrone.Core.Localization;
using NzbDrone.Core.ThingiProvider.Events; using NzbDrone.Core.ThingiProvider.Events;
@@ -1,7 +1,7 @@
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Cardigann; using NzbDrone.Core.Indexers.Definitions.Cardigann;
using NzbDrone.Core.IndexerVersions; using NzbDrone.Core.IndexerVersions;
using NzbDrone.Core.Localization; using NzbDrone.Core.Localization;
using NzbDrone.Core.ThingiProvider.Events; using NzbDrone.Core.ThingiProvider.Events;
@@ -31,9 +31,7 @@ namespace NzbDrone.Core.History
public History MostRecentForDownloadId(string downloadId) public History MostRecentForDownloadId(string downloadId)
{ {
return FindByDownloadId(downloadId) return FindByDownloadId(downloadId).MaxBy(h => h.Date);
.OrderByDescending(h => h.Date)
.FirstOrDefault();
} }
public List<History> FindByDownloadId(string downloadId) public List<History> FindByDownloadId(string downloadId)
@@ -75,9 +73,7 @@ namespace NzbDrone.Core.History
public History MostRecentForIndexer(int indexerId) public History MostRecentForIndexer(int indexerId)
{ {
return Query(x => x.IndexerId == indexerId) return Query(x => x.IndexerId == indexerId).MaxBy(h => h.Date);
.OrderByDescending(h => h.Date)
.FirstOrDefault();
} }
public List<History> Between(DateTime start, DateTime end) public List<History> Between(DateTime start, DateTime end)
+3 -1
View File
@@ -118,12 +118,14 @@ namespace NzbDrone.Core.History
public void Handle(IndexerQueryEvent message) public void Handle(IndexerQueryEvent message)
{ {
var response = message.QueryResult.Response;
var history = new History var history = new History
{ {
Date = DateTime.UtcNow, Date = DateTime.UtcNow,
IndexerId = message.IndexerId, IndexerId = message.IndexerId,
EventType = message.Query.IsRssSearch ? HistoryEventType.IndexerRss : HistoryEventType.IndexerQuery, EventType = message.Query.IsRssSearch ? HistoryEventType.IndexerRss : HistoryEventType.IndexerQuery,
Successful = message.QueryResult.Response?.StatusCode == HttpStatusCode.OK 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)
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.IndexerSearch
@"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]", @"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]",
RegexOptions.Compiled); RegexOptions.Compiled);
public List<ReleaseInfo> Releases { get; set; } public IList<ReleaseInfo> Releases { get; set; }
private static string RemoveInvalidXMLChars(string text) private static string RemoveInvalidXMLChars(string text)
{ {
@@ -148,7 +148,7 @@ namespace NzbDrone.Core.IndexerSearch
return spec; return spec;
} }
private async Task<List<ReleaseInfo>> Dispatch(Func<IIndexer, Task<IndexerPageableQueryResult>> searchAction, SearchCriteriaBase criteriaBase) private async Task<IList<ReleaseInfo>> Dispatch(Func<IIndexer, Task<IndexerPageableQueryResult>> searchAction, SearchCriteriaBase criteriaBase)
{ {
var indexers = _indexerFactory.Enabled(); var indexers = _indexerFactory.Enabled();
@@ -168,7 +168,7 @@ namespace NzbDrone.Core.IndexerSearch
if (indexers.Count == 0) if (indexers.Count == 0)
{ {
_logger.Debug("All provided categories are unsupported by selected indexers: {0}", string.Join(", ", criteriaBase.Categories)); _logger.Debug("All provided categories are unsupported by selected indexers: {0}", string.Join(", ", criteriaBase.Categories));
return new List<ReleaseInfo>(); return Array.Empty<ReleaseInfo>();
} }
} }
@@ -189,7 +189,7 @@ namespace NzbDrone.Core.IndexerSearch
{ {
if (_indexerLimitService.AtQueryLimit((IndexerDefinition)indexer.Definition)) if (_indexerLimitService.AtQueryLimit((IndexerDefinition)indexer.Definition))
{ {
return new List<ReleaseInfo>(); return Array.Empty<ReleaseInfo>();
} }
try try
@@ -224,7 +224,7 @@ namespace NzbDrone.Core.IndexerSearch
_logger.Error(e, "Error while searching for {0}", criteriaBase); _logger.Error(e, "Error while searching for {0}", criteriaBase);
} }
return new List<ReleaseInfo>(); return Array.Empty<ReleaseInfo>();
} }
} }
} }
@@ -8,7 +8,7 @@ using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Cardigann; using NzbDrone.Core.Indexers.Definitions.Cardigann;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@@ -29,7 +29,7 @@ namespace NzbDrone.Core.IndexerVersions
/* Update Service will fall back if version # does not exist for an indexer per Ta */ /* Update Service will fall back if version # does not exist for an indexer per Ta */
private const string DEFINITION_BRANCH = "master"; private const string DEFINITION_BRANCH = "master";
private const int DEFINITION_VERSION = 8; private const int DEFINITION_VERSION = 9;
// Used when moving yml to C# // Used when moving yml to C#
private readonly List<string> _definitionBlocklist = new () private readonly List<string> _definitionBlocklist = new ()
File diff suppressed because it is too large Load Diff
@@ -59,6 +59,6 @@ namespace NzbDrone.Core.Indexers.Definitions
public class AvistaZParser : AvistazParserBase public class AvistaZParser : AvistazParserBase
{ {
protected override string TimezoneOffset => "+01:00"; protected override string TimezoneOffset => "+02:00";
} }
} }
@@ -67,9 +67,9 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
_logger.Debug("Avistaz authentication succeeded."); _logger.Debug("Avistaz authentication succeeded.");
} }
protected override bool CheckIfLoginNeeded(HttpResponse response) protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{ {
return response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.PreconditionFailed; return httpResponse.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.PreconditionFailed;
} }
protected override void ModifyRequest(IndexerRequest request) protected override void ModifyRequest(IndexerRequest request)
@@ -13,18 +13,18 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{ {
public class AvistazParserBase : IParseIndexerResponse public class AvistazParserBase : IParseIndexerResponse
{ {
protected virtual string TimezoneOffset => "-05:00"; // Avistaz does not specify a timezone & returns server time protected virtual string TimezoneOffset => "-04:00"; // Avistaz does not specify a timezone & returns server time
private readonly HashSet<string> _hdResolutions = new () { "1080p", "1080i", "720p" }; private readonly HashSet<string> _hdResolutions = new () { "1080p", "1080i", "720p" };
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; } public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse) public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{ {
var torrentInfos = new List<TorrentInfo>(); var releaseInfos = new List<ReleaseInfo>();
if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.NotFound) if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.NotFound)
{ {
return torrentInfos.ToArray(); return releaseInfos.ToArray();
} }
if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.TooManyRequests) if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.TooManyRequests)
@@ -80,11 +80,13 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
release.TvdbId = row.MovieTvinfo.Tvdb.IsNullOrWhiteSpace() ? 0 : ParseUtil.TryCoerceInt(row.MovieTvinfo.Tvdb, out var tvdbResult) ? tvdbResult : 0; release.TvdbId = row.MovieTvinfo.Tvdb.IsNullOrWhiteSpace() ? 0 : ParseUtil.TryCoerceInt(row.MovieTvinfo.Tvdb, out var tvdbResult) ? tvdbResult : 0;
} }
torrentInfos.Add(release); releaseInfos.Add(release);
} }
// order by date // order by date
return torrentInfos.OrderByDescending(o => o.PublishDate).ToArray(); return releaseInfos
.OrderByDescending(o => o.PublishDate)
.ToArray();
} }
// hook to adjust category parsing // hook to adjust category parsing
@@ -115,7 +117,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
cats.Add(NewznabStandardCategory.Audio); cats.Add(NewznabStandardCategory.Audio);
break; break;
default: default:
throw new Exception(string.Format("Error parsing Avistaz category type {0}", row.Type)); throw new Exception($"Error parsing Avistaz category type {row.Type}");
} }
return cats; return cats;
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net;
using NLog; using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
@@ -85,6 +86,8 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
var request = new IndexerRequest(searchUrl, HttpAccept.Html); var request = new IndexerRequest(searchUrl, HttpAccept.Html);
request.HttpRequest.Headers.Add("Authorization", $"Bearer {Settings.Token}"); request.HttpRequest.Headers.Add("Authorization", $"Bearer {Settings.Token}");
request.HttpRequest.SuppressHttpErrorStatusCodes = new[] { HttpStatusCode.NotFound };
yield return request; yield return request;
} }
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using AngleSharp.Dom; using AngleSharp.Dom;
using AngleSharp.Html.Parser; using AngleSharp.Html.Parser;
using NLog; using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
@@ -27,7 +28,6 @@ namespace NzbDrone.Core.Indexers.Definitions
public override string[] IndexerUrls => new[] { "https://bakabt.me/" }; public override string[] IndexerUrls => new[] { "https://bakabt.me/" };
public override string Description => "Anime Community"; public override string Description => "Anime Community";
private string LoginUrl => Settings.BaseUrl + "login.php";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent; public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private; public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities(); public override IndexerCapabilities Capabilities => SetCapabilities();
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var dom = parser.ParseDocument(response.Content); var dom = parser.ParseDocument(response.Content);
var downloadLink = dom.QuerySelector(".download_link")?.GetAttribute("href"); var downloadLink = dom.QuerySelector(".download_link")?.GetAttribute("href");
if (string.IsNullOrWhiteSpace(downloadLink)) if (downloadLink.IsNullOrWhiteSpace())
{ {
throw new Exception("Unable to find download link."); throw new Exception("Unable to find download link.");
} }
@@ -71,17 +71,19 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
UpdateCookies(null, null); UpdateCookies(null, null);
var requestBuilder = new HttpRequestBuilder(LoginUrl) var loginUrl = Settings.BaseUrl + "login.php";
var requestBuilder = new HttpRequestBuilder(loginUrl)
{ {
LogResponseContent = true, LogResponseContent = true,
AllowAutoRedirect = true, AllowAutoRedirect = true,
Method = HttpMethod.Post Method = HttpMethod.Post
}; };
var loginPage = await ExecuteAuth(new HttpRequest(LoginUrl)); var loginPage = await ExecuteAuth(new HttpRequest(loginUrl));
var parser = new HtmlParser(); var parser = new HtmlParser();
var dom = parser.ParseDocument(loginPage.Content); var dom = await parser.ParseDocumentAsync(loginPage.Content);
var loginKey = dom.QuerySelector("input[name=\"loginKey\"]"); var loginKey = dom.QuerySelector("input[name=\"loginKey\"]");
if (loginKey != null) if (loginKey != null)
{ {
@@ -98,21 +100,23 @@ namespace NzbDrone.Core.Indexers.Definitions
var response = await ExecuteAuth(authLoginRequest); var response = await ExecuteAuth(authLoginRequest);
if (response.Content != null && response.Content.Contains("<a href=\"logout.php\">Logout</a>")) if (CheckIfLoginNeeded(response))
{ {
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(30)); var htmlParser = new HtmlParser();
var document = await htmlParser.ParseDocumentAsync(response.Content);
var errorMessage = document.QuerySelector("#loginError, .error")?.TextContent.Trim();
_logger.Debug("BakaBT authentication succeeded"); throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
}
else
{
throw new IndexerAuthException("BakaBT authentication failed");
} }
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(30));
_logger.Debug("BakaBT authentication succeeded");
} }
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse) protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{ {
return !httpResponse.Content.Contains("<a href=\"logout.php\">Logout</a>"); return httpResponse.Content.Contains("loginForm");
} }
private IndexerCapabilities SetCapabilities() private IndexerCapabilities SetCapabilities()
@@ -241,7 +245,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse) public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{ {
var torrentInfos = new List<TorrentInfo>(); var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser(); var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content); var dom = parser.ParseDocument(indexerResponse.Content);
@@ -356,11 +360,11 @@ namespace NzbDrone.Core.Indexers.Definitions
release.DownloadVolumeFactor = row.QuerySelector("span.freeleech") != null ? 0 : 1; release.DownloadVolumeFactor = row.QuerySelector("span.freeleech") != null ? 0 : 1;
release.UploadVolumeFactor = 1; release.UploadVolumeFactor = 1;
torrentInfos.Add(release); releaseInfos.Add(release);
} }
} }
return torrentInfos.ToArray(); return releaseInfos.ToArray();
} }
private ICollection<IndexerCategory> GetNextCategory(IElement row, ICollection<IndexerCategory> currentCategories) private ICollection<IndexerCategory> GetNextCategory(IElement row, ICollection<IndexerCategory> currentCategories)
@@ -388,12 +392,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var categoryName = categoryElement.GetAttribute("title"); var categoryName = categoryElement.GetAttribute("title");
if (!string.IsNullOrWhiteSpace(categoryName)) return categoryName.IsNotNullOrWhiteSpace() ? categoryName : null;
{
return categoryName;
}
return null;
} }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; } public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
@@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Globalization;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
@@ -50,50 +50,61 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
var parameters = new BroadcastheNetTorrentQuery(); var parameters = new BroadcastheNetTorrentQuery();
var searchString = searchCriteria.SearchTerm != null ? searchCriteria.SearchTerm : ""; var searchString = searchCriteria.SearchTerm ?? "";
var btnResults = searchCriteria.Limit.GetValueOrDefault(); var btnResults = searchCriteria.Limit.GetValueOrDefault();
if (btnResults == 0) if (btnResults == 0)
{ {
btnResults = (int)Capabilities.LimitsDefault; btnResults = Capabilities.LimitsDefault.GetValueOrDefault(PageSize);
} }
var btnOffset = searchCriteria.Offset.GetValueOrDefault(); var btnOffset = searchCriteria.Offset.GetValueOrDefault(0);
if (searchCriteria.TvdbId > 0) if (searchCriteria.TvdbId > 0)
{ {
parameters.Tvdb = string.Format("{0}", searchCriteria.TvdbId); parameters.Tvdb = $"{searchCriteria.TvdbId}";
} }
if (searchCriteria.RId > 0) if (searchCriteria.RId > 0)
{ {
parameters.Tvrage = string.Format("{0}", searchCriteria.RId); parameters.Tvrage = $"{searchCriteria.RId}";
} }
// If only the season/episode is searched for then change format to match expected format // If only the season/episode is searched for then change format to match expected format
if (searchCriteria.Season > 0 && searchCriteria.Episode.IsNullOrWhiteSpace()) if (searchCriteria.Season > 0 && searchCriteria.Episode.IsNullOrWhiteSpace())
{ {
// Season Only // Search Season
parameters.Name = string.Format("Season {0}%", searchCriteria.Season.Value);
parameters.Category = "Season"; parameters.Category = "Season";
parameters.Name = $"Season {searchCriteria.Season}%";
pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset));
parameters = parameters.Clone();
// Search Episode
parameters.Category = "Episode";
parameters.Name = $"S{searchCriteria.Season:00}E%";
pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset));
} }
else if (searchCriteria.Season > 0 && Regex.IsMatch(searchCriteria.EpisodeSearchString, "(\\d{4}\\.\\d{2}\\.\\d{2})")) else if (DateTime.TryParseExact($"{searchCriteria.Season} {searchCriteria.Episode}", "yyyy MM/dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var showDate))
{ {
// Daily Episode // Daily Episode
parameters.Name = searchCriteria.EpisodeSearchString; parameters.Name = showDate.ToString("yyyy.MM.dd");
parameters.Category = "Episode"; parameters.Category = "Episode";
pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset));
} }
else if (searchCriteria.Season > 0 && int.Parse(searchCriteria.Episode) > 0) else if (searchCriteria.Season > 0 && int.TryParse(searchCriteria.Episode, out var episode) && episode > 0)
{ {
// Standard (S/E) Episode // Standard (S/E) Episode
parameters.Name = string.Format("S{0:00}E{1:00}", searchCriteria.Season.Value, int.Parse(searchCriteria.Episode)); parameters.Name = $"S{searchCriteria.Season:00}E{episode:00}";
parameters.Category = "Episode"; parameters.Category = "Episode";
pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset));
}
else
{
// Neither a season only search nor daily nor standard, fall back to query
parameters.Search = searchString.Replace(" ", "%");
pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset));
} }
// Neither a season only search nor daily nor standard, fall back to query
parameters.Search = searchString.Replace(" ", "%");
pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset));
return pageableRequests; return pageableRequests;
} }
@@ -109,17 +120,17 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
var parameters = new BroadcastheNetTorrentQuery(); var parameters = new BroadcastheNetTorrentQuery();
var searchString = searchCriteria.SearchTerm != null ? searchCriteria.SearchTerm : ""; var searchString = searchCriteria.SearchTerm ?? "";
var btnResults = searchCriteria.Limit.GetValueOrDefault(); var btnResults = searchCriteria.Limit.GetValueOrDefault();
if (btnResults == 0) if (btnResults == 0)
{ {
btnResults = (int)Capabilities.LimitsDefault; btnResults = Capabilities.LimitsDefault.GetValueOrDefault(PageSize);
} }
parameters.Search = searchString.Replace(" ", "%"); var btnOffset = searchCriteria.Offset.GetValueOrDefault(0);
var btnOffset = searchCriteria.Offset.GetValueOrDefault(); parameters.Search = searchString.Replace(" ", "%");
pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset)); pageableRequests.Add(GetPagedRequests(parameters, btnResults, btnOffset));
@@ -14,7 +14,7 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation; using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Cardigann namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{ {
public class Cardigann : TorrentIndexerBase<CardigannSettings> public class Cardigann : TorrentIndexerBase<CardigannSettings>
{ {
@@ -15,7 +15,7 @@ using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
namespace NzbDrone.Core.Indexers.Cardigann namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{ {
public class CardigannBase public class CardigannBase
{ {
@@ -825,9 +825,19 @@ namespace NzbDrone.Core.Indexers.Cardigann
protected JArray JsonParseRowsSelector(JToken parsedJson, string rowSelector) protected JArray JsonParseRowsSelector(JToken parsedJson, string rowSelector)
{ {
var selector = rowSelector.Split(':')[0]; var selector = rowSelector.Split(':')[0];
var rowsObj = parsedJson.SelectToken(selector).Value<JArray>();
return new JArray(rowsObj.Where(t => try
JsonParseFieldSelector(t.Value<JObject>(), rowSelector.Remove(0, selector.Length)) != null)); {
var rowsObj = parsedJson.SelectToken(selector).Value<JArray>();
return new JArray(rowsObj.Where(t => JsonParseFieldSelector(t.Value<JObject>(), rowSelector.Remove(0, selector.Length)) != null));
}
catch (Exception ex)
{
_logger.Trace(ex, "Failed to parse JSON rows for selector \"{0}\"", rowSelector);
return null;
}
} }
private string JsonParseFieldSelector(JToken parsedJson, string rowSelector) private string JsonParseFieldSelector(JToken parsedJson, string rowSelector)
@@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace NzbDrone.Core.Indexers.Cardigann namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{ {
// A Dictionary allowing the same key multiple times // A Dictionary allowing the same key multiple times
public class KeyValuePairList : List<KeyValuePair<string, SelectorBlock>>, IDictionary<string, SelectorBlock> public class KeyValuePairList : List<KeyValuePair<string, SelectorBlock>>, IDictionary<string, SelectorBlock>
@@ -116,6 +116,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
{ {
public string Selector { get; set; } public string Selector { get; set; }
public bool Optional { get; set; } public bool Optional { get; set; }
public string Default { get; set; }
public string Text { get; set; } public string Text { get; set; }
public string Attribute { get; set; } public string Attribute { get; set; }
public string Remove { get; set; } public string Remove { get; set; }
@@ -146,6 +147,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
public List<SearchPathBlock> Paths { get; set; } public List<SearchPathBlock> Paths { get; set; }
public Dictionary<string, List<string>> Headers { get; set; } public Dictionary<string, List<string>> Headers { get; set; }
public List<FilterBlock> Keywordsfilters { get; set; } public List<FilterBlock> Keywordsfilters { get; set; }
public bool AllowEmptyInputs { get; set; }
public Dictionary<string, string> Inputs { get; set; } public Dictionary<string, string> Inputs { get; set; }
public List<ErrorBlock> Error { get; set; } public List<ErrorBlock> Error { get; set; }
public List<FilterBlock> Preprocessingfilters { get; set; } public List<FilterBlock> Preprocessingfilters { get; set; }
@@ -159,6 +161,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
public SelectorBlock Dateheaders { get; set; } public SelectorBlock Dateheaders { get; set; }
public SelectorBlock Count { get; set; } public SelectorBlock Count { get; set; }
public bool Multiple { get; set; } public bool Multiple { get; set; }
public bool MissingAttributeEqualsNoResults { get; set; }
} }
public class SearchPathBlock : RequestBlock public class SearchPathBlock : RequestBlock
@@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace NzbDrone.Core.Indexers.Cardigann namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{ {
public class CardigannMetaDefinition public class CardigannMetaDefinition
{ {
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text.RegularExpressions;
using AngleSharp.Dom; using AngleSharp.Dom;
using AngleSharp.Html.Parser; using AngleSharp.Html.Parser;
using AngleSharp.Xml.Parser; using AngleSharp.Xml.Parser;
@@ -16,7 +15,7 @@ using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Cardigann namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{ {
public class CardigannParser : CardigannBase, IParseIndexerResponse public class CardigannParser : CardigannBase, IParseIndexerResponse
{ {
@@ -81,20 +80,38 @@ namespace NzbDrone.Core.Indexers.Cardigann
if (search.Rows.Count != null) if (search.Rows.Count != null)
{ {
var countVal = HandleJsonSelector(search.Rows.Count, parsedJson, variables); try
if (int.TryParse(countVal, out var count) && count < 1)
{ {
return releases; var countVal = HandleJsonSelector(search.Rows.Count, parsedJson, variables);
if (int.TryParse(countVal, out var count) && count < 1)
{
return releases;
}
}
catch (Exception ex)
{
_logger.Trace(ex, "Failed to parse JSON rows count.");
} }
} }
var rowsArray = JsonParseRowsSelector(parsedJson, search.Rows.Selector); var rowsArray = JsonParseRowsSelector(parsedJson, search.Rows.Selector);
if (rowsArray == null) if (rowsArray == null)
{ {
if (search.Rows.MissingAttributeEqualsNoResults)
{
return releases;
}
throw new IndexerException(indexerResponse, "Error Parsing Rows Selector"); throw new IndexerException(indexerResponse, "Error Parsing Rows Selector");
} }
if (rowsArray.Count == 0)
{
return releases;
}
foreach (var row in rowsArray) foreach (var row in rowsArray)
{ {
var selObj = search.Rows.Attribute != null ? row.SelectToken(search.Rows.Attribute).Value<JToken>() : row; var selObj = search.Rows.Attribute != null ? row.SelectToken(search.Rows.Attribute).Value<JToken>() : row;
@@ -117,6 +134,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
string value = null; string value = null;
var variablesKey = ".Result." + fieldName; var variablesKey = ".Result." + fieldName;
var isOptional = OptionalFields.Contains(field.Key) || fieldModifiers.Contains("optional") || field.Value.Optional; var isOptional = OptionalFields.Contains(field.Key) || fieldModifiers.Contains("optional") || field.Value.Optional;
try try
{ {
var parentObj = mulRow; var parentObj = mulRow;
@@ -126,28 +144,35 @@ namespace NzbDrone.Core.Indexers.Cardigann
} }
value = HandleJsonSelector(field.Value, parentObj, variables, !isOptional); value = HandleJsonSelector(field.Value, parentObj, variables, !isOptional);
if (isOptional && string.IsNullOrWhiteSpace(value))
if (isOptional && value.IsNullOrWhiteSpace())
{ {
variables[variablesKey] = null; var defaultValue = ApplyGoTemplateText(field.Value.Default, variables);
continue;
if (defaultValue.IsNullOrWhiteSpace())
{
variables[variablesKey] = null;
continue;
}
value = defaultValue;
} }
variables[variablesKey] = ParseFields(value, fieldName, release, fieldModifiers, searchUrlUri); variables[variablesKey] = ParseFields(value, fieldName, release, fieldModifiers, searchUrlUri);
} }
catch (Exception ex) catch (Exception ex)
{ {
if (!variables.ContainsKey(variablesKey)) if (!variables.ContainsKey(variablesKey) || isOptional)
{ {
variables[variablesKey] = null; variables[variablesKey] = null;
} }
if (isOptional) if (isOptional)
{ {
variables[variablesKey] = null;
continue; continue;
} }
throw new CardigannException(string.Format("Error while parsing field={0}, selector={1}, value={2}: {3}", field.Key, field.Value.Selector, value ?? "<null>", ex.Message)); throw new CardigannException($"Error while parsing field={field.Key}, selector={field.Value.Selector}, value={value ?? "<null>"}: {ex.Message}", ex);
} }
} }
@@ -248,34 +273,41 @@ namespace NzbDrone.Core.Indexers.Cardigann
string value = null; string value = null;
var variablesKey = ".Result." + fieldName; var variablesKey = ".Result." + fieldName;
var isOptional = OptionalFields.Contains(field.Key) || fieldModifiers.Contains("optional") || field.Value.Optional; var isOptional = OptionalFields.Contains(field.Key) || fieldModifiers.Contains("optional") || field.Value.Optional;
try try
{ {
value = HandleSelector(field.Value, row, variables, !isOptional); value = HandleSelector(field.Value, row, variables, !isOptional);
if (isOptional && string.IsNullOrWhiteSpace(value)) if (isOptional && value.IsNullOrWhiteSpace())
{ {
variables[variablesKey] = null; var defaultValue = ApplyGoTemplateText(field.Value.Default, variables);
continue;
if (defaultValue.IsNullOrWhiteSpace())
{
variables[variablesKey] = null;
continue;
}
value = defaultValue;
} }
variables[variablesKey] = ParseFields(value, fieldName, release, fieldModifiers, searchUrlUri); variables[variablesKey] = ParseFields(value, fieldName, release, fieldModifiers, searchUrlUri);
} }
catch (Exception ex) catch (Exception ex)
{ {
if (!variables.ContainsKey(variablesKey)) if (!variables.ContainsKey(variablesKey) || isOptional)
{ {
variables[variablesKey] = null; variables[variablesKey] = null;
} }
if (OptionalFields.Contains(field.Key) || fieldModifiers.Contains("optional") || field.Value.Optional) if (isOptional)
{ {
variables[variablesKey] = null;
continue; continue;
} }
if (indexerLogging) if (indexerLogging)
{ {
_logger.Trace("Error while parsing field={0}, selector={1}, value={2}: {3}", field.Key, field.Value.Selector, value == null ? "<null>" : value, ex.Message); _logger.Trace(ex, "Error while parsing field={0}, selector={1}, value={2}: {3}", field.Key, field.Value.Selector, value ?? "<null>", ex.Message);
} }
} }
} }
@@ -1,7 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
namespace NzbDrone.Core.Indexers.Cardigann namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{ {
public class CardigannRequest : IndexerRequest public class CardigannRequest : IndexerRequest
{ {
@@ -9,15 +9,15 @@ using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser; using AngleSharp.Html.Parser;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NLog; using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.Cardigann;
using NzbDrone.Core.Indexers.Definitions.Cardigann.Exceptions; using NzbDrone.Core.Indexers.Definitions.Cardigann.Exceptions;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers.Cardigann namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{ {
public class CardigannRequestGenerator : CardigannBase, IIndexerRequestGenerator public class CardigannRequestGenerator : CardigannBase, IIndexerRequestGenerator
{ {
@@ -589,7 +589,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
return true; return true;
} }
public async Task<Captcha> GetConfigurationForSetup(bool automaticlogin) public async Task<Captcha> GetConfigurationForSetup(bool automaticLogin)
{ {
var login = _definition.Login; var login = _definition.Login;
@@ -610,6 +610,11 @@ namespace NzbDrone.Core.Indexers.Cardigann
Encoding = _encoding Encoding = _encoding
}; };
if (_definition.Followredirect)
{
requestBuilder.AllowAutoRedirect = true;
}
Cookies = null; Cookies = null;
if (login.Cookies != null) if (login.Cookies != null)
{ {
@@ -627,11 +632,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
Cookies = landingResult.GetCookies(); Cookies = landingResult.GetCookies();
// Some sites have a temporary redirect before the login page, we need to process it.
//if (_definition.Followredirect)
//{
// await FollowIfRedirect(landingResult, loginUrl.AbsoluteUri, overrideCookies: landingResult.Cookies, accumulateCookies: true);
//}
var htmlParser = new HtmlParser(); var htmlParser = new HtmlParser();
landingResultDocument = htmlParser.ParseDocument(landingResult.Content); landingResultDocument = htmlParser.ParseDocument(landingResult.Content);
@@ -642,7 +642,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
captcha = await GetCaptcha(login); captcha = await GetCaptcha(login);
} }
if (captcha != null && automaticlogin) if (captcha != null && automaticLogin)
{ {
_logger.Error("CardigannIndexer ({0}): Found captcha during automatic login, aborting", _definition.Id); _logger.Error("CardigannIndexer ({0}): Found captcha during automatic login, aborting", _definition.Id);
} }
@@ -727,16 +727,19 @@ namespace NzbDrone.Core.Indexers.Cardigann
pairs = new Dictionary<string, string>(); pairs = new Dictionary<string, string>();
} }
foreach (var input in request.Inputs) if (request.Inputs != null)
{ {
var value = ApplyGoTemplateText(input.Value, variables); foreach (var input in request.Inputs)
if (method == HttpMethod.Get)
{ {
queryCollection.Add(input.Key, value); var value = ApplyGoTemplateText(input.Value, variables);
} if (method == HttpMethod.Get)
else if (method == HttpMethod.Post) {
{ queryCollection.Add(input.Key, value);
pairs.Add(input.Key, value); }
else if (method == HttpMethod.Post)
{
pairs.Add(input.Key, value);
}
} }
} }
@@ -1101,7 +1104,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
var rawStr = ApplyGoTemplateText(input.Value, variables, WebUtility.UrlEncode); var rawStr = ApplyGoTemplateText(input.Value, variables, WebUtility.UrlEncode);
foreach (var part in rawStr.Split('&')) foreach (var part in rawStr.Split('&'))
{ {
var parts = part.Split(new char[] { '=' }, 2); var parts = part.Split(new[] { '=' }, 2);
var key = parts[0]; var key = parts[0];
if (key.Length == 0) if (key.Length == 0)
{ {
@@ -1119,7 +1122,12 @@ namespace NzbDrone.Core.Indexers.Cardigann
} }
else else
{ {
queryCollection.Add(input.Key, ApplyGoTemplateText(input.Value, variables)); var inputValue = ApplyGoTemplateText(input.Value, variables);
if (inputValue.IsNotNullOrWhiteSpace() || search.AllowEmptyInputs)
{
queryCollection.Add(input.Key, inputValue);
}
} }
} }
} }
@@ -1133,7 +1141,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
} }
} }
_logger.Info($"Adding request: {searchUrl}"); _logger.Debug($"Adding request: {searchUrl}");
var requestBuilder = new HttpRequestBuilder(searchUrl) var requestBuilder = new HttpRequestBuilder(searchUrl)
{ {
@@ -1157,19 +1165,16 @@ namespace NzbDrone.Core.Indexers.Cardigann
requestBuilder.SetHeaders(headers ?? new Dictionary<string, string>()); requestBuilder.SetHeaders(headers ?? new Dictionary<string, string>());
} }
if (searchPath.Followredirect)
{
requestBuilder.AllowAutoRedirect = true;
}
var request = requestBuilder var request = requestBuilder
.WithRateLimit(_rateLimit.TotalSeconds) .WithRateLimit(_rateLimit.TotalSeconds)
.Build(); .Build();
var cardigannRequest = new CardigannRequest(request, variables, searchPath) yield return new CardigannRequest(request, variables, searchPath);
{
HttpRequest =
{
AllowAutoRedirect = searchPath.Followredirect
}
};
yield return cardigannRequest;
} }
} }
} }
@@ -1,11 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentValidation;
using FluentValidation.Results;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
using NzbDrone.Core.Indexers.Settings; using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Cardigann namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{ {
public class CardigannSettings : NoAuthTorrentBaseSettings public class CardigannSettings : NoAuthTorrentBaseSettings
{ {
@@ -1,6 +1,3 @@
using NzbDrone.Common.Exceptions;
using NzbDrone.Core.Indexers.Cardigann;
namespace NzbDrone.Core.Indexers.Definitions.Cardigann.Exceptions namespace NzbDrone.Core.Indexers.Definitions.Cardigann.Exceptions
{ {
public class CardigannConfigException : CardigannException public class CardigannConfigException : CardigannException
@@ -1,8 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NzbDrone.Common.Exceptions; using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Indexers.Definitions.Cardigann.Exceptions namespace NzbDrone.Core.Indexers.Definitions.Cardigann.Exceptions
@@ -72,7 +72,7 @@ public class FileListParser : IParseIndexerResponse
InfoUrl = GetInfoUrl(id), InfoUrl = GetInfoUrl(id),
Seeders = row.Seeders, Seeders = row.Seeders,
Peers = row.Leechers + row.Seeders, Peers = row.Leechers + row.Seeders,
PublishDate = DateTime.Parse(row.UploadDate + " +0200", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal), PublishDate = DateTime.Parse(row.UploadDate + " +0300", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal),
Description = row.SmallDescription, Description = row.SmallDescription,
Genres = row.SmallDescription.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(), Genres = row.SmallDescription.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(),
ImdbId = imdbId, ImdbId = imdbId,
@@ -378,7 +378,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var jsonResponse = JsonConvert.DeserializeObject<MyAnonamouseResponse>(indexerResponse.Content); var jsonResponse = JsonConvert.DeserializeObject<MyAnonamouseResponse>(indexerResponse.Content);
var error = jsonResponse.Error; var error = jsonResponse.Error;
if (error != null && error == "Nothing returned, out of 0") if (error is "Nothing returned, out of 0" or "Nothing returned, out of 1")
{ {
return torrentInfos.ToArray(); return torrentInfos.ToArray();
} }
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Net;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -47,6 +48,11 @@ namespace NzbDrone.Core.Indexers.Definitions
return new NebulanceParser(Settings); return new NebulanceParser(Settings);
} }
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{
return false;
}
protected override Task<HttpRequest> GetDownloadRequest(Uri link) protected override Task<HttpRequest> GetDownloadRequest(Uri link)
{ {
// Avoid using cookies to prevent redirects to login page // Avoid using cookies to prevent redirects to login page
@@ -189,6 +195,11 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
var torrentInfos = new List<ReleaseInfo>(); var torrentInfos = new List<ReleaseInfo>();
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from API request");
}
var jsonResponse = new HttpResponse<JsonRpcResponse<NebulanceTorrents>>(indexerResponse.HttpResponse).Resource; var jsonResponse = new HttpResponse<JsonRpcResponse<NebulanceTorrents>>(indexerResponse.HttpResponse).Resource;
if (jsonResponse.Error != null || jsonResponse.Result == null) if (jsonResponse.Error != null || jsonResponse.Result == null)
@@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq; using System.Linq;
using System.Net;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using FluentValidation; using FluentValidation;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@@ -10,6 +10,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
@@ -35,7 +36,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()
{ {
return new NzbIndexRequestGenerator() { Settings = Settings, Capabilities = Capabilities }; return new NzbIndexRequestGenerator(Settings, Capabilities);
} }
public override IParseIndexerResponse GetParser() public override IParseIndexerResponse GetParser()
@@ -48,21 +49,21 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities var caps = new IndexerCapabilities
{ {
TvSearchParams = new List<TvSearchParam> TvSearchParams = new List<TvSearchParam>
{ {
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
}, },
MovieSearchParams = new List<MovieSearchParam> MovieSearchParams = new List<MovieSearchParam>
{ {
MovieSearchParam.Q MovieSearchParam.Q
}, },
MusicSearchParams = new List<MusicSearchParam> MusicSearchParams = new List<MusicSearchParam>
{ {
MusicSearchParam.Q MusicSearchParam.Q
}, },
BookSearchParams = new List<BookSearchParam> BookSearchParams = new List<BookSearchParam>
{ {
BookSearchParam.Q BookSearchParam.Q
} }
}; };
// TODO build this out more // TODO build this out more
@@ -349,15 +350,15 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(915, NewznabStandardCategory.Other, "a.b.graveyard"); caps.Categories.AddCategoryMapping(915, NewznabStandardCategory.Other, "a.b.graveyard");
caps.Categories.AddCategoryMapping(255, NewznabStandardCategory.Other, "a.b.guitar.tab"); caps.Categories.AddCategoryMapping(255, NewznabStandardCategory.Other, "a.b.guitar.tab");
caps.Categories.AddCategoryMapping(256, NewznabStandardCategory.Other, "a.b.hd.ger.moviez"); caps.Categories.AddCategoryMapping(256, NewznabStandardCategory.Other, "a.b.hd.ger.moviez");
caps.Categories.AddCategoryMapping(257, NewznabStandardCategory.Other, "a.b.hdtv"); caps.Categories.AddCategoryMapping(257, NewznabStandardCategory.TV, "a.b.hdtv");
caps.Categories.AddCategoryMapping(258, NewznabStandardCategory.Other, "a.b.hdtv.french"); caps.Categories.AddCategoryMapping(258, NewznabStandardCategory.Other, "a.b.hdtv.french");
caps.Categories.AddCategoryMapping(259, NewznabStandardCategory.Other, "a.b.hdtv.french.repost"); caps.Categories.AddCategoryMapping(259, NewznabStandardCategory.Other, "a.b.hdtv.french.repost");
caps.Categories.AddCategoryMapping(260, NewznabStandardCategory.Other, "a.b.hdtv.german"); caps.Categories.AddCategoryMapping(260, NewznabStandardCategory.TVForeign, "a.b.hdtv.german");
caps.Categories.AddCategoryMapping(261, NewznabStandardCategory.Other, "a.b.hdtv.german-audio"); caps.Categories.AddCategoryMapping(261, NewznabStandardCategory.TVForeign, "a.b.hdtv.german-audio");
caps.Categories.AddCategoryMapping(262, NewznabStandardCategory.Other, "a.b.hdtv.repost"); caps.Categories.AddCategoryMapping(262, NewznabStandardCategory.Other, "a.b.hdtv.repost");
caps.Categories.AddCategoryMapping(263, NewznabStandardCategory.Other, "a.b.hdtv.tv-episodes"); caps.Categories.AddCategoryMapping(263, NewznabStandardCategory.TV, "a.b.hdtv.tv-episodes");
caps.Categories.AddCategoryMapping(264, NewznabStandardCategory.Other, "a.b.hdtv.x264"); caps.Categories.AddCategoryMapping(264, NewznabStandardCategory.TV, "a.b.hdtv.x264");
caps.Categories.AddCategoryMapping(265, NewznabStandardCategory.Other, "a.b.hdtv.x264.french"); caps.Categories.AddCategoryMapping(265, NewznabStandardCategory.TVForeign, "a.b.hdtv.x264.french");
caps.Categories.AddCategoryMapping(266, NewznabStandardCategory.Other, "a.b.highspeed"); caps.Categories.AddCategoryMapping(266, NewznabStandardCategory.Other, "a.b.highspeed");
caps.Categories.AddCategoryMapping(267, NewznabStandardCategory.Other, "a.b.hitoshirezu"); caps.Categories.AddCategoryMapping(267, NewznabStandardCategory.Other, "a.b.hitoshirezu");
caps.Categories.AddCategoryMapping(268, NewznabStandardCategory.Other, "a.b.holiday"); caps.Categories.AddCategoryMapping(268, NewznabStandardCategory.Other, "a.b.holiday");
@@ -1021,45 +1022,20 @@ namespace NzbDrone.Core.Indexers.Definitions
public class NzbIndexRequestGenerator : IIndexerRequestGenerator public class NzbIndexRequestGenerator : IIndexerRequestGenerator
{ {
public NzbIndexSettings Settings { get; set; } private readonly NzbIndexSettings _settings;
public IndexerCapabilities Capabilities { get; set; } private readonly IndexerCapabilities _capabilities;
public NzbIndexRequestGenerator() public NzbIndexRequestGenerator(NzbIndexSettings settings, IndexerCapabilities capabilities)
{ {
} _settings = settings;
_capabilities = capabilities;
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, int limit, int offset)
{
var searchString = term;
var queryCollection = new NameValueCollection
{
{ "key", Settings.ApiKey },
{ "max", limit.ToString() },
{ "q", searchString },
{ "p", ((offset / limit) + 1).ToString() }
};
var searchUrl = string.Format("{0}/api/v3/search/?{1}", Settings.BaseUrl.TrimEnd('/'), queryCollection.GetQueryString());
if (categories != null)
{
foreach (var cat in Capabilities.Categories.MapTorznabCapsToTrackers(categories))
{
searchUrl += string.Format("&g[]={0}", cat);
}
}
var request = new IndexerRequest(searchUrl, HttpAccept.Json);
yield return request;
} }
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria) public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.Limit ?? 100, searchCriteria.Offset ?? 0)); pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories, searchCriteria.Limit ?? 100, searchCriteria.Offset ?? 0));
return pageableRequests; return pageableRequests;
} }
@@ -1068,7 +1044,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.Limit ?? 100, searchCriteria.Offset ?? 0)); pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories, searchCriteria.Limit ?? 100, searchCriteria.Offset ?? 0));
return pageableRequests; return pageableRequests;
} }
@@ -1077,7 +1053,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories, searchCriteria.Limit ?? 100, searchCriteria.Offset ?? 0)); pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedTvSearchString}", searchCriteria.Categories, searchCriteria.Limit ?? 100, searchCriteria.Offset ?? 0));
return pageableRequests; return pageableRequests;
} }
@@ -1086,7 +1062,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.Limit ?? 100, searchCriteria.Offset ?? 0)); pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories, searchCriteria.Limit ?? 100, searchCriteria.Offset ?? 0));
return pageableRequests; return pageableRequests;
} }
@@ -1095,11 +1071,36 @@ namespace NzbDrone.Core.Indexers.Definitions
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.Limit ?? 100, searchCriteria.Offset ?? 0)); pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories, searchCriteria.Limit ?? 100, searchCriteria.Offset ?? 0));
return pageableRequests; return pageableRequests;
} }
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, int limit, int offset)
{
var queryCollection = new List<KeyValuePair<string, string>>
{
{ "key", _settings.ApiKey },
{ "max", limit.ToString() },
{ "q", term },
{ "p", (offset / limit).ToString() }
};
if (categories != null)
{
foreach (var cat in _capabilities.Categories.MapTorznabCapsToTrackers(categories))
{
queryCollection.Add("g[]", $"{cat}");
}
}
var searchUrl = $"{_settings.BaseUrl.TrimEnd('/')}/api/v3/search/?{queryCollection.GetQueryString()}";
var request = new IndexerRequest(searchUrl, HttpAccept.Json);
yield return request;
}
public Func<IDictionary<string, string>> GetCookies { get; set; } public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; } public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
} }
@@ -1109,6 +1110,8 @@ namespace NzbDrone.Core.Indexers.Definitions
private readonly NzbIndexSettings _settings; private readonly NzbIndexSettings _settings;
private readonly IndexerCapabilitiesCategories _categories; private readonly IndexerCapabilitiesCategories _categories;
private static readonly Regex ParseTitleRegex = new (@"\""(?<title>[^:\/]*?)(?:\.(rar|nfo|mkv|par2|001|nzb|url|zip|r[0-9]{2}))?\""", RegexOptions.Compiled);
public NzbIndexParser(NzbIndexSettings settings, IndexerCapabilitiesCategories categories) public NzbIndexParser(NzbIndexSettings settings, IndexerCapabilitiesCategories categories)
{ {
_settings = settings; _settings = settings;
@@ -1117,6 +1120,16 @@ namespace NzbDrone.Core.Indexers.Definitions
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse) public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{ {
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new IndexerException(indexerResponse, "Unexpected response status {0} code from API request", indexerResponse.HttpResponse.StatusCode);
}
if (!indexerResponse.HttpResponse.Headers.ContentType.Contains(HttpAccept.Json.Value))
{
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
}
var releaseInfos = new List<ReleaseInfo>(); var releaseInfos = new List<ReleaseInfo>();
// TODO Deserialize to TorrentSyndikatResponse Type // TODO Deserialize to TorrentSyndikatResponse Type
@@ -1154,8 +1167,6 @@ namespace NzbDrone.Core.Indexers.Definitions
return releaseInfos.ToArray(); return releaseInfos.ToArray();
} }
private static readonly Regex ParseTitleRegex = new Regex(@"\""(?<title>[^:\/]*?)(?:\.(rar|nfo|mkv|par2|001|nzb|url|zip|r[0-9]{2}))?\""");
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; } public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
} }
@@ -281,7 +281,6 @@ namespace NzbDrone.Core.Indexers.Definitions
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders), Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
PublishDate = torrent.Time.ToUniversalTime(), PublishDate = torrent.Time.ToUniversalTime(),
Scene = torrent.Scene, Scene = torrent.Scene,
Freeleech = torrent.IsFreeLeech || torrent.IsPersonalFreeLeech,
Files = torrent.FileCount, Files = torrent.FileCount,
Grabs = torrent.Snatches, Grabs = torrent.Snatches,
DownloadVolumeFactor = torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsPersonalFreeLeech ? 0 : 1, DownloadVolumeFactor = torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsPersonalFreeLeech ? 0 : 1,
@@ -318,7 +317,6 @@ namespace NzbDrone.Core.Indexers.Definitions
Seeders = int.Parse(result.Seeders), Seeders = int.Parse(result.Seeders),
Peers = int.Parse(result.Leechers) + int.Parse(result.Seeders), Peers = int.Parse(result.Leechers) + int.Parse(result.Seeders),
PublishDate = long.TryParse(result.GroupTime, out var num) ? DateTimeOffset.FromUnixTimeSeconds(num).UtcDateTime : DateTimeUtil.FromFuzzyTime(result.GroupTime), PublishDate = long.TryParse(result.GroupTime, out var num) ? DateTimeOffset.FromUnixTimeSeconds(num).UtcDateTime : DateTimeUtil.FromFuzzyTime(result.GroupTime),
Freeleech = result.IsFreeLeech || result.IsPersonalFreeLeech,
Files = result.FileCount, Files = result.FileCount,
Grabs = result.Snatches, Grabs = result.Snatches,
DownloadVolumeFactor = result.IsFreeLeech || result.IsNeutralLeech || result.IsPersonalFreeLeech ? 0 : 1, DownloadVolumeFactor = result.IsFreeLeech || result.IsNeutralLeech || result.IsPersonalFreeLeech ? 0 : 1,
@@ -1,7 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using NLog; using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@@ -10,20 +8,19 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public class PassThePopcorn : TorrentIndexerBase<PassThePopcornSettings> public class PassThePopcorn : TorrentIndexerBase<PassThePopcornSettings>
{ {
public override string Name => "PassThePopcorn"; public override string Name => "PassThePopcorn";
public override string[] IndexerUrls => new string[] { "https://passthepopcorn.me" }; public override string[] IndexerUrls => new[] { "https://passthepopcorn.me" };
public override string Description => "PassThePopcorn (PTP) is a Private site for MOVIES / TV"; public override string Description => "PassThePopcorn (PTP) is a Private site for MOVIES / TV";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent; public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private; public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override bool SupportsRss => true; public override bool SupportsRss => true;
public override bool SupportsSearch => true; public override bool SupportsSearch => true;
public override bool SupportsPagination => true;
public override int PageSize => 50;
public override IndexerCapabilities Capabilities => SetCapabilities(); public override IndexerCapabilities Capabilities => SetCapabilities();
public override int PageSize => 50;
public PassThePopcorn(IIndexerHttpClient httpClient, public PassThePopcorn(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
ICacheManager cacheManager,
IIndexerStatusService indexerStatusService, IIndexerStatusService indexerStatusService,
IConfigService configService, IConfigService configService,
Logger logger) Logger logger)
@@ -33,7 +30,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()
{ {
return new PassThePopcornRequestGenerator() return new PassThePopcornRequestGenerator
{ {
Settings = Settings, Settings = Settings,
HttpClient = _httpClient, HttpClient = _httpClient,
@@ -39,11 +39,6 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public List<string> Tags { get; set; } public List<string> Tags { get; set; }
public List<Director> Directors { get; set; } public List<Director> Directors { get; set; }
public string ImdbId { get; set; } public string ImdbId { get; set; }
public int TotalLeechers { get; set; }
public int TotalSeeders { get; set; }
public int TotalSnatched { get; set; }
public long MaxSize { get; set; }
public string LastUploadTime { get; set; }
public List<Torrent> Torrents { get; set; } public List<Torrent> Torrents { get; set; }
} }
@@ -1,10 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using Newtonsoft.Json;
using NLog; using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers.Exceptions; using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@@ -57,7 +57,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}"); throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from API request, expected {HttpAccept.Json.Value}");
} }
var jsonResponse = JsonConvert.DeserializeObject<PassThePopcornResponse>(indexerResponse.Content); var jsonResponse = STJson.Deserialize<PassThePopcornResponse>(indexerResponse.Content);
if (jsonResponse.TotalResults == "0" || if (jsonResponse.TotalResults == "0" ||
jsonResponse.TotalResults.IsNullOrWhiteSpace() || jsonResponse.TotalResults.IsNullOrWhiteSpace() ||
jsonResponse.Movies == null) jsonResponse.Movies == null)
@@ -94,13 +94,14 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
// Only add approved torrents // Only add approved torrents
try try
{ {
torrentInfos.Add(new TorrentInfo() torrentInfos.Add(new TorrentInfo
{ {
Guid = string.Format("PassThePopcorn-{0}", id), Guid = string.Format("PassThePopcorn-{0}", id),
Title = title, Title = title,
Size = long.Parse(torrent.Size), Size = long.Parse(torrent.Size),
DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey), DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey),
InfoUrl = GetInfoUrl(result.GroupId, id), InfoUrl = GetInfoUrl(result.GroupId, id),
Grabs = int.Parse(torrent.Snatched),
Seeders = int.Parse(torrent.Seeders), Seeders = int.Parse(torrent.Seeders),
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders), Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
PublishDate = torrent.UploadTime.ToUniversalTime(), PublishDate = torrent.UploadTime.ToUniversalTime(),
@@ -128,8 +129,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
} }
} }
return return torrentInfos;
torrentInfos;
} }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; } public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
@@ -24,11 +24,11 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace()) if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
{ {
pageableRequests.Add(GetRequest(searchCriteria.FullImdbId)); pageableRequests.Add(GetRequest(searchCriteria.FullImdbId, searchCriteria));
} }
else else
{ {
pageableRequests.Add(GetRequest(string.Format("{0}", searchCriteria.SearchTerm))); pageableRequests.Add(GetRequest($"{searchCriteria.SearchTerm}", searchCriteria));
} }
return pageableRequests; return pageableRequests;
@@ -37,18 +37,25 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public Func<IDictionary<string, string>> GetCookies { get; set; } public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; } public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
private IEnumerable<IndexerRequest> GetRequest(string searchParameters) private IEnumerable<IndexerRequest> GetRequest(string searchParameters, SearchCriteriaBase searchCriteria)
{ {
var queryParams = new NameValueCollection var queryParams = new NameValueCollection
{ {
{ "action", "advanced" }, { "action", "advanced" },
{ "json", "noredirect" }, { "json", "noredirect" },
{ "grouping", "0" },
{ "searchstr", searchParameters } { "searchstr", searchParameters }
}; };
if (searchCriteria.Limit is > 0 && searchCriteria.Offset is > 0)
{
var page = (int)(searchCriteria.Offset / searchCriteria.Limit) + 1;
queryParams.Set("page", page.ToString());
}
if (Settings.FreeleechOnly) if (Settings.FreeleechOnly)
{ {
queryParams.Add("freetorrent", "1"); queryParams.Set("freetorrent", "1");
} }
var request = var request =
@@ -91,7 +98,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(string.Format("{0}", searchCriteria.SearchTerm))); pageableRequests.Add(GetRequest($"{searchCriteria.SearchTerm}", searchCriteria));
return pageableRequests; return pageableRequests;
} }
@@ -58,7 +58,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Rarbg
case (int)HttpStatusCode.OK: case (int)HttpStatusCode.OK:
break; break;
default: default:
throw new IndexerException(response, "Indexer API call returned an unexpected StatusCode [{0}]", responseCode); throw new IndexerException(response, "Indexer API call returned an unexpected status code [{0}]", responseCode);
} }
} }
@@ -112,9 +112,9 @@ namespace NzbDrone.Core.Indexers.Definitions.Rarbg
return caps; return caps;
} }
protected override async Task<IndexerQueryResult> FetchPage(IndexerRequest request, IParseIndexerResponse parser) protected override async Task<IndexerResponse> FetchIndexerResponse(IndexerRequest request)
{ {
var response = await FetchIndexerResponse(request); var response = await base.FetchIndexerResponse(request);
CheckResponseByStatusCode(response); CheckResponseByStatusCode(response);
@@ -133,36 +133,19 @@ namespace NzbDrone.Core.Indexers.Definitions.Rarbg
qs.Set("token", newToken); qs.Set("token", newToken);
request.HttpRequest.Url = request.Url.SetQuery(qs.GetQueryString()); request.HttpRequest.Url = request.Url.SetQuery(qs.GetQueryString());
response = await FetchIndexerResponse(request);
return await FetchIndexerResponse(request);
} }
else if (jsonResponse.Resource.error_code is 5)
if (jsonResponse.Resource.error_code is 5)
{ {
_logger.Debug("Rarbg temp rate limit hit, retrying request"); _logger.Debug("Rarbg temp rate limit hit, retrying request");
response = await FetchIndexerResponse(request);
return await FetchIndexerResponse(request);
} }
} }
try return response;
{
var releases = parser.ParseResponse(response).ToList();
if (releases.Count == 0)
{
_logger.Trace(response.Content);
}
return new IndexerQueryResult
{
Releases = releases,
Response = response.HttpResponse
};
}
catch (Exception ex)
{
ex.WithData(response.HttpResponse, 128 * 1024);
_logger.Trace("Unexpected Response content ({0} bytes): {1}", response.HttpResponse.ResponseData.Length, response.HttpResponse.Content);
throw;
}
} }
public override object RequestAction(string action, IDictionary<string, string> query) public override object RequestAction(string action, IDictionary<string, string> query)
@@ -46,11 +46,11 @@ namespace NzbDrone.Core.Indexers.Definitions.Rarbg
{ {
requestBuilder.AddQueryParam("search_imdb", imdbId); requestBuilder.AddQueryParam("search_imdb", imdbId);
} }
else if (tmdbId.HasValue && tmdbId > 0) else if (tmdbId is > 0)
{ {
requestBuilder.AddQueryParam("search_themoviedb", tmdbId); requestBuilder.AddQueryParam("search_themoviedb", tmdbId);
} }
else if (tvdbId.HasValue && tvdbId > 0) else if (tvdbId is > 0)
{ {
requestBuilder.AddQueryParam("search_tvdb", tvdbId); requestBuilder.AddQueryParam("search_tvdb", tvdbId);
} }
@@ -47,7 +47,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Rarbg
return response.Resource["token"].ToString(); return response.Resource["token"].ToString();
}, },
TimeSpan.FromMinutes(14.0)); TimeSpan.FromMinutes(14));
} }
} }
} }
@@ -247,7 +247,6 @@ namespace NzbDrone.Core.Indexers.Definitions
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders), Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
PublishDate = torrent.Time.ToUniversalTime(), PublishDate = torrent.Time.ToUniversalTime(),
Scene = torrent.Scene, Scene = torrent.Scene,
Freeleech = torrent.IsFreeLeech || torrent.IsPersonalFreeLeech,
Files = torrent.FileCount, Files = torrent.FileCount,
Grabs = torrent.Snatches, Grabs = torrent.Snatches,
DownloadVolumeFactor = torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsPersonalFreeLeech ? 0 : 1, DownloadVolumeFactor = torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsPersonalFreeLeech ? 0 : 1,
@@ -284,7 +283,6 @@ namespace NzbDrone.Core.Indexers.Definitions
Seeders = int.Parse(result.Seeders), Seeders = int.Parse(result.Seeders),
Peers = int.Parse(result.Leechers) + int.Parse(result.Seeders), Peers = int.Parse(result.Leechers) + int.Parse(result.Seeders),
PublishDate = DateTimeOffset.FromUnixTimeSeconds(ParseUtil.CoerceLong(result.GroupTime)).UtcDateTime, PublishDate = DateTimeOffset.FromUnixTimeSeconds(ParseUtil.CoerceLong(result.GroupTime)).UtcDateTime,
Freeleech = result.IsFreeLeech || result.IsPersonalFreeLeech,
Files = result.FileCount, Files = result.FileCount,
Grabs = result.Snatches, Grabs = result.Snatches,
DownloadVolumeFactor = result.IsFreeLeech || result.IsNeutralLeech || result.IsPersonalFreeLeech ? 0 : 1, DownloadVolumeFactor = result.IsFreeLeech || result.IsNeutralLeech || result.IsPersonalFreeLeech ? 0 : 1,
@@ -27,7 +27,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public override string[] IndexerUrls => new[] public override string[] IndexerUrls => new[]
{ {
"https://rutracker.org/", "https://rutracker.org/",
"https://rutracker.net/" "https://rutracker.net/",
"https://rutracker.nl/"
}; };
public override string Description => "RuTracker is a Semi-Private Russian torrent site with a thriving file-sharing community"; public override string Description => "RuTracker is a Semi-Private Russian torrent site with a thriving file-sharing community";
public override string Language => "ru-RU"; public override string Language => "ru-RU";
@@ -72,7 +72,7 @@ public class SecretCinemaParser : IParseIndexerResponse
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse) public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{ {
var torrentInfos = new List<ReleaseInfo>(); var releaseInfos = new List<ReleaseInfo>();
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK) if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{ {
@@ -95,7 +95,7 @@ public class SecretCinemaParser : IParseIndexerResponse
jsonResponse.Resource.Status.IsNullOrWhiteSpace() || jsonResponse.Resource.Status.IsNullOrWhiteSpace() ||
jsonResponse.Resource.Response == null) jsonResponse.Resource.Response == null)
{ {
return torrentInfos; return releaseInfos.ToArray();
} }
foreach (var result in jsonResponse.Resource.Response.Results) foreach (var result in jsonResponse.Resource.Response.Results)
@@ -109,10 +109,11 @@ public class SecretCinemaParser : IParseIndexerResponse
// in SC movies, artist=director and GroupName=title // in SC movies, artist=director and GroupName=title
var artist = WebUtility.HtmlDecode(result.Artist); var artist = WebUtility.HtmlDecode(result.Artist);
var title = WebUtility.HtmlDecode(result.GroupName); var title = WebUtility.HtmlDecode(result.GroupName);
var time = DateTime.SpecifyKind(torrent.Time, DateTimeKind.Unspecified);
var release = new GazelleInfo var release = new GazelleInfo
{ {
Guid = string.Format("SecretCinema-{0}", id), Guid = $"SecretCinema-{id}",
Title = title, Title = title,
Container = torrent.Encoding, Container = torrent.Encoding,
Files = torrent.FileCount, Files = torrent.FileCount,
@@ -123,7 +124,7 @@ public class SecretCinemaParser : IParseIndexerResponse
InfoUrl = GetInfoUrl(result.GroupId, id), InfoUrl = GetInfoUrl(result.GroupId, id),
Seeders = int.Parse(torrent.Seeders), Seeders = int.Parse(torrent.Seeders),
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders), Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
PublishDate = torrent.Time.ToUniversalTime(), PublishDate = new DateTimeOffset(time, TimeSpan.FromHours(2)).UtcDateTime,
Scene = torrent.Scene, Scene = torrent.Scene,
DownloadVolumeFactor = torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsPersonalFreeLeech ? 0 : 1, DownloadVolumeFactor = torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsPersonalFreeLeech ? 0 : 1,
UploadVolumeFactor = torrent.IsNeutralLeech ? 0 : 1 UploadVolumeFactor = torrent.IsNeutralLeech ? 0 : 1
@@ -162,7 +163,7 @@ public class SecretCinemaParser : IParseIndexerResponse
release.Title += " [Cue]"; release.Title += " [Cue]";
} }
torrentInfos.Add(release); releaseInfos.Add(release);
} }
} }
else else
@@ -172,7 +173,7 @@ public class SecretCinemaParser : IParseIndexerResponse
var release = new GazelleInfo var release = new GazelleInfo
{ {
Guid = string.Format("SecretCinema-{0}", id), Guid = $"SecretCinema-{id}",
Title = groupName, Title = groupName,
Size = long.Parse(result.Size), Size = long.Parse(result.Size),
DownloadUrl = GetDownloadUrl(id), DownloadUrl = GetDownloadUrl(id),
@@ -196,13 +197,13 @@ public class SecretCinemaParser : IParseIndexerResponse
release.Categories = _capabilities.Categories.MapTrackerCatDescToNewznab(category); release.Categories = _capabilities.Categories.MapTrackerCatDescToNewznab(category);
} }
torrentInfos.Add(release); releaseInfos.Add(release);
} }
} }
// order by date // order by date
return return
torrentInfos releaseInfos
.OrderByDescending(o => o.PublishDate) .OrderByDescending(o => o.PublishDate)
.ToArray(); .ToArray();
} }
@@ -323,6 +323,11 @@ namespace NzbDrone.Core.Indexers.Definitions
{ "nm", term.IsNotNullOrWhiteSpace() ? term.Replace("-", " ") : "" } { "nm", term.IsNotNullOrWhiteSpace() ? term.Replace("-", " ") : "" }
}; };
if (_settings.FreeleechOnly)
{
parameters.Add("sds", "1");
}
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(categories); var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(categories);
if (queryCats.Any()) if (queryCats.Any())
{ {
@@ -408,6 +413,19 @@ namespace NzbDrone.Core.Indexers.Definitions
MinimumSeedTime = 0 MinimumSeedTime = 0
}; };
if (row.QuerySelector("img[src=\"images/gold.gif\"], img[src=\"images/authors.gif\"]") != null)
{
release.DownloadVolumeFactor = 0;
}
else if (row.QuerySelector("img[src=\"images/silver.gif\"]") != null)
{
release.DownloadVolumeFactor = 0.5;
}
else if (row.QuerySelector("img[src=\"images/bronze.gif\"]") != null)
{
release.DownloadVolumeFactor = 0.75;
}
releaseInfos.Add(release); releaseInfos.Add(release);
} }
@@ -540,7 +558,10 @@ namespace NzbDrone.Core.Indexers.Definitions
StripCyrillicLetters = true; StripCyrillicLetters = true;
} }
[FieldDefinition(4, Label = "Strip Cyrillic Letters", Type = FieldType.Checkbox)] [FieldDefinition(4, Label = "Freeleech Only", HelpText = "Search Freeleech torrents only", Type = FieldType.Checkbox)]
public bool FreeleechOnly { get; set; }
[FieldDefinition(5, Label = "Strip Cyrillic Letters", Type = FieldType.Checkbox)]
public bool StripCyrillicLetters { get; set; } public bool StripCyrillicLetters { get; set; }
} }
} }
@@ -1,15 +1,14 @@
using System; using System;
using NLog; using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.TorrentPotato namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
{ {
public class TorrentPotato : TorrentIndexerBase<TorrentPotatoSettings> public class TorrentPotato : TorrentIndexerBase<TorrentPotatoSettings>
{ {
public override string Name => "TorrentPotato"; public override string Name => "TorrentPotato";
public override string[] IndexerUrls => new string[] { "http://127.0.0.1" }; public override string[] IndexerUrls => new[] { "http://127.0.0.1" };
public override string Description => "A JSON based torrent provider previously developed for CouchPotato"; public override string Description => "A JSON based torrent provider previously developed for CouchPotato";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent; public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
@@ -6,7 +6,7 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Exceptions; using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.TorrentPotato namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
{ {
public class TorrentPotatoParser : IParseIndexerResponse public class TorrentPotatoParser : IParseIndexerResponse
{ {
@@ -31,17 +31,17 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
foreach (var torrent in jsonResponse.Resource.results) foreach (var torrent in jsonResponse.Resource.results)
{ {
var torrentInfo = new TorrentInfo(); var torrentInfo = new TorrentInfo
{
torrentInfo.Guid = GetGuid(torrent); Guid = GetGuid(torrent),
torrentInfo.Title = torrent.release_name; Title = torrent.release_name,
torrentInfo.Size = (long)torrent.size * 1000 * 1000; Size = (long)torrent.size * 1000 * 1000,
torrentInfo.DownloadUrl = torrent.download_url; DownloadUrl = torrent.download_url,
torrentInfo.InfoUrl = torrent.details_url; InfoUrl = torrent.details_url,
torrentInfo.PublishDate = torrent.publish_date.ToUniversalTime(); PublishDate = torrent.publish_date.ToUniversalTime(),
torrentInfo.Seeders = torrent.seeders; Seeders = torrent.seeders,
torrentInfo.Peers = torrent.leechers + torrent.seeders; Peers = torrent.leechers + torrent.seeders
torrentInfo.Freeleech = torrent.freeleech; };
results.Add(torrentInfo); results.Add(torrentInfo);
} }
@@ -4,16 +4,12 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
namespace NzbDrone.Core.Indexers.TorrentPotato namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
{ {
public class TorrentPotatoRequestGenerator : IIndexerRequestGenerator public class TorrentPotatoRequestGenerator : IIndexerRequestGenerator
{ {
public TorrentPotatoSettings Settings { get; set; } public TorrentPotatoSettings Settings { get; set; }
public TorrentPotatoRequestGenerator()
{
}
public virtual IndexerPageableRequestChain GetRecentRequests() public virtual IndexerPageableRequestChain GetRecentRequests()
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
@@ -1,6 +1,6 @@
using System; using System;
namespace NzbDrone.Core.Indexers.TorrentPotato namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
{ {
public class TorrentPotatoResponse public class TorrentPotatoResponse
{ {
@@ -3,7 +3,7 @@ using NzbDrone.Core.Annotations;
using NzbDrone.Core.Indexers.Settings; using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.Validation; using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.TorrentPotato namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
{ {
public class TorrentPotatoSettingsValidator : NoAuthSettingsValidator<TorrentPotatoSettings> public class TorrentPotatoSettingsValidator : NoAuthSettingsValidator<TorrentPotatoSettings>
{ {
@@ -508,9 +508,12 @@ namespace NzbDrone.Core.Indexers
} }
// Throw common http errors here before we try to parse // Throw common http errors here before we try to parse
if (response.HasHttpError) if (response.HasHttpError && (request.HttpRequest.SuppressHttpErrorStatusCodes == null || !request.HttpRequest.SuppressHttpErrorStatusCodes.Contains(response.StatusCode)))
{ {
_logger.Warn("HTTP Error - {0}", response); if (response.Request.LogHttpError)
{
_logger.Warn("HTTP Error - {0}", response);
}
if (response.StatusCode == HttpStatusCode.TooManyRequests) if (response.StatusCode == HttpStatusCode.TooManyRequests)
{ {
@@ -644,7 +647,7 @@ namespace NzbDrone.Core.Indexers
{ {
_logger.Warn(ex, "Unable to connect to indexer"); _logger.Warn(ex, "Unable to connect to indexer");
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details"); return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details. " + ex.Message);
} }
return null; return null;
@@ -93,7 +93,7 @@ namespace NzbDrone.Core.Indexers
{ {
if (string.IsNullOrWhiteSpace(input)) if (string.IsNullOrWhiteSpace(input))
{ {
return new List<IndexerCategory>(); return Array.Empty<IndexerCategory>();
} }
var cats = _categoryMapping var cats = _categoryMapping
@@ -109,7 +109,7 @@ namespace NzbDrone.Core.Indexers
{ {
if (string.IsNullOrWhiteSpace(trackerCategoryDesc)) if (string.IsNullOrWhiteSpace(trackerCategoryDesc))
{ {
return new List<IndexerCategory>(); return Array.Empty<IndexerCategory>();
} }
var cats = _categoryMapping var cats = _categoryMapping
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Indexers.Cardigann; using NzbDrone.Core.Indexers.Definitions.Cardigann;
using NzbDrone.Core.Profiles; using NzbDrone.Core.Profiles;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
+10 -10
View File
@@ -5,7 +5,7 @@ using System.Text;
using FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Indexers.Cardigann; using NzbDrone.Core.Indexers.Definitions.Cardigann;
using NzbDrone.Core.Indexers.Newznab; using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.IndexerVersions; using NzbDrone.Core.IndexerVersions;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@@ -49,7 +49,7 @@ namespace NzbDrone.Core.Indexers
foreach (var definition in definitions) foreach (var definition in definitions)
{ {
if (definition.Implementation == typeof(Cardigann.Cardigann).Name) if (definition.Implementation == nameof(Cardigann))
{ {
try try
{ {
@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Indexers
{ {
var definition = base.Get(id); var definition = base.Get(id);
if (definition.Implementation == typeof(Cardigann.Cardigann).Name) if (definition.Implementation == nameof(Cardigann))
{ {
try try
{ {
@@ -177,7 +177,7 @@ namespace NzbDrone.Core.Indexers
} }
var definitions = provider.DefaultDefinitions var definitions = provider.DefaultDefinitions
.Where(v => v.Name != null && v.Name != nameof(Cardigann.Cardigann) && v.Name != nameof(Newznab.Newznab) && v.Name != nameof(Torznab.Torznab)); .Where(v => v.Name != null && v.Name != nameof(Cardigann) && v.Name != nameof(Newznab.Newznab) && v.Name != nameof(Torznab.Torznab));
foreach (IndexerDefinition definition in definitions) foreach (IndexerDefinition definition in definitions)
{ {
@@ -189,7 +189,7 @@ namespace NzbDrone.Core.Indexers
public override IEnumerable<IndexerDefinition> GetPresetDefinitions(IndexerDefinition providerDefinition) public override IEnumerable<IndexerDefinition> GetPresetDefinitions(IndexerDefinition providerDefinition)
{ {
return new List<IndexerDefinition>(); return Array.Empty<IndexerDefinition>();
} }
public override void SetProviderCharacteristics(IIndexer provider, IndexerDefinition definition) public override void SetProviderCharacteristics(IIndexer provider, IndexerDefinition definition)
@@ -203,7 +203,7 @@ namespace NzbDrone.Core.Indexers
definition.SupportsPagination = provider.SupportsPagination; definition.SupportsPagination = provider.SupportsPagination;
//We want to use the definition Caps and Privacy for Cardigann instead of the provider. //We want to use the definition Caps and Privacy for Cardigann instead of the provider.
if (definition.Implementation != nameof(Cardigann.Cardigann)) if (definition.Implementation != nameof(Cardigann))
{ {
definition.IndexerUrls = provider.IndexerUrls; definition.IndexerUrls = provider.IndexerUrls;
definition.LegacyUrls = provider.LegacyUrls; definition.LegacyUrls = provider.LegacyUrls;
@@ -276,13 +276,13 @@ namespace NzbDrone.Core.Indexers
SetProviderCharacteristics(provider, definition); SetProviderCharacteristics(provider, definition);
if (definition.Implementation == typeof(Newznab.Newznab).Name || definition.Implementation == typeof(Torznab.Torznab).Name) if (definition.Implementation is nameof(Newznab.Newznab) or nameof(Torznab.Torznab))
{ {
var settings = (NewznabSettings)definition.Settings; var settings = (NewznabSettings)definition.Settings;
settings.Categories = _newznabCapabilitiesProvider.GetCapabilities(settings, definition)?.Categories.GetTorznabCategoryList() ?? null; settings.Categories = _newznabCapabilitiesProvider.GetCapabilities(settings, definition)?.Categories.GetTorznabCategoryList() ?? null;
} }
if (definition.Implementation == typeof(Cardigann.Cardigann).Name) if (definition.Implementation == nameof(Cardigann))
{ {
MapCardigannDefinition(definition); MapCardigannDefinition(definition);
} }
@@ -296,13 +296,13 @@ namespace NzbDrone.Core.Indexers
SetProviderCharacteristics(provider, definition); SetProviderCharacteristics(provider, definition);
if (definition.Enable && (definition.Implementation == typeof(Newznab.Newznab).Name || definition.Implementation == typeof(Torznab.Torznab).Name)) if (definition.Enable && definition.Implementation is nameof(Newznab.Newznab) or nameof(Torznab.Torznab))
{ {
var settings = (NewznabSettings)definition.Settings; var settings = (NewznabSettings)definition.Settings;
settings.Categories = _newznabCapabilitiesProvider.GetCapabilities(settings, definition)?.Categories.GetTorznabCategoryList() ?? null; settings.Categories = _newznabCapabilitiesProvider.GetCapabilities(settings, definition)?.Categories.GetTorznabCategoryList() ?? null;
} }
if (definition.Implementation == typeof(Cardigann.Cardigann).Name) if (definition.Implementation == nameof(Cardigann))
{ {
MapCardigannDefinition(definition); MapCardigannDefinition(definition);
} }

Some files were not shown because too many files have changed in this diff Show More