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

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!
validations:
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

View File

@@ -9,13 +9,13 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '1.3.2'
majorVersion: '1.4.0'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.405'
dotnetVersion: '6.0.408'
innoVersion: '6.2.0'
nodeVersion: '16.x'
windowsImage: 'windows-2022'

View File

@@ -112,6 +112,12 @@ class TextInput extends Component {
this._isMouseTarget = false;
};
onWheel = () => {
if (this.props.type === 'number') {
this._input.blur();
}
};
//
// Render
@@ -161,6 +167,7 @@ class TextInput extends Component {
onKeyUp={this.onKeyUp}
onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp}
onWheel={this.onWheel}
/>
);
}

View File

@@ -17,6 +17,7 @@ function TagDetailsModalContent(props) {
indexers,
notifications,
indexerProxies,
applications,
onModalClose,
onDeleteTagPress
} = props;
@@ -79,6 +80,21 @@ function TagDetailsModalContent(props) {
}
</FieldSet>
}
{
!!applications.length &&
<FieldSet legend={translate('Applications')}>
{
applications.map((item) => {
return (
<div key={item.id}>
{item.name}
</div>
);
})
}
</FieldSet>
}
</ModalBody>
<ModalFooter>
@@ -110,6 +126,7 @@ TagDetailsModalContent.propTypes = {
indexers: PropTypes.arrayOf(PropTypes.object).isRequired,
notifications: PropTypes.arrayOf(PropTypes.object).isRequired,
indexerProxies: PropTypes.arrayOf(PropTypes.object).isRequired,
applications: PropTypes.arrayOf(PropTypes.object).isRequired,
onModalClose: PropTypes.func.isRequired,
onDeleteTagPress: PropTypes.func.isRequired
};

View File

@@ -18,16 +18,24 @@ function createMatchingIndexersSelector() {
function createMatchingIndexerProxiesSelector() {
return createSelector(
(state, { notificationIds }) => notificationIds,
(state) => state.settings.notifications.items,
(state, { indexerProxyIds }) => indexerProxyIds,
(state) => state.settings.indexerProxies.items,
findMatchingItems
);
}
function createMatchingNotificationsSelector() {
return createSelector(
(state, { indexerProxyIds }) => indexerProxyIds,
(state) => state.settings.indexerProxies.items,
(state, { notificationIds }) => notificationIds,
(state) => state.settings.notifications.items,
findMatchingItems
);
}
function createMatchingApplicationsSelector() {
return createSelector(
(state, { applicationIds }) => applicationIds,
(state) => state.settings.applications.items,
findMatchingItems
);
}
@@ -37,11 +45,13 @@ function createMapStateToProps() {
createMatchingIndexersSelector(),
createMatchingIndexerProxiesSelector(),
createMatchingNotificationsSelector(),
(indexers, indexerProxies, notifications) => {
createMatchingApplicationsSelector(),
(indexers, indexerProxies, notifications, applications) => {
return {
indexers,
indexerProxies,
notifications
notifications,
applications
};
}
);

View File

@@ -55,7 +55,8 @@ class Tag extends Component {
label,
notificationIds,
indexerIds,
indexerProxyIds
indexerProxyIds,
applicationIds
} = this.props;
const {
@@ -66,7 +67,8 @@ class Tag extends Component {
const isTagUsed = !!(
indexerIds.length ||
notificationIds.length ||
indexerProxyIds.length
indexerProxyIds.length ||
applicationIds.length
);
return (
@@ -102,6 +104,13 @@ class Tag extends Component {
{indexerProxyIds.length} {indexerProxyIds.length > 1 ? translate('IndexerProxies') : translate('IndexerProxy')}
</div>
}
{
!!applicationIds.length &&
<div>
{applicationIds.length} {applicationIds.length > 1 ? translate('Applications') : translate('Application')}
</div>
}
</div>
}
@@ -118,6 +127,7 @@ class Tag extends Component {
indexerIds={indexerIds}
notificationIds={notificationIds}
indexerProxyIds={indexerProxyIds}
applicationIds={applicationIds}
isOpen={isDetailsModalOpen}
onModalClose={this.onDetailsModalClose}
onDeleteTagPress={this.onDeleteTagPress}
@@ -143,13 +153,15 @@ Tag.propTypes = {
notificationIds: PropTypes.arrayOf(PropTypes.number).isRequired,
indexerIds: PropTypes.arrayOf(PropTypes.number).isRequired,
indexerProxyIds: PropTypes.arrayOf(PropTypes.number).isRequired,
applicationIds: PropTypes.arrayOf(PropTypes.number).isRequired,
onConfirmDeleteTag: PropTypes.func.isRequired
};
Tag.defaultProps = {
indexerIds: [],
notificationIds: [],
indexerProxyIds: []
indexerProxyIds: [],
applicationIds: []
};
export default Tag;

View File

@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
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 Tags from './Tags';
@@ -27,7 +27,8 @@ function createMapStateToProps() {
const mapDispatchToProps = {
dispatchFetchTagDetails: fetchTagDetails,
dispatchFetchNotifications: fetchNotifications,
dispatchFetchIndexerProxies: fetchIndexerProxies
dispatchFetchIndexerProxies: fetchIndexerProxies,
dispatchFetchApplications: fetchApplications
};
class MetadatasConnector extends Component {
@@ -39,12 +40,14 @@ class MetadatasConnector extends Component {
const {
dispatchFetchTagDetails,
dispatchFetchNotifications,
dispatchFetchIndexerProxies
dispatchFetchIndexerProxies,
dispatchFetchApplications
} = this.props;
dispatchFetchTagDetails();
dispatchFetchNotifications();
dispatchFetchIndexerProxies();
dispatchFetchApplications();
}
//
@@ -62,7 +65,8 @@ class MetadatasConnector extends Component {
MetadatasConnector.propTypes = {
dispatchFetchTagDetails: 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);

View File

@@ -144,7 +144,7 @@ export const defaultState = {
},
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 firstCat = sortedCats[0];

View File

@@ -76,27 +76,27 @@ module.exports = {
defaultButtonBackgroundColor: '#333',
defaultBorderColor: '#eaeaea',
defaultHoverBackgroundColor: '#444',
defaultHoverBorderColor: '#d6d6d6;',
defaultHoverBorderColor: '#d6d6d6',
primaryBackgroundColor: '#5d9cec',
primaryBorderColor: '#5899eb',
primaryHoverBackgroundColor: '#4b91ea',
primaryHoverBorderColor: '#3483e7;',
primaryHoverBorderColor: '#3483e7',
successBackgroundColor: '#27c24c',
successBorderColor: '#26be4a',
successHoverBackgroundColor: '#24b145',
successHoverBorderColor: '#1f9c3d;',
successHoverBorderColor: '#1f9c3d',
warningBackgroundColor: '#ff902b',
warningBorderColor: '#ff8d26',
warningHoverBackgroundColor: '#ff8517',
warningHoverBorderColor: '#fc7800;',
warningHoverBorderColor: '#fc7800',
dangerBackgroundColor: '#f05050',
dangerBorderColor: '#f04b4b',
dangerHoverBackgroundColor: '#ee3d3d',
dangerHoverBorderColor: '#ec2626;',
dangerHoverBorderColor: '#ec2626',
iconButtonDisabledColor: '#7a7a7a',
iconButtonHoverColor: '#666',

View File

@@ -76,27 +76,27 @@ module.exports = {
defaultButtonBackgroundColor: '#fff',
defaultBorderColor: '#eaeaea',
defaultHoverBackgroundColor: '#f5f5f5',
defaultHoverBorderColor: '#d6d6d6;',
defaultHoverBorderColor: '#d6d6d6',
primaryBackgroundColor: '#5d9cec',
primaryBorderColor: '#5899eb',
primaryHoverBackgroundColor: '#4b91ea',
primaryHoverBorderColor: '#3483e7;',
primaryHoverBorderColor: '#3483e7',
successBackgroundColor: '#27c24c',
successBorderColor: '#26be4a',
successHoverBackgroundColor: '#24b145',
successHoverBorderColor: '#1f9c3d;',
successHoverBorderColor: '#1f9c3d',
warningBackgroundColor: '#ff902b',
warningBorderColor: '#ff8d26',
warningHoverBackgroundColor: '#ff8517',
warningHoverBorderColor: '#fc7800;',
warningHoverBorderColor: '#fc7800',
dangerBackgroundColor: '#f05050',
dangerBorderColor: '#f04b4b',
dangerHoverBackgroundColor: '#ee3d3d',
dangerHoverBorderColor: '#ec2626;',
dangerHoverBorderColor: '#ec2626',
iconButtonDisabledColor: '#7a7a7a',
iconButtonHoverColor: '#666',

View File

@@ -5,7 +5,7 @@
"scripts": {
"build": "webpack --config ./frontend/build/webpack.config.js",
"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",
"watch": "webpack --watch --config ./frontend/build/webpack.config.js",
"lint": "eslint --config frontend/.eslintrc.js --ignore-path frontend/.eslintignore frontend/",
@@ -26,36 +26,36 @@
"not chrome < 60"
],
"dependencies": {
"@fortawesome/fontawesome-free": "6.2.1",
"@fortawesome/fontawesome-svg-core": "6.2.1",
"@fortawesome/free-regular-svg-icons": "6.2.1",
"@fortawesome/free-solid-svg-icons": "6.2.1",
"@fortawesome/fontawesome-free": "6.4.0",
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-regular-svg-icons": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.4.0",
"@fortawesome/react-fontawesome": "0.2.0",
"@juggle/resize-observer": "3.4.0",
"@microsoft/signalr": "6.0.13",
"@sentry/browser": "7.28.0",
"@sentry/integrations": "7.28.0",
"@types/jest": "29.2.5",
"@types/node": "18.11.18",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
"chart.js": "4.1.1",
"@microsoft/signalr": "6.0.16",
"@sentry/browser": "7.46.0",
"@sentry/integrations": "7.46.0",
"@types/jest": "29.5.0",
"@types/node": "18.15.11",
"@types/react": "18.0.31",
"@types/react-dom": "18.0.11",
"chart.js": "4.2.1",
"classnames": "2.3.2",
"clipboard": "2.0.11",
"connected-react-router": "6.9.3",
"element-class": "0.2.2",
"filesize": "10.0.6",
"filesize": "10.0.7",
"history": "4.10.1",
"https-browserify": "1.0.0",
"jdu": "1.0.0",
"jquery": "3.6.2",
"jquery": "3.6.4",
"lodash": "4.17.21",
"mobile-detect": "1.4.5",
"moment": "2.29.4",
"mousetrap": "1.6.5",
"normalize.css": "8.0.1",
"prop-types": "15.8.1",
"qs": "6.11.0",
"qs": "6.11.1",
"react": "17.0.2",
"react-addons-shallow-compare": "15.6.3",
"react-async-script": "1.2.0",
@@ -67,7 +67,7 @@
"react-dnd-touch-backend": "14.1.1",
"react-document-title": "2.0.3",
"react-dom": "17.0.2",
"react-focus-lock": "2.9.2",
"react-focus-lock": "2.9.4",
"react-google-recaptcha": "2.1.0",
"react-lazyload": "3.2.0",
"react-measure": "1.4.7",
@@ -77,78 +77,77 @@
"react-router-dom": "5.2.0",
"react-text-truncate": "0.19.0",
"react-use-measure": "2.1.1",
"react-virtualized": "9.21.1",
"react-virtualized": "9.22.3",
"react-window": "1.8.8",
"redux": "4.2.0",
"redux": "4.2.1",
"redux-actions": "2.6.5",
"redux-batched-actions": "0.5.0",
"redux-localstorage": "0.4.1",
"redux-thunk": "2.4.2",
"reselect": "4.1.7",
"stacktrace-js": "2.0.2",
"typescript": "4.9.4"
"typescript": "5.0.3"
},
"devDependencies": {
"@babel/core": "7.20.5",
"@babel/eslint-parser": "7.19.1",
"@babel/core": "7.21.3",
"@babel/eslint-parser": "7.21.3",
"@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-namespace-from": "7.18.9",
"@babel/plugin-proposal-function-sent": "7.18.6",
"@babel/plugin-proposal-nullish-coalescing-operator": "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-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.20.2",
"@babel/preset-react": "7.18.6",
"@babel/preset-typescript": "7.18.6",
"@babel/preset-typescript": "7.21.0",
"@types/react-window": "1.8.5",
"@typescript-eslint/eslint-plugin": "5.48.1",
"@typescript-eslint/parser": "5.48.0",
"@typescript-eslint/eslint-plugin": "5.57.0",
"@typescript-eslint/parser": "5.57.0",
"are-you-es5": "2.1.2",
"autoprefixer": "10.4.13",
"babel-eslint": "10.1.0",
"babel-loader": "9.1.0",
"autoprefixer": "10.4.14",
"babel-loader": "9.1.2",
"babel-plugin-inline-classnames": "2.0.1",
"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-modules-typescript-loader": "4.0.1",
"eslint": "8.30.0",
"eslint-config-prettier": "8.6.0",
"eslint": "8.37.0",
"eslint-config-prettier": "8.8.0",
"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-react": "7.31.11",
"eslint-plugin-react": "7.32.2",
"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",
"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",
"loader-utils": "^3.2.1",
"mini-css-extract-plugin": "2.7.2",
"postcss": "8.4.20",
"mini-css-extract-plugin": "2.7.5",
"postcss": "8.4.21",
"postcss-color-function": "4.1.0",
"postcss-loader": "7.0.2",
"postcss-loader": "7.1.0",
"postcss-mixins": "9.0.4",
"postcss-nested": "6.0.0",
"postcss-nested": "6.0.1",
"postcss-simple-vars": "7.0.1",
"postcss-url": "10.1.3",
"prettier": "2.8.2",
"prettier": "2.8.7",
"require-nocache": "1.0.0",
"rimraf": "3.0.2",
"rimraf": "4.4.1",
"run-sequence": "2.2.1",
"streamqueue": "1.1.2",
"style-loader": "3.3.1",
"style-loader": "3.3.2",
"stylelint": "14.16.0",
"stylelint-order": "5.0.0",
"ts-loader": "9.4.2",
"typescript-plugin-css-modules": "4.1.1",
"typescript-plugin-css-modules": "5.0.0",
"url-loader": "4.1.1",
"webpack": "5.75.0",
"webpack": "5.77.0",
"webpack-cli": "5.0.1",
"webpack-livereload-plugin": "3.0.2"
}

View File

@@ -74,6 +74,8 @@
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<Deterministic Condition="$(AssemblyVersion.EndsWith('*'))">False</Deterministic>
<PathMap>$(MSBuildProjectDirectory)=./$(MSBuildProjectName)/</PathMap>
</PropertyGroup>
<!-- Set the AssemblyConfiguration attribute for projects -->

View File

@@ -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));
}
}
}

View File

@@ -5,8 +5,6 @@ using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Text;
using System.Threading.Tasks;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common.Composition
@@ -19,16 +17,17 @@ namespace NzbDrone.Common.Composition
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(OsInfo.IsWindows ? "Prowlarr.Windows" : "Prowlarr.Mono");
var startupPath = AppDomain.CurrentDomain.BaseDirectory;
return toLoad.Select(x =>
AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(startupPath, $"{x}.dll")));
return toLoad
.Select(x => AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(startupPath, $"{x}.dll")))
.ToList();
}
private static Assembly ContainerResolveEventHandler(object sender, ResolveEventArgs args)

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();
@@ -478,8 +478,7 @@ namespace NzbDrone.Common.Disk
return mounts.Where(drive => drive.RootDirectory.PathEquals(path) ||
drive.RootDirectory.IsParentPath(path))
.OrderByDescending(drive => drive.RootDirectory.Length)
.FirstOrDefault();
.MaxBy(drive => drive.RootDirectory.Length);
}
catch (Exception ex)
{

View File

@@ -16,18 +16,7 @@ namespace NzbDrone.Common.Extensions
return false;
}
Uri uri;
if (!Uri.TryCreate(path, UriKind.Absolute, out uri))
{
return false;
}
if (!uri.IsWellFormedOriginalString())
{
return false;
}
return true;
return Uri.TryCreate(path, UriKind.Absolute, out var uri) && uri.IsWellFormedOriginalString();
}
}
}

View File

@@ -9,7 +9,7 @@ namespace NzbDrone.Common.Http
{
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
// 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 char[] InvalidKeyChars = { '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t', '\n' };
private static readonly char[] InvalidValueChars = { '"', ',', ';', '\\', ' ', '\t', '\n' };
@@ -22,7 +22,7 @@ namespace NzbDrone.Common.Http
return cookieDictionary;
}
var matches = _CookieRegex.Match(cookieHeader);
var matches = CookieRegex.Match(cookieHeader);
while (matches.Success)
{
if (matches.Groups.Count > 2 && !FilterProps.Contains(matches.Groups[1].Value.ToUpperInvariant()))

View File

@@ -21,6 +21,7 @@ namespace NzbDrone.Common.Http
public Dictionary<string, string> Segments { get; private set; }
public HttpHeader Headers { get; private set; }
public bool SuppressHttpError { get; set; }
public IEnumerable<HttpStatusCode> SuppressHttpErrorStatusCodes { get; set; }
public bool LogHttpError { get; set; }
public bool UseSimplifiedUserAgent { get; set; }
public bool AllowAutoRedirect { get; set; }
@@ -108,6 +109,7 @@ namespace NzbDrone.Common.Http
request.Method = Method;
request.Encoding = Encoding;
request.SuppressHttpError = SuppressHttpError;
request.SuppressHttpErrorStatusCodes = SuppressHttpErrorStatusCodes;
request.LogHttpError = LogHttpError;
request.UseSimplifiedUserAgent = UseSimplifiedUserAgent;
request.AllowAutoRedirect = AllowAutoRedirect;

View File

@@ -14,14 +14,14 @@ namespace NzbDrone.Common.OAuth
{
get
{
var parameters = this.Where(p => p.Name.Equals(name));
var parameters = this.Where(p => p.Name.Equals(name)).ToArray();
if (!parameters.Any())
{
return null;
}
if (parameters.Count() == 1)
if (parameters.Length == 1)
{
return parameters.Single();
}

View File

@@ -4,16 +4,16 @@
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
</PropertyGroup>
<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.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.Extensions.Logging" Version="5.2.0" />
<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="SharpZipLib" Version="1.3.3" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" />

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -22,10 +22,10 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
Subject.Definition = new IndexerDefinition
{
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))
.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.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.CommentUrl.Should().BeNullOrEmpty();
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.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -22,10 +22,10 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
Subject.Definition = new IndexerDefinition
{
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))
.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.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.CommentUrl.Should().BeNullOrEmpty();
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.InfoHash.Should().Be("asdjfiasdf54asd7f4a2sdf544asdf");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -22,10 +22,10 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
Subject.Definition = new IndexerDefinition
{
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))
.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.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.CommentUrl.Should().BeNullOrEmpty();
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.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Indexers.Cardigann;
using NzbDrone.Core.Indexers.Definitions.Cardigann;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests.CardigannTests

View File

@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
torrentInfo.InfoUrl.Should().Be("https://filelist.io/details.php?id=665873");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
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.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
Subject.Definition = new IndexerDefinition
{
Name = "Orpheus",
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))
.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.First().Should().BeOfType<GazelleInfo>();
@@ -56,6 +56,7 @@ namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
torrentInfo.MagnetUrl.Should().Be(null);
torrentInfo.Peers.Should().Be(0);
torrentInfo.Seeders.Should().Be(0);
torrentInfo.Files.Should().Be(18);
torrentInfo.ImdbId.Should().Be(0);
torrentInfo.TmdbId.Should().Be(0);
torrentInfo.TvdbId.Should().Be(0);

View File

@@ -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);
}
}
}

View File

@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Test.IndexerTests.SecretCinemaTests
Subject.Definition = new IndexerDefinition()
{
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))
.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.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.CommentUrl.Should().BeNullOrEmpty();
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.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -19,10 +19,12 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
public class LazyLibrarianSettings : IApplicationSettings
{
private static readonly LazyLibrarianSettingsValidator Validator = new LazyLibrarianSettingsValidator();
private static readonly LazyLibrarianSettingsValidator Validator = new ();
public LazyLibrarianSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:5299";
SyncCategories = new[]
{
NewznabStandardCategory.AudioAudiobook.Id,

View File

@@ -18,10 +18,12 @@ namespace NzbDrone.Core.Applications.Lidarr
public class LidarrSettings : IApplicationSettings
{
private static readonly LidarrSettingsValidator Validator = new LidarrSettingsValidator();
private static readonly LidarrSettingsValidator Validator = new ();
public LidarrSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8686";
SyncCategories = new[] { 3000, 3010, 3030, 3040, 3050, 3060 };
}

View File

@@ -23,6 +23,8 @@ namespace NzbDrone.Core.Applications.Lidarr
public class LidarrV1Proxy : ILidarrV1Proxy
{
private const string AppApiRoute = "/api/v1";
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
@@ -34,13 +36,13 @@ namespace NzbDrone.Core.Applications.Lidarr
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);
}
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);
}
@@ -48,7 +50,7 @@ namespace NzbDrone.Core.Applications.Lidarr
{
try
{
var request = BuildRequest(settings, $"/api/v1/indexer/{indexerId}", HttpMethod.Get);
var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Get);
return Execute<LidarrIndexer>(request);
}
catch (HttpException ex)
@@ -64,37 +66,37 @@ namespace NzbDrone.Core.Applications.Lidarr
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);
}
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);
}
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());
return Execute<LidarrIndexer>(request);
return ExecuteIndexerRequest(request);
}
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());
return Execute<LidarrIndexer>(request);
return ExecuteIndexerRequest(request);
}
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());
@@ -134,6 +136,53 @@ namespace NzbDrone.Core.Applications.Lidarr
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)
{
var baseUrl = settings.BaseUrl.TrimEnd('/');

View File

@@ -19,10 +19,12 @@ namespace NzbDrone.Core.Applications.Mylar
public class MylarSettings : IApplicationSettings
{
private static readonly MylarSettingsValidator Validator = new MylarSettingsValidator();
private static readonly MylarSettingsValidator Validator = new ();
public MylarSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8090";
SyncCategories = new[] { NewznabStandardCategory.BooksComics.Id };
}

View File

@@ -19,10 +19,12 @@ namespace NzbDrone.Core.Applications.Radarr
public class RadarrSettings : IApplicationSettings
{
private static readonly RadarrSettingsValidator Validator = new RadarrSettingsValidator();
private static readonly RadarrSettingsValidator Validator = new ();
public RadarrSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:7878";
SyncCategories = new[] { 2000, 2010, 2020, 2030, 2040, 2045, 2050, 2060, 2070, 2080 };
}

View File

@@ -23,6 +23,8 @@ namespace NzbDrone.Core.Applications.Radarr
public class RadarrV3Proxy : IRadarrV3Proxy
{
private const string AppApiRoute = "/api/v3";
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
@@ -34,13 +36,13 @@ namespace NzbDrone.Core.Applications.Radarr
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);
}
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);
}
@@ -48,7 +50,7 @@ namespace NzbDrone.Core.Applications.Radarr
{
try
{
var request = BuildRequest(settings, $"/api/v3/indexer/{indexerId}", HttpMethod.Get);
var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Get);
return Execute<RadarrIndexer>(request);
}
catch (HttpException ex)
@@ -64,37 +66,37 @@ namespace NzbDrone.Core.Applications.Radarr
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);
}
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);
}
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());
return Execute<RadarrIndexer>(request);
return ExecuteIndexerRequest(request);
}
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());
return Execute<RadarrIndexer>(request);
return ExecuteIndexerRequest(request);
}
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());
@@ -134,6 +136,53 @@ namespace NzbDrone.Core.Applications.Radarr
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)
{
var baseUrl = settings.BaseUrl.TrimEnd('/');

View File

@@ -19,10 +19,12 @@ namespace NzbDrone.Core.Applications.Readarr
public class ReadarrSettings : IApplicationSettings
{
private static readonly ReadarrSettingsValidator Validator = new ReadarrSettingsValidator();
private static readonly ReadarrSettingsValidator Validator = new ();
public ReadarrSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8787";
SyncCategories = new[] { 3030, 7000, 7010, 7020, 7030, 7040, 7050, 7060 };
}

View File

@@ -23,6 +23,8 @@ namespace NzbDrone.Core.Applications.Readarr
public class ReadarrV1Proxy : IReadarrV1Proxy
{
private const string AppApiRoute = "/api/v1";
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
@@ -34,13 +36,13 @@ namespace NzbDrone.Core.Applications.Readarr
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);
}
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);
}
@@ -48,7 +50,7 @@ namespace NzbDrone.Core.Applications.Readarr
{
try
{
var request = BuildRequest(settings, $"/api/v1/indexer/{indexerId}", HttpMethod.Get);
var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Get);
return Execute<ReadarrIndexer>(request);
}
catch (HttpException ex)
@@ -64,37 +66,37 @@ namespace NzbDrone.Core.Applications.Readarr
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);
}
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);
}
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());
return Execute<ReadarrIndexer>(request);
return ExecuteIndexerRequest(request);
}
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());
return Execute<ReadarrIndexer>(request);
return ExecuteIndexerRequest(request);
}
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());
@@ -134,6 +136,53 @@ namespace NzbDrone.Core.Applications.Readarr
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)
{
var baseUrl = settings.BaseUrl.TrimEnd('/');

View File

@@ -18,10 +18,12 @@ namespace NzbDrone.Core.Applications.Sonarr
public class SonarrSettings : IApplicationSettings
{
private static readonly SonarrSettingsValidator Validator = new SonarrSettingsValidator();
private static readonly SonarrSettingsValidator Validator = new ();
public SonarrSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8989";
SyncCategories = new[] { 5000, 5010, 5020, 5030, 5040, 5045, 5050 };
AnimeSyncCategories = new[] { 5070 };
}

View File

@@ -23,6 +23,8 @@ namespace NzbDrone.Core.Applications.Sonarr
public class SonarrV3Proxy : ISonarrV3Proxy
{
private const string AppApiRoute = "/api/v3";
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
@@ -34,13 +36,13 @@ namespace NzbDrone.Core.Applications.Sonarr
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);
}
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);
}
@@ -48,7 +50,7 @@ namespace NzbDrone.Core.Applications.Sonarr
{
try
{
var request = BuildRequest(settings, $"/api/v3/indexer/{indexerId}", HttpMethod.Get);
var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Get);
return Execute<SonarrIndexer>(request);
}
catch (HttpException ex)
@@ -64,37 +66,37 @@ namespace NzbDrone.Core.Applications.Sonarr
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);
}
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);
}
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());
return Execute<SonarrIndexer>(request);
return ExecuteIndexerRequest(request);
}
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());
return Execute<SonarrIndexer>(request);
return ExecuteIndexerRequest(request);
}
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());
@@ -140,6 +142,53 @@ namespace NzbDrone.Core.Applications.Sonarr
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)
{
var baseUrl = settings.BaseUrl.TrimEnd('/');

View File

@@ -19,10 +19,12 @@ namespace NzbDrone.Core.Applications.Whisparr
public class WhisparrSettings : IApplicationSettings
{
private static readonly WhisparrSettingsValidator Validator = new WhisparrSettingsValidator();
private static readonly WhisparrSettingsValidator Validator = new ();
public WhisparrSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:6969";
SyncCategories = new[] { 6000, 6010, 6020, 6030, 6040, 6045, 6050, 6070, 6080, 6090 };
}

View File

@@ -23,6 +23,8 @@ namespace NzbDrone.Core.Applications.Whisparr
public class WhisparrV3Proxy : IWhisparrV3Proxy
{
private const string AppApiRoute = "/api/v3";
private const string AppIndexerApiRoute = $"{AppApiRoute}/indexer";
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
@@ -34,13 +36,13 @@ namespace NzbDrone.Core.Applications.Whisparr
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);
}
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);
}
@@ -48,7 +50,7 @@ namespace NzbDrone.Core.Applications.Whisparr
{
try
{
var request = BuildRequest(settings, $"/api/v3/indexer/{indexerId}", HttpMethod.Get);
var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Get);
return Execute<WhisparrIndexer>(request);
}
catch (HttpException ex)
@@ -64,19 +66,19 @@ namespace NzbDrone.Core.Applications.Whisparr
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);
}
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);
}
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());
@@ -85,16 +87,16 @@ namespace NzbDrone.Core.Applications.Whisparr
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());
return Execute<WhisparrIndexer>(request);
return ExecuteIndexerRequest(request);
}
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());
@@ -134,6 +136,53 @@ namespace NzbDrone.Core.Applications.Whisparr
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)
{
var baseUrl = settings.BaseUrl.TrimEnd('/');

View File

@@ -69,7 +69,8 @@ namespace NzbDrone.Core.Backup
_diskProvider.EnsureFolder(_backupTempFolder);
_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);
Cleanup();
@@ -81,7 +82,7 @@ namespace NzbDrone.Core.Backup
BackupConfigFile();
BackupDatabase();
CreateVersionInfo();
CreateVersionInfo(dateNow);
_logger.ProgressDebug("Creating backup zip");
@@ -208,11 +209,15 @@ namespace NzbDrone.Core.Backup
_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)

View File

@@ -25,6 +25,7 @@ namespace NzbDrone.Core.Configuration
{
Dictionary<string, object> GetConfigDictionary();
void SaveConfigDictionary(Dictionary<string, object> configValues);
void EnsureDefaultConfigFile();
string BindAddress { get; }
int Port { get; }
@@ -258,7 +259,7 @@ namespace NzbDrone.Core.Configuration
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)
@@ -317,7 +318,7 @@ namespace NzbDrone.Core.Configuration
SetValue(key, value.ToString().ToLower());
}
private void EnsureDefaultConfigFile()
public void EnsureDefaultConfigFile()
{
if (!File.Exists(_configFile))
{

View File

@@ -103,7 +103,7 @@ namespace NzbDrone.Core.Datastore
{
if (!ids.Any())
{
return new List<TModel>();
return Array.Empty<TModel>();
}
var result = Query(x => ids.Contains(x.Id));

View File

@@ -1,5 +1,7 @@
using System;
using System.Data;
using System.Data.Common;
using System.Data.SQLite;
using System.Text.RegularExpressions;
using Dapper;
using NLog;
@@ -38,17 +40,9 @@ namespace NzbDrone.Core.Datastore
{
get
{
using (var db = _datamapperFactory())
{
if (db.ConnectionString.Contains(".db"))
{
return DatabaseType.SQLite;
}
else
{
return DatabaseType.PostgreSQL;
}
}
using var db = _datamapperFactory();
return db is SQLiteConnection ? DatabaseType.SQLite : DatabaseType.PostgreSQL;
}
}
@@ -56,24 +50,11 @@ namespace NzbDrone.Core.Datastore
{
get
{
using (var db = _datamapperFactory())
{
string version;
using var db = _datamapperFactory();
var dbConnection = db as DbConnection;
var version = Regex.Replace(dbConnection.ServerVersion, @"\(.*?\)", "");
try
{
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);
}
return new Version(version);
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using NLog;
@@ -64,7 +65,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
catch (DownloadClientException e)
{
_logger.Error(e);
return new List<DownloadStationTask>();
return Array.Empty<DownloadStationTask>();
}
}

View File

@@ -45,6 +45,11 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
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)
{
throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", new HttpException(response));

View File

@@ -16,12 +16,12 @@ namespace NzbDrone.Core.Download.Clients.rTorrent
public RTorrentDirectoryValidator(PathExistsValidator pathExistsValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator)
{
RuleFor(c => c.Directory).Cascade(CascadeMode.StopOnFirstFailure)
.IsValidPath()
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(pathExistsValidator)
.When(c => c.Directory.IsNotNullOrWhiteSpace())
.When(c => c.Host == "localhost" || c.Host == "127.0.0.1");
RuleFor(c => c.Directory).Cascade(CascadeMode.Stop)
.IsValidPath()
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(pathExistsValidator)
.When(c => c.Directory.IsNotNullOrWhiteSpace())
.When(c => c.Host == "localhost" || c.Host == "127.0.0.1");
}
}
}

View File

@@ -2,9 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
#if !LIBRARY
using NzbDrone.Common.EnsureThat;
#endif
namespace NzbDrone.Core
{
@@ -12,9 +10,8 @@ namespace NzbDrone.Core
{
public static string WithDefault(this string actual, object defaultValue)
{
#if !LIBRARY
Ensure.That(defaultValue, () => defaultValue).IsNotNull();
#endif
if (string.IsNullOrWhiteSpace(actual))
{
return defaultValue.ToString();

View File

@@ -1,7 +1,6 @@
using System.Linq;
using NLog;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Cardigann;
using NzbDrone.Core.Indexers.Definitions.Cardigann;
using NzbDrone.Core.IndexerVersions;
using NzbDrone.Core.Localization;
using NzbDrone.Core.ThingiProvider.Events;

View File

@@ -1,7 +1,7 @@
using System.Linq;
using NLog;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Cardigann;
using NzbDrone.Core.Indexers.Definitions.Cardigann;
using NzbDrone.Core.IndexerVersions;
using NzbDrone.Core.Localization;
using NzbDrone.Core.ThingiProvider.Events;

View File

@@ -31,9 +31,7 @@ namespace NzbDrone.Core.History
public History MostRecentForDownloadId(string downloadId)
{
return FindByDownloadId(downloadId)
.OrderByDescending(h => h.Date)
.FirstOrDefault();
return FindByDownloadId(downloadId).MaxBy(h => h.Date);
}
public List<History> FindByDownloadId(string downloadId)
@@ -75,9 +73,7 @@ namespace NzbDrone.Core.History
public History MostRecentForIndexer(int indexerId)
{
return Query(x => x.IndexerId == indexerId)
.OrderByDescending(h => h.Date)
.FirstOrDefault();
return Query(x => x.IndexerId == indexerId).MaxBy(h => h.Date);
}
public List<History> Between(DateTime start, DateTime end)

View File

@@ -118,12 +118,14 @@ namespace NzbDrone.Core.History
public void Handle(IndexerQueryEvent message)
{
var response = message.QueryResult.Response;
var history = new History
{
Date = DateTime.UtcNow,
IndexerId = message.IndexerId,
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)

View File

@@ -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]",
RegexOptions.Compiled);
public List<ReleaseInfo> Releases { get; set; }
public IList<ReleaseInfo> Releases { get; set; }
private static string RemoveInvalidXMLChars(string text)
{

View File

@@ -148,7 +148,7 @@ namespace NzbDrone.Core.IndexerSearch
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();
@@ -168,7 +168,7 @@ namespace NzbDrone.Core.IndexerSearch
if (indexers.Count == 0)
{
_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))
{
return new List<ReleaseInfo>();
return Array.Empty<ReleaseInfo>();
}
try
@@ -224,7 +224,7 @@ namespace NzbDrone.Core.IndexerSearch
_logger.Error(e, "Error while searching for {0}", criteriaBase);
}
return new List<ReleaseInfo>();
return Array.Empty<ReleaseInfo>();
}
}
}

View File

@@ -8,7 +8,7 @@ using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Cardigann;
using NzbDrone.Core.Indexers.Definitions.Cardigann;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Commands;
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 */
private const string DEFINITION_BRANCH = "master";
private const int DEFINITION_VERSION = 8;
private const int DEFINITION_VERSION = 9;
// Used when moving yml to C#
private readonly List<string> _definitionBlocklist = new ()

File diff suppressed because it is too large Load Diff

View File

@@ -59,6 +59,6 @@ namespace NzbDrone.Core.Indexers.Definitions
public class AvistaZParser : AvistazParserBase
{
protected override string TimezoneOffset => "+01:00";
protected override string TimezoneOffset => "+02:00";
}
}

View File

@@ -67,9 +67,9 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
_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)

View File

@@ -13,18 +13,18 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{
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" };
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<TorrentInfo>();
var releaseInfos = new List<ReleaseInfo>();
if (indexerResponse.HttpResponse.StatusCode == HttpStatusCode.NotFound)
{
return torrentInfos.ToArray();
return releaseInfos.ToArray();
}
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;
}
torrentInfos.Add(release);
releaseInfos.Add(release);
}
// order by date
return torrentInfos.OrderByDescending(o => o.PublishDate).ToArray();
return releaseInfos
.OrderByDescending(o => o.PublishDate)
.ToArray();
}
// hook to adjust category parsing
@@ -115,7 +117,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
cats.Add(NewznabStandardCategory.Audio);
break;
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;

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
@@ -85,6 +86,8 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
request.HttpRequest.Headers.Add("Authorization", $"Bearer {Settings.Token}");
request.HttpRequest.SuppressHttpErrorStatusCodes = new[] { HttpStatusCode.NotFound };
yield return request;
}

View File

@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using AngleSharp.Dom;
using AngleSharp.Html.Parser;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
@@ -27,7 +28,6 @@ namespace NzbDrone.Core.Indexers.Definitions
public override string[] IndexerUrls => new[] { "https://bakabt.me/" };
public override string Description => "Anime Community";
private string LoginUrl => Settings.BaseUrl + "login.php";
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var dom = parser.ParseDocument(response.Content);
var downloadLink = dom.QuerySelector(".download_link")?.GetAttribute("href");
if (string.IsNullOrWhiteSpace(downloadLink))
if (downloadLink.IsNullOrWhiteSpace())
{
throw new Exception("Unable to find download link.");
}
@@ -71,17 +71,19 @@ namespace NzbDrone.Core.Indexers.Definitions
{
UpdateCookies(null, null);
var requestBuilder = new HttpRequestBuilder(LoginUrl)
var loginUrl = Settings.BaseUrl + "login.php";
var requestBuilder = new HttpRequestBuilder(loginUrl)
{
LogResponseContent = true,
AllowAutoRedirect = true,
Method = HttpMethod.Post
};
var loginPage = await ExecuteAuth(new HttpRequest(LoginUrl));
var loginPage = await ExecuteAuth(new HttpRequest(loginUrl));
var parser = new HtmlParser();
var dom = parser.ParseDocument(loginPage.Content);
var dom = await parser.ParseDocumentAsync(loginPage.Content);
var loginKey = dom.QuerySelector("input[name=\"loginKey\"]");
if (loginKey != null)
{
@@ -98,21 +100,23 @@ namespace NzbDrone.Core.Indexers.Definitions
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");
}
else
{
throw new IndexerAuthException("BakaBT authentication failed");
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
}
UpdateCookies(response.GetCookies(), DateTime.Now.AddDays(30));
_logger.Debug("BakaBT authentication succeeded");
}
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{
return !httpResponse.Content.Contains("<a href=\"logout.php\">Logout</a>");
return httpResponse.Content.Contains("loginForm");
}
private IndexerCapabilities SetCapabilities()
@@ -241,7 +245,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<TorrentInfo>();
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
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.UploadVolumeFactor = 1;
torrentInfos.Add(release);
releaseInfos.Add(release);
}
}
return torrentInfos.ToArray();
return releaseInfos.ToArray();
}
private ICollection<IndexerCategory> GetNextCategory(IElement row, ICollection<IndexerCategory> currentCategories)
@@ -388,12 +392,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var categoryName = categoryElement.GetAttribute("title");
if (!string.IsNullOrWhiteSpace(categoryName))
{
return categoryName;
}
return null;
return categoryName.IsNotNullOrWhiteSpace() ? categoryName : null;
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }

View File

@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Globalization;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
@@ -50,50 +50,61 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
var parameters = new BroadcastheNetTorrentQuery();
var searchString = searchCriteria.SearchTerm != null ? searchCriteria.SearchTerm : "";
var searchString = searchCriteria.SearchTerm ?? "";
var btnResults = searchCriteria.Limit.GetValueOrDefault();
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)
{
parameters.Tvdb = string.Format("{0}", searchCriteria.TvdbId);
parameters.Tvdb = $"{searchCriteria.TvdbId}";
}
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 (searchCriteria.Season > 0 && searchCriteria.Episode.IsNullOrWhiteSpace())
{
// Season Only
parameters.Name = string.Format("Season {0}%", searchCriteria.Season.Value);
// Search 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
parameters.Name = searchCriteria.EpisodeSearchString;
parameters.Name = showDate.ToString("yyyy.MM.dd");
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
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";
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;
}
@@ -109,17 +120,17 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
var parameters = new BroadcastheNetTorrentQuery();
var searchString = searchCriteria.SearchTerm != null ? searchCriteria.SearchTerm : "";
var searchString = searchCriteria.SearchTerm ?? "";
var btnResults = searchCriteria.Limit.GetValueOrDefault();
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));

View File

@@ -14,7 +14,7 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Cardigann
namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{
public class Cardigann : TorrentIndexerBase<CardigannSettings>
{

View File

@@ -15,7 +15,7 @@ using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.Indexers.Cardigann
namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{
public class CardigannBase
{
@@ -825,9 +825,19 @@ namespace NzbDrone.Core.Indexers.Cardigann
protected JArray JsonParseRowsSelector(JToken parsedJson, string rowSelector)
{
var selector = rowSelector.Split(':')[0];
var rowsObj = parsedJson.SelectToken(selector).Value<JArray>();
return new JArray(rowsObj.Where(t =>
JsonParseFieldSelector(t.Value<JObject>(), rowSelector.Remove(0, selector.Length)) != null));
try
{
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)

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
namespace NzbDrone.Core.Indexers.Cardigann
namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{
// A Dictionary allowing the same key multiple times
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 bool Optional { get; set; }
public string Default { get; set; }
public string Text { get; set; }
public string Attribute { get; set; }
public string Remove { get; set; }
@@ -146,6 +147,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
public List<SearchPathBlock> Paths { get; set; }
public Dictionary<string, List<string>> Headers { get; set; }
public List<FilterBlock> Keywordsfilters { get; set; }
public bool AllowEmptyInputs { get; set; }
public Dictionary<string, string> Inputs { get; set; }
public List<ErrorBlock> Error { get; set; }
public List<FilterBlock> Preprocessingfilters { get; set; }
@@ -159,6 +161,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
public SelectorBlock Dateheaders { get; set; }
public SelectorBlock Count { get; set; }
public bool Multiple { get; set; }
public bool MissingAttributeEqualsNoResults { get; set; }
}
public class SearchPathBlock : RequestBlock

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace NzbDrone.Core.Indexers.Cardigann
namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{
public class CardigannMetaDefinition
{

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using AngleSharp.Dom;
using AngleSharp.Html.Parser;
using AngleSharp.Xml.Parser;
@@ -16,7 +15,7 @@ using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Cardigann
namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{
public class CardigannParser : CardigannBase, IParseIndexerResponse
{
@@ -81,20 +80,38 @@ namespace NzbDrone.Core.Indexers.Cardigann
if (search.Rows.Count != null)
{
var countVal = HandleJsonSelector(search.Rows.Count, parsedJson, variables);
if (int.TryParse(countVal, out var count) && count < 1)
try
{
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);
if (rowsArray == null)
{
if (search.Rows.MissingAttributeEqualsNoResults)
{
return releases;
}
throw new IndexerException(indexerResponse, "Error Parsing Rows Selector");
}
if (rowsArray.Count == 0)
{
return releases;
}
foreach (var row in rowsArray)
{
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;
var variablesKey = ".Result." + fieldName;
var isOptional = OptionalFields.Contains(field.Key) || fieldModifiers.Contains("optional") || field.Value.Optional;
try
{
var parentObj = mulRow;
@@ -126,28 +144,35 @@ namespace NzbDrone.Core.Indexers.Cardigann
}
value = HandleJsonSelector(field.Value, parentObj, variables, !isOptional);
if (isOptional && string.IsNullOrWhiteSpace(value))
if (isOptional && value.IsNullOrWhiteSpace())
{
variables[variablesKey] = null;
continue;
var defaultValue = ApplyGoTemplateText(field.Value.Default, variables);
if (defaultValue.IsNullOrWhiteSpace())
{
variables[variablesKey] = null;
continue;
}
value = defaultValue;
}
variables[variablesKey] = ParseFields(value, fieldName, release, fieldModifiers, searchUrlUri);
}
catch (Exception ex)
{
if (!variables.ContainsKey(variablesKey))
if (!variables.ContainsKey(variablesKey) || isOptional)
{
variables[variablesKey] = null;
}
if (isOptional)
{
variables[variablesKey] = null;
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;
var variablesKey = ".Result." + fieldName;
var isOptional = OptionalFields.Contains(field.Key) || fieldModifiers.Contains("optional") || field.Value.Optional;
try
{
value = HandleSelector(field.Value, row, variables, !isOptional);
if (isOptional && string.IsNullOrWhiteSpace(value))
if (isOptional && value.IsNullOrWhiteSpace())
{
variables[variablesKey] = null;
continue;
var defaultValue = ApplyGoTemplateText(field.Value.Default, variables);
if (defaultValue.IsNullOrWhiteSpace())
{
variables[variablesKey] = null;
continue;
}
value = defaultValue;
}
variables[variablesKey] = ParseFields(value, fieldName, release, fieldModifiers, searchUrlUri);
}
catch (Exception ex)
{
if (!variables.ContainsKey(variablesKey))
if (!variables.ContainsKey(variablesKey) || isOptional)
{
variables[variablesKey] = null;
}
if (OptionalFields.Contains(field.Key) || fieldModifiers.Contains("optional") || field.Value.Optional)
if (isOptional)
{
variables[variablesKey] = null;
continue;
}
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);
}
}
}

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using NzbDrone.Common.Http;
namespace NzbDrone.Core.Indexers.Cardigann
namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{
public class CardigannRequest : IndexerRequest
{

View File

@@ -9,15 +9,15 @@ using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.Cardigann;
using NzbDrone.Core.Indexers.Definitions.Cardigann.Exceptions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers.Cardigann
namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{
public class CardigannRequestGenerator : CardigannBase, IIndexerRequestGenerator
{
@@ -589,7 +589,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
return true;
}
public async Task<Captcha> GetConfigurationForSetup(bool automaticlogin)
public async Task<Captcha> GetConfigurationForSetup(bool automaticLogin)
{
var login = _definition.Login;
@@ -610,6 +610,11 @@ namespace NzbDrone.Core.Indexers.Cardigann
Encoding = _encoding
};
if (_definition.Followredirect)
{
requestBuilder.AllowAutoRedirect = true;
}
Cookies = null;
if (login.Cookies != null)
{
@@ -627,11 +632,6 @@ namespace NzbDrone.Core.Indexers.Cardigann
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();
landingResultDocument = htmlParser.ParseDocument(landingResult.Content);
@@ -642,7 +642,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
captcha = await GetCaptcha(login);
}
if (captcha != null && automaticlogin)
if (captcha != null && automaticLogin)
{
_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>();
}
foreach (var input in request.Inputs)
if (request.Inputs != null)
{
var value = ApplyGoTemplateText(input.Value, variables);
if (method == HttpMethod.Get)
foreach (var input in request.Inputs)
{
queryCollection.Add(input.Key, value);
}
else if (method == HttpMethod.Post)
{
pairs.Add(input.Key, value);
var value = ApplyGoTemplateText(input.Value, variables);
if (method == HttpMethod.Get)
{
queryCollection.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);
foreach (var part in rawStr.Split('&'))
{
var parts = part.Split(new char[] { '=' }, 2);
var parts = part.Split(new[] { '=' }, 2);
var key = parts[0];
if (key.Length == 0)
{
@@ -1119,7 +1122,12 @@ namespace NzbDrone.Core.Indexers.Cardigann
}
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)
{
@@ -1157,19 +1165,16 @@ namespace NzbDrone.Core.Indexers.Cardigann
requestBuilder.SetHeaders(headers ?? new Dictionary<string, string>());
}
if (searchPath.Followredirect)
{
requestBuilder.AllowAutoRedirect = true;
}
var request = requestBuilder
.WithRateLimit(_rateLimit.TotalSeconds)
.Build();
var cardigannRequest = new CardigannRequest(request, variables, searchPath)
{
HttpRequest =
{
AllowAutoRedirect = searchPath.Followredirect
}
};
yield return cardigannRequest;
yield return new CardigannRequest(request, variables, searchPath);
}
}
}

View File

@@ -1,11 +1,8 @@
using System.Collections.Generic;
using FluentValidation;
using FluentValidation.Results;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Cardigann
namespace NzbDrone.Core.Indexers.Definitions.Cardigann
{
public class CardigannSettings : NoAuthTorrentBaseSettings
{

View File

@@ -1,6 +1,3 @@
using NzbDrone.Common.Exceptions;
using NzbDrone.Core.Indexers.Cardigann;
namespace NzbDrone.Core.Indexers.Definitions.Cardigann.Exceptions
{
public class CardigannConfigException : CardigannException

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Indexers.Definitions.Cardigann.Exceptions

View File

@@ -72,7 +72,7 @@ public class FileListParser : IParseIndexerResponse
InfoUrl = GetInfoUrl(id),
Seeders = 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,
Genres = row.SmallDescription.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(),
ImdbId = imdbId,

View File

@@ -378,7 +378,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var jsonResponse = JsonConvert.DeserializeObject<MyAnonamouseResponse>(indexerResponse.Content);
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();
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@@ -47,6 +48,11 @@ namespace NzbDrone.Core.Indexers.Definitions
return new NebulanceParser(Settings);
}
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{
return false;
}
protected override Task<HttpRequest> GetDownloadRequest(Uri link)
{
// Avoid using cookies to prevent redirects to login page
@@ -189,6 +195,11 @@ namespace NzbDrone.Core.Indexers.Definitions
{
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;
if (jsonResponse.Error != null || jsonResponse.Result == null)

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using FluentValidation;
using Newtonsoft.Json.Linq;
@@ -10,6 +10,7 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
@@ -35,7 +36,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new NzbIndexRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
return new NzbIndexRequestGenerator(Settings, Capabilities);
}
public override IParseIndexerResponse GetParser()
@@ -48,21 +49,21 @@ namespace NzbDrone.Core.Indexers.Definitions
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
},
{
MovieSearchParam.Q
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
}
{
BookSearchParam.Q
}
};
// 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(255, NewznabStandardCategory.Other, "a.b.guitar.tab");
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(259, NewznabStandardCategory.Other, "a.b.hdtv.french.repost");
caps.Categories.AddCategoryMapping(260, NewznabStandardCategory.Other, "a.b.hdtv.german");
caps.Categories.AddCategoryMapping(261, NewznabStandardCategory.Other, "a.b.hdtv.german-audio");
caps.Categories.AddCategoryMapping(260, NewznabStandardCategory.TVForeign, "a.b.hdtv.german");
caps.Categories.AddCategoryMapping(261, NewznabStandardCategory.TVForeign, "a.b.hdtv.german-audio");
caps.Categories.AddCategoryMapping(262, NewznabStandardCategory.Other, "a.b.hdtv.repost");
caps.Categories.AddCategoryMapping(263, NewznabStandardCategory.Other, "a.b.hdtv.tv-episodes");
caps.Categories.AddCategoryMapping(264, NewznabStandardCategory.Other, "a.b.hdtv.x264");
caps.Categories.AddCategoryMapping(265, NewznabStandardCategory.Other, "a.b.hdtv.x264.french");
caps.Categories.AddCategoryMapping(263, NewznabStandardCategory.TV, "a.b.hdtv.tv-episodes");
caps.Categories.AddCategoryMapping(264, NewznabStandardCategory.TV, "a.b.hdtv.x264");
caps.Categories.AddCategoryMapping(265, NewznabStandardCategory.TVForeign, "a.b.hdtv.x264.french");
caps.Categories.AddCategoryMapping(266, NewznabStandardCategory.Other, "a.b.highspeed");
caps.Categories.AddCategoryMapping(267, NewznabStandardCategory.Other, "a.b.hitoshirezu");
caps.Categories.AddCategoryMapping(268, NewznabStandardCategory.Other, "a.b.holiday");
@@ -1021,45 +1022,20 @@ namespace NzbDrone.Core.Indexers.Definitions
public class NzbIndexRequestGenerator : IIndexerRequestGenerator
{
public NzbIndexSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
private readonly NzbIndexSettings _settings;
private readonly IndexerCapabilities _capabilities;
public NzbIndexRequestGenerator()
public NzbIndexRequestGenerator(NzbIndexSettings settings, IndexerCapabilities 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;
_settings = settings;
_capabilities = capabilities;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
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;
}
@@ -1068,7 +1044,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
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;
}
@@ -1077,7 +1053,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
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;
}
@@ -1086,7 +1062,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
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;
}
@@ -1095,11 +1071,36 @@ namespace NzbDrone.Core.Indexers.Definitions
{
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;
}
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 Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
@@ -1109,6 +1110,8 @@ namespace NzbDrone.Core.Indexers.Definitions
private readonly NzbIndexSettings _settings;
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)
{
_settings = settings;
@@ -1117,6 +1120,16 @@ namespace NzbDrone.Core.Indexers.Definitions
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>();
// TODO Deserialize to TorrentSyndikatResponse Type
@@ -1154,8 +1167,6 @@ namespace NzbDrone.Core.Indexers.Definitions
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; }
}

View File

@@ -281,7 +281,6 @@ namespace NzbDrone.Core.Indexers.Definitions
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
PublishDate = torrent.Time.ToUniversalTime(),
Scene = torrent.Scene,
Freeleech = torrent.IsFreeLeech || torrent.IsPersonalFreeLeech,
Files = torrent.FileCount,
Grabs = torrent.Snatches,
DownloadVolumeFactor = torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsPersonalFreeLeech ? 0 : 1,
@@ -318,7 +317,6 @@ namespace NzbDrone.Core.Indexers.Definitions
Seeders = 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),
Freeleech = result.IsFreeLeech || result.IsPersonalFreeLeech,
Files = result.FileCount,
Grabs = result.Snatches,
DownloadVolumeFactor = result.IsFreeLeech || result.IsNeutralLeech || result.IsPersonalFreeLeech ? 0 : 1,

View File

@@ -1,7 +1,5 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Messaging.Events;
@@ -10,20 +8,19 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public class PassThePopcorn : TorrentIndexerBase<PassThePopcornSettings>
{
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 DownloadProtocol Protocol => DownloadProtocol.Torrent;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override bool SupportsRss => true;
public override bool SupportsSearch => true;
public override bool SupportsPagination => true;
public override int PageSize => 50;
public override IndexerCapabilities Capabilities => SetCapabilities();
public override int PageSize => 50;
public PassThePopcorn(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
ICacheManager cacheManager,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
@@ -33,7 +30,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new PassThePopcornRequestGenerator()
return new PassThePopcornRequestGenerator
{
Settings = Settings,
HttpClient = _httpClient,

View File

@@ -39,11 +39,6 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public List<string> Tags { get; set; }
public List<Director> Directors { 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; }
}

View File

@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Net;
using Newtonsoft.Json;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers.Exceptions;
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}");
}
var jsonResponse = JsonConvert.DeserializeObject<PassThePopcornResponse>(indexerResponse.Content);
var jsonResponse = STJson.Deserialize<PassThePopcornResponse>(indexerResponse.Content);
if (jsonResponse.TotalResults == "0" ||
jsonResponse.TotalResults.IsNullOrWhiteSpace() ||
jsonResponse.Movies == null)
@@ -94,13 +94,14 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
// Only add approved torrents
try
{
torrentInfos.Add(new TorrentInfo()
torrentInfos.Add(new TorrentInfo
{
Guid = string.Format("PassThePopcorn-{0}", id),
Title = title,
Size = long.Parse(torrent.Size),
DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey),
InfoUrl = GetInfoUrl(result.GroupId, id),
Grabs = int.Parse(torrent.Snatched),
Seeders = int.Parse(torrent.Seeders),
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
PublishDate = torrent.UploadTime.ToUniversalTime(),
@@ -128,8 +129,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
}
}
return
torrentInfos;
return torrentInfos;
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }

View File

@@ -24,11 +24,11 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
{
pageableRequests.Add(GetRequest(searchCriteria.FullImdbId));
pageableRequests.Add(GetRequest(searchCriteria.FullImdbId, searchCriteria));
}
else
{
pageableRequests.Add(GetRequest(string.Format("{0}", searchCriteria.SearchTerm)));
pageableRequests.Add(GetRequest($"{searchCriteria.SearchTerm}", searchCriteria));
}
return pageableRequests;
@@ -37,18 +37,25 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public Func<IDictionary<string, string>> GetCookies { 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
{
{ "action", "advanced" },
{ "json", "noredirect" },
{ "grouping", "0" },
{ "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)
{
queryParams.Add("freetorrent", "1");
queryParams.Set("freetorrent", "1");
}
var request =
@@ -91,7 +98,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest(string.Format("{0}", searchCriteria.SearchTerm)));
pageableRequests.Add(GetRequest($"{searchCriteria.SearchTerm}", searchCriteria));
return pageableRequests;
}

View File

@@ -58,7 +58,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Rarbg
case (int)HttpStatusCode.OK:
break;
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;
}
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);
@@ -133,36 +133,19 @@ namespace NzbDrone.Core.Indexers.Definitions.Rarbg
qs.Set("token", newToken);
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");
response = await FetchIndexerResponse(request);
return await FetchIndexerResponse(request);
}
}
try
{
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;
}
return response;
}
public override object RequestAction(string action, IDictionary<string, string> query)

View File

@@ -46,11 +46,11 @@ namespace NzbDrone.Core.Indexers.Definitions.Rarbg
{
requestBuilder.AddQueryParam("search_imdb", imdbId);
}
else if (tmdbId.HasValue && tmdbId > 0)
else if (tmdbId is > 0)
{
requestBuilder.AddQueryParam("search_themoviedb", tmdbId);
}
else if (tvdbId.HasValue && tvdbId > 0)
else if (tvdbId is > 0)
{
requestBuilder.AddQueryParam("search_tvdb", tvdbId);
}

View File

@@ -47,7 +47,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Rarbg
return response.Resource["token"].ToString();
},
TimeSpan.FromMinutes(14.0));
TimeSpan.FromMinutes(14));
}
}
}

View File

@@ -247,7 +247,6 @@ namespace NzbDrone.Core.Indexers.Definitions
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
PublishDate = torrent.Time.ToUniversalTime(),
Scene = torrent.Scene,
Freeleech = torrent.IsFreeLeech || torrent.IsPersonalFreeLeech,
Files = torrent.FileCount,
Grabs = torrent.Snatches,
DownloadVolumeFactor = torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsPersonalFreeLeech ? 0 : 1,
@@ -284,7 +283,6 @@ namespace NzbDrone.Core.Indexers.Definitions
Seeders = int.Parse(result.Seeders),
Peers = int.Parse(result.Leechers) + int.Parse(result.Seeders),
PublishDate = DateTimeOffset.FromUnixTimeSeconds(ParseUtil.CoerceLong(result.GroupTime)).UtcDateTime,
Freeleech = result.IsFreeLeech || result.IsPersonalFreeLeech,
Files = result.FileCount,
Grabs = result.Snatches,
DownloadVolumeFactor = result.IsFreeLeech || result.IsNeutralLeech || result.IsPersonalFreeLeech ? 0 : 1,

View File

@@ -27,7 +27,8 @@ namespace NzbDrone.Core.Indexers.Definitions
public override string[] IndexerUrls => new[]
{
"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 Language => "ru-RU";

View File

@@ -72,7 +72,7 @@ public class SecretCinemaParser : IParseIndexerResponse
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
var releaseInfos = new List<ReleaseInfo>();
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
@@ -95,7 +95,7 @@ public class SecretCinemaParser : IParseIndexerResponse
jsonResponse.Resource.Status.IsNullOrWhiteSpace() ||
jsonResponse.Resource.Response == null)
{
return torrentInfos;
return releaseInfos.ToArray();
}
foreach (var result in jsonResponse.Resource.Response.Results)
@@ -109,10 +109,11 @@ public class SecretCinemaParser : IParseIndexerResponse
// in SC movies, artist=director and GroupName=title
var artist = WebUtility.HtmlDecode(result.Artist);
var title = WebUtility.HtmlDecode(result.GroupName);
var time = DateTime.SpecifyKind(torrent.Time, DateTimeKind.Unspecified);
var release = new GazelleInfo
{
Guid = string.Format("SecretCinema-{0}", id),
Guid = $"SecretCinema-{id}",
Title = title,
Container = torrent.Encoding,
Files = torrent.FileCount,
@@ -123,7 +124,7 @@ public class SecretCinemaParser : IParseIndexerResponse
InfoUrl = GetInfoUrl(result.GroupId, id),
Seeders = 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,
DownloadVolumeFactor = torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsPersonalFreeLeech ? 0 : 1,
UploadVolumeFactor = torrent.IsNeutralLeech ? 0 : 1
@@ -162,7 +163,7 @@ public class SecretCinemaParser : IParseIndexerResponse
release.Title += " [Cue]";
}
torrentInfos.Add(release);
releaseInfos.Add(release);
}
}
else
@@ -172,7 +173,7 @@ public class SecretCinemaParser : IParseIndexerResponse
var release = new GazelleInfo
{
Guid = string.Format("SecretCinema-{0}", id),
Guid = $"SecretCinema-{id}",
Title = groupName,
Size = long.Parse(result.Size),
DownloadUrl = GetDownloadUrl(id),
@@ -196,13 +197,13 @@ public class SecretCinemaParser : IParseIndexerResponse
release.Categories = _capabilities.Categories.MapTrackerCatDescToNewznab(category);
}
torrentInfos.Add(release);
releaseInfos.Add(release);
}
}
// order by date
return
torrentInfos
releaseInfos
.OrderByDescending(o => o.PublishDate)
.ToArray();
}

View File

@@ -323,6 +323,11 @@ namespace NzbDrone.Core.Indexers.Definitions
{ "nm", term.IsNotNullOrWhiteSpace() ? term.Replace("-", " ") : "" }
};
if (_settings.FreeleechOnly)
{
parameters.Add("sds", "1");
}
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(categories);
if (queryCats.Any())
{
@@ -408,6 +413,19 @@ namespace NzbDrone.Core.Indexers.Definitions
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);
}
@@ -540,7 +558,10 @@ namespace NzbDrone.Core.Indexers.Definitions
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; }
}
}

View File

@@ -1,15 +1,14 @@
using System;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.TorrentPotato
namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
{
public class TorrentPotato : TorrentIndexerBase<TorrentPotatoSettings>
{
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 DownloadProtocol Protocol => DownloadProtocol.Torrent;

View File

@@ -6,7 +6,7 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.TorrentPotato
namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
{
public class TorrentPotatoParser : IParseIndexerResponse
{
@@ -31,17 +31,17 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
foreach (var torrent in jsonResponse.Resource.results)
{
var torrentInfo = new TorrentInfo();
torrentInfo.Guid = GetGuid(torrent);
torrentInfo.Title = torrent.release_name;
torrentInfo.Size = (long)torrent.size * 1000 * 1000;
torrentInfo.DownloadUrl = torrent.download_url;
torrentInfo.InfoUrl = torrent.details_url;
torrentInfo.PublishDate = torrent.publish_date.ToUniversalTime();
torrentInfo.Seeders = torrent.seeders;
torrentInfo.Peers = torrent.leechers + torrent.seeders;
torrentInfo.Freeleech = torrent.freeleech;
var torrentInfo = new TorrentInfo
{
Guid = GetGuid(torrent),
Title = torrent.release_name,
Size = (long)torrent.size * 1000 * 1000,
DownloadUrl = torrent.download_url,
InfoUrl = torrent.details_url,
PublishDate = torrent.publish_date.ToUniversalTime(),
Seeders = torrent.seeders,
Peers = torrent.leechers + torrent.seeders
};
results.Add(torrentInfo);
}

View File

@@ -4,16 +4,12 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
namespace NzbDrone.Core.Indexers.TorrentPotato
namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
{
public class TorrentPotatoRequestGenerator : IIndexerRequestGenerator
{
public TorrentPotatoSettings Settings { get; set; }
public TorrentPotatoRequestGenerator()
{
}
public virtual IndexerPageableRequestChain GetRecentRequests()
{
var pageableRequests = new IndexerPageableRequestChain();

View File

@@ -1,6 +1,6 @@
using System;
namespace NzbDrone.Core.Indexers.TorrentPotato
namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
{
public class TorrentPotatoResponse
{

View File

@@ -3,7 +3,7 @@ using NzbDrone.Core.Annotations;
using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.TorrentPotato
namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
{
public class TorrentPotatoSettingsValidator : NoAuthSettingsValidator<TorrentPotatoSettings>
{

View File

@@ -508,9 +508,12 @@ namespace NzbDrone.Core.Indexers
}
// 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)
{
@@ -644,7 +647,7 @@ namespace NzbDrone.Core.Indexers
{
_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;

View File

@@ -93,7 +93,7 @@ namespace NzbDrone.Core.Indexers
{
if (string.IsNullOrWhiteSpace(input))
{
return new List<IndexerCategory>();
return Array.Empty<IndexerCategory>();
}
var cats = _categoryMapping
@@ -109,7 +109,7 @@ namespace NzbDrone.Core.Indexers
{
if (string.IsNullOrWhiteSpace(trackerCategoryDesc))
{
return new List<IndexerCategory>();
return Array.Empty<IndexerCategory>();
}
var cats = _categoryMapping

View File

@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Indexers.Cardigann;
using NzbDrone.Core.Indexers.Definitions.Cardigann;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.ThingiProvider;

View File

@@ -5,7 +5,7 @@ using System.Text;
using FluentValidation.Results;
using NLog;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Indexers.Cardigann;
using NzbDrone.Core.Indexers.Definitions.Cardigann;
using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.IndexerVersions;
using NzbDrone.Core.Messaging.Events;
@@ -49,7 +49,7 @@ namespace NzbDrone.Core.Indexers
foreach (var definition in definitions)
{
if (definition.Implementation == typeof(Cardigann.Cardigann).Name)
if (definition.Implementation == nameof(Cardigann))
{
try
{
@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Indexers
{
var definition = base.Get(id);
if (definition.Implementation == typeof(Cardigann.Cardigann).Name)
if (definition.Implementation == nameof(Cardigann))
{
try
{
@@ -177,7 +177,7 @@ namespace NzbDrone.Core.Indexers
}
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)
{
@@ -189,7 +189,7 @@ namespace NzbDrone.Core.Indexers
public override IEnumerable<IndexerDefinition> GetPresetDefinitions(IndexerDefinition providerDefinition)
{
return new List<IndexerDefinition>();
return Array.Empty<IndexerDefinition>();
}
public override void SetProviderCharacteristics(IIndexer provider, IndexerDefinition definition)
@@ -203,7 +203,7 @@ namespace NzbDrone.Core.Indexers
definition.SupportsPagination = provider.SupportsPagination;
//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.LegacyUrls = provider.LegacyUrls;
@@ -276,13 +276,13 @@ namespace NzbDrone.Core.Indexers
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;
settings.Categories = _newznabCapabilitiesProvider.GetCapabilities(settings, definition)?.Categories.GetTorznabCategoryList() ?? null;
}
if (definition.Implementation == typeof(Cardigann.Cardigann).Name)
if (definition.Implementation == nameof(Cardigann))
{
MapCardigannDefinition(definition);
}
@@ -296,13 +296,13 @@ namespace NzbDrone.Core.Indexers
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;
settings.Categories = _newznabCapabilitiesProvider.GetCapabilities(settings, definition)?.Categories.GetTorznabCategoryList() ?? null;
}
if (definition.Implementation == typeof(Cardigann.Cardigann).Name)
if (definition.Implementation == nameof(Cardigann))
{
MapCardigannDefinition(definition);
}

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