Compare commits

...

96 Commits

Author SHA1 Message Date
Bogdan
95f899131d Fix count in OrpheusFixture 2023-11-07 15:38:49 +02:00
Bogdan
0ba4f3e692 Fixed: (Orpheus) Filter old releases on RSS 2023-11-07 15:03:50 +02:00
Bogdan
a7c00a0fd7 Fixed: (Redacted) Filter old releases on RSS 2023-11-07 15:03:49 +02:00
Bogdan
c84ff60ec9 Fixed: (PTP) Add TV search capabilities 2023-11-05 21:00:24 +02:00
Bogdan
b3f6f54e6e Fixed: (PTP) Add support for TV searches 2023-11-05 20:53:21 +02:00
Bogdan
ed272aaf74 Increase the timeout for CheckHealth command 2023-11-05 20:17:02 +02:00
Bogdan
c0b10f889b Prevent NullRef on header assert 2023-11-05 19:58:30 +02:00
Bogdan
bbfb92bbd8 Bump version to 1.10.3 2023-11-05 11:10:44 +02:00
Bogdan
793de05e3d Fixed: (Beyond-HD) Types filtering 2023-11-04 17:36:01 +02:00
Bogdan
1b1f9d16be Remove default definitions for dead indexers 2023-11-04 16:30:40 +02:00
Bogdan
051dea30c2 Fixed: (Beyond-HD) Category filtering 2023-11-04 16:27:39 +02:00
Bogdan
75d8a3d1d0 Fixed: (FileList) Change TZ to account DST 2023-11-02 19:40:38 +02:00
Bogdan
edf41e2ead Bump version to 1.10.2 2023-10-29 10:35:43 +02:00
Bogdan
c15c71386d New: Set busy timeout for SQLite
(cherry picked from commit 192eb7b62ae60f300a9371ce3ed2e0056b5a1f4d)
2023-10-29 01:28:13 +03:00
Bogdan
71a19efd9a Improve appearance of info fields 2023-10-27 20:54:50 +03:00
Bogdan
2c6c0fcc81 Allow 0 as value in download client Id validation 2023-10-25 17:11:48 +03:00
Bogdan
203e2dbb10 Remove mandatory validation for download client in indexers 2023-10-25 16:24:30 +03:00
Bogdan
6169fc2fa3 New: Add Download Client validation for indexers 2023-10-25 15:55:38 +03:00
Bogdan
768ce14afb Fix integration tests for indexers 2023-10-25 14:17:22 +03:00
Servarr
31d32e8c30 Automated API Docs update 2023-10-25 13:38:07 +03:00
Bogdan
7a61761b2b Add schema endpoint for app profiles 2023-10-25 13:25:16 +03:00
Bogdan
3963807c96 New: Add App Profile validation for indexers
Fixes #1903
2023-10-25 13:04:02 +03:00
Bogdan
e0f6726a3d Fixed: Detect Raw search in Generic Torznab/Newznab feeds
Fixes #1895
2023-10-25 11:51:31 +03:00
Weblate
dd25bff3d6 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dlgeri123 <bornemiszageri@gmail.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Jhonata da Rocha <jhonata182@gmail.com>
Co-authored-by: LandonLi <lxx4work@gmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: hpoon <henry.yh.poon@gmail.com>
Co-authored-by: jianl <jianjianfengyun@126.com>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-10-25 11:26:16 +03:00
Jordy
d834c4292e Update link to Docker instructions in readme (#1905) 2023-10-25 11:16:13 +03:00
Bogdan
7e8272ec2b Bump version to 1.10.1 2023-10-22 09:32:32 +03:00
Bogdan
62548f32fe Fixed: (FileList) Skip ID searches for daily episodes 2023-10-21 12:46:50 +03:00
Bogdan
db9f061564 Return TV category if season/episode detected in title for PTP 2023-10-20 00:13:55 +03:00
Bogdan
b37d8799a0 Add acronym for PrivateHD 2023-10-16 18:45:06 +03:00
DaftFuzz
4366530409 Fixed: Calculating value for peers filter (#1900)
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2023-10-16 18:21:40 +03:00
Bogdan
c7959f735e Bump version to 1.10.0 2023-10-15 21:46:33 +03:00
Gabe
be3ee00e1f Fixed: Remove Defunct Tracker PirateTheNet
PTN closed down for good.

They had an official announcement to close down on the 14th October.

Aither offered to accept refugees. See /forums/topics/2535 on aither for more context.
Description

The definition is no longer needed as the tracker ceased all operation.
2023-10-15 19:22:06 +03:00
Bogdan
dace1982d6 Fixed: (FileList) Use air date in search query 2023-10-15 17:44:50 +03:00
Bogdan
980bd35f95 Revert dependencies update 2023-10-14 15:34:41 +03:00
Bogdan
4b2f81bee8 Update postcss, webpack and core-js 2023-10-14 15:19:56 +03:00
Bogdan
30eb481c65 Add year to releases for AnimeBytes 2023-10-14 10:17:54 +03:00
Bogdan
29f1c36f54 Prevent NullRef in Nebulance with null TvMazeIds 2023-10-13 18:55:04 +03:00
Bogdan
f1c01343bf New: (Nebulance) Parse TvMazeId and scene attributes 2023-10-13 14:27:32 +03:00
Weblate
bae79b22ad Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: DavidHenryThoreau <sorau@protonmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translation: Servarr/Prowlarr
2023-10-13 11:55:11 +03:00
Bogdan
229d879f86 New: Show indexer categories in Add Indexer modal 2023-10-13 09:39:51 +03:00
bakerboy448
d1cee950a4 Fixed: New Indexer URL for DICMusic 2023-10-13 04:21:09 +03:00
Bogdan
7e32b54547 Fixed: Prevent NullRef in NewznabRequestGenerator for missing definition 2023-10-12 06:50:13 +03:00
Bogdan
b1f7d30021 Fixed: Ignore case when cleansing announce URLs 2023-10-12 05:10:26 +03:00
Mark McDowall
c41a7e0ccc Fixed: Duplicate notifications for failed health checks
(cherry picked from commit c0e54773e213f526a5046fa46aa7b57532471128)

Mock debouncer for testing

(cherry picked from commit bb7b2808e2f70389157408809ec47cc8860b4938)
2023-10-11 03:34:18 +03:00
Weblate
42c533386b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Timo <Tclemens@live.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: blankhang <blankhang@gmail.com>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-10-11 02:52:16 +03:00
Servarr
bdae7a2cdc Automated API Docs update 2023-10-11 02:51:54 +03:00
Mark McDowall
5e8d3542f4 Log executing health check
(cherry picked from commit 78b39bd2fecda60e04a1fef17ae17f62bd2b6914)
2023-10-11 02:33:38 +03:00
Mark McDowall
d9d2aa8493 Sync label styling for FormLabel
(cherry picked from commit 87e0a7983a437a4d166aa8b9c9eaf78ea5431969)
2023-10-11 02:30:18 +03:00
Stevie Robinson
09bf1500d6 New: Additional tooltips for icon buttons
(cherry picked from commit 8c07f0d3d19a48ed96d1ded54399c66bf2977b2a)
2023-10-11 02:24:32 +03:00
Mark McDowall
34464160cb Paging params in API docs
(cherry picked from commit bfaa7291e14a8d3847ef2154a52c363944560803)
2023-10-11 02:17:27 +03:00
Bogdan
bada5fe309 New: Add TorrentNetwork 2023-10-10 13:29:26 +03:00
Bogdan
b088febbc4 Use async for http client test 2023-10-10 03:05:16 +03:00
Bogdan
1a307b8e21 Fix test for http client 2023-10-10 03:02:35 +03:00
Bogdan
32db2af0ea Improved http timeout handling
Co-authored-by: Qstick <qstick@gmail.com>
2023-10-10 02:41:05 +03:00
Bogdan
e602862102 Fixed: (FileList) Remove dead domain 2023-10-08 21:31:38 +03:00
Bogdan
bd5336e4c4 Bump version to 1.9.4 2023-10-08 07:08:25 +03:00
Mark McDowall
c664eaa9b5 New: Don't treat 400 responses from Notifiarr as errors
(cherry picked from commit 5eb420bbe12f59d0a5392abf3d351be28ca210e6)
2023-10-07 23:02:44 +03:00
Bogdan
b7e57f0c08 Fixed: (Nebulance) Filter releases by season and episode for ID based searches 2023-10-07 03:09:44 +03:00
Bogdan
c06bf0e4ea Fixed: (TorrentDay) Update categories
Fixes #1888
2023-10-05 22:41:59 +03:00
Bogdan
c6db30c35a Parse description in RSS Parser 2023-10-05 01:54:40 +03:00
Bogdan
75c30dd318 Add year to XML results 2023-10-04 19:51:49 +03:00
Bogdan
6e7bf55dbd Add poster URL to PassThePopcorn 2023-10-04 19:16:55 +03:00
Bogdan
eb642dd2f9 Fix document being disposed before returning 2023-10-04 18:36:31 +03:00
Bogdan
19a196e2c7 Ensure the correct use of disposable parsed documents 2023-10-04 18:11:23 +03:00
Weblate
93ec6cf89b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dimitri <dimitridroeck@gmail.com>
Co-authored-by: Garkus98 <ivan12061998@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-10-04 07:00:00 +03:00
Bogdan
52c6b56a4c Cleanup BooleanConverter to STJson 2023-10-04 05:19:03 +03:00
Bogdan
82688d8a55 Use ExecuteAuth in AvistazBase 2023-10-04 04:27:04 +03:00
Bogdan
c81cbc801a Fixed: (AvistaZBase) Parse response with STJson
Also ensure GetToken is using a proxied request and rate limit
2023-10-04 02:27:32 +03:00
Bogdan
993d189c61 Fixed: (Nebulance) Parse response with STJson 2023-10-04 02:27:29 +03:00
Bogdan
1901af5a51 Fixed: (BeyondHD) Parse response with STJson 2023-10-04 02:27:24 +03:00
Bogdan
c1b399be39 Fixed: (FileList) Parse response with STJson 2023-10-04 02:27:19 +03:00
Bogdan
2100e96570 Fixed: (PassThePopcorn) Use UTC for publish dates 2023-10-02 04:35:57 +03:00
Bogdan
3ff144421d Fixed: (PassThePopcorn) Cleanup and ensure pagination is working in Radarr 2023-10-02 02:55:34 +03:00
Bogdan
f37ccba3f9 Fixed: (Shizaproject) Title improvements 2023-10-02 00:47:33 +03:00
Bogdan
181cb2e0fe Revert "New: (Orpheus) Add options to prevent downloads without FL tokens"
This reverts commit 93c81bb7d3.
2023-10-01 19:47:28 +03:00
Bogdan
93c81bb7d3 New: (Orpheus) Add options to prevent downloads without FL tokens 2023-10-01 17:03:43 +03:00
Weblate
7dd289b5f9 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Jaspils <jasperkemper@gmail.com>
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-10-01 17:03:03 +03:00
Bogdan
09cef8cf94 Bump version to 1.9.3 2023-10-01 17:01:35 +03:00
Bogdan
ca08c818e6 Fixed: (TorrentPotato) Use full IMDb Id 2023-09-30 22:41:25 +03:00
Bogdan
3e95bc4056 Fixed: (TorrentPotato) Title not being decoded 2023-09-30 22:15:25 +03:00
ilike2burnthing
e241112915 Fixed: (Shizaproject) Available again 2023-09-29 22:13:14 +03:00
Bogdan
0d98c12fa2 Fix the description for Use Filenames for Single Episodes 2023-09-29 20:45:04 +03:00
Bogdan
a0bcf5c9ae Allow using filename for single episodes along with generated titles in AnimeBytes 2023-09-29 20:26:35 +03:00
Bogdan
e318a47b3a Extend the torrent settings interface in TorrentPotato settings class 2023-09-27 23:23:43 +03:00
Bogdan
b8df720c6c Bump version to 1.9.2 2023-09-24 16:22:57 +03:00
Bogdan
9625be723d Fixed: (Search) Releases deduplication 2023-09-22 19:54:11 +03:00
Bogdan
d4b037db78 New: (AlphaRatio) Add pagination support 2023-09-21 20:46:34 +03:00
Bogdan
add2988789 Fixed: (Cardigann) Fallback variables to empty string to prevent NullRef 2023-09-20 02:54:25 +03:00
Bogdan
9869c2272a Avoid returning null in static resource mapper Task 2023-09-19 20:17:05 +03:00
Bogdan
4c8b0c9eec Fixed: Ignore releases without title 2023-09-19 14:06:15 +03:00
Bogdan
43cb22ff2b Bump migration timeout to 10 minutes 2023-09-19 00:57:06 +03:00
Bogdan
3cabc0589a Simplify use the group name when the release name is empty 2023-09-18 21:17:36 +03:00
Bogdan
cdb3ed36f6 Fixed: (Nebulance) Use the group name when the release name is empty 2023-09-18 21:16:13 +03:00
Bogdan
840f2ae3e6 Sync static resource controller with upstream
(cherry picked from commit ad1f185330a30a2a9d27c9d3f18d384e66727c2a)
2023-09-18 03:53:14 +03:00
Bogdan
3ed6ef0336 Use await on reading the response content
(cherry picked from commit 82d586e7015d7ea06356ca436024a8af5a4fb677)
2023-09-18 03:49:31 +03:00
Bogdan
c2ae0cce03 Bump version to 1.9.1 2023-09-17 12:01:01 +03:00
140 changed files with 1583 additions and 1166 deletions

View File

@@ -2,7 +2,7 @@
[![Build Status](https://dev.azure.com/Prowlarr/Prowlarr/_apis/build/status/Prowlarr.Prowlarr?branchName=develop)](https://dev.azure.com/Prowlarr/Prowlarr/_build/latest?definitionId=1&branchName=develop)
[![Translated](https://translate.servarr.com/widgets/servarr/-/prowlarr/svg-badge.svg)](https://translate.servarr.com/engage/prowlarr/?utm_source=widget)
[![Docker Pulls](https://img.shields.io/docker/pulls/hotio/prowlarr.svg)](https://wiki.servarr.com/prowlarr/installation#docker)
[![Docker Pulls](https://img.shields.io/docker/pulls/hotio/prowlarr.svg)](https://wiki.servarr.com/prowlarr/installation/docker)
![Github Downloads](https://img.shields.io/github/downloads/Prowlarr/Prowlarr/total.svg)
[![Backers on Open Collective](https://opencollective.com/Prowlarr/backers/badge.svg)](#backers)
[![Sponsors on Open Collective](https://opencollective.com/Prowlarr/sponsors/badge.svg)](#sponsors)

View File

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

View File

@@ -2,8 +2,10 @@
display: flex;
justify-content: flex-end;
margin-right: $formLabelRightMarginWidth;
padding-top: 8px;
min-height: 35px;
text-align: end;
font-weight: bold;
line-height: 35px;
}
.hasError {

View File

@@ -1,5 +1,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import { kinds } from 'Helpers/Props';
class InfoInput extends Component {
@@ -7,12 +9,12 @@ class InfoInput extends Component {
// Render
render() {
const {
value
} = this.props;
const { value } = this.props;
return (
<span dangerouslySetInnerHTML={{ __html: value }} />
<Alert kind={kinds.INFO}>
<span dangerouslySetInnerHTML={{ __html: value }} />
</Alert>
);
}
}

View File

@@ -3,6 +3,7 @@ import React from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ModalContent.css';
function ModalContent(props) {
@@ -28,6 +29,7 @@ function ModalContent(props) {
<Icon
name={icons.CLOSE}
size={18}
title={translate('Close')}
/>
</Link>
}

View File

@@ -78,6 +78,7 @@ class PageHeader extends Component {
aria-label="Donate"
to="https://prowlarr.com/donate"
size={14}
title={translate('Donate')}
/>
<IconButton
className={styles.translate}

View File

@@ -24,6 +24,7 @@ function PageHeaderActionsMenu(props) {
<MenuButton className={styles.menuButton} aria-label="Menu Button">
<Icon
name={icons.INTERACTIVE}
title={translate('Menu')}
/>
</MenuButton>

View File

@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import AddIndexerModalContentConnector from './AddIndexerModalContentConnector';
import styles from './AddIndexerModal.css';
@@ -8,6 +9,7 @@ function AddIndexerModal({ isOpen, onModalClose, onSelectIndexer, ...otherProps
return (
<Modal
isOpen={isOpen}
size={sizes.EXTRA_LARGE}
onModalClose={onModalClose}
className={styles.modal}
>

View File

@@ -16,7 +16,7 @@ import TableBody from 'Components/Table/TableBody';
import { kinds, scrollDirections } from 'Helpers/Props';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import SelectIndexerRowConnector from './SelectIndexerRowConnector';
import SelectIndexerRow from './SelectIndexerRow';
import styles from './AddIndexerModalContent.css';
const columns = [
@@ -49,6 +49,12 @@ const columns = [
label: () => translate('Privacy'),
isSortable: true,
isVisible: true
},
{
name: 'categories',
label: () => translate('Categories'),
isSortable: false,
isVisible: true
}
];
@@ -260,7 +266,7 @@ class AddIndexerModalContent extends Component {
<TableBody>
{
filteredIndexers.map((indexer) => (
<SelectIndexerRowConnector
<SelectIndexerRow
key={`${indexer.implementation}-${indexer.name}`}
implementation={indexer.implementation}
implementationName={indexer.implementationName}

View File

@@ -1,15 +1,18 @@
import { some } from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchIndexerSchema, selectIndexerSchema, setIndexerSchemaSort } from 'Store/Actions/indexerActions';
import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import AddIndexerModalContent from './AddIndexerModalContent';
function createMapStateToProps() {
return createSelector(
createClientSideCollectionSelector('indexers.schema'),
(indexers) => {
createAllIndexersSelector(),
(indexers, allIndexers) => {
const {
isFetching,
isPopulated,
@@ -19,11 +22,19 @@ function createMapStateToProps() {
sortKey
} = indexers;
const indexerList = items.map((item) => {
const { definitionName } = item;
return {
...item,
isExistingIndexer: some(allIndexers, { definitionName })
};
});
return {
isFetching,
isPopulated,
error,
indexers: items,
indexers: indexerList,
sortKey,
sortDirection
};

View File

@@ -1,90 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRowButton from 'Components/Table/TableRowButton';
import { icons } from 'Helpers/Props';
import ProtocolLabel from 'Indexer/Index/Table/ProtocolLabel';
import firstCharToUpper from 'Utilities/String/firstCharToUpper';
import translate from 'Utilities/String/translate';
import styles from './SelectIndexerRow.css';
class SelectIndexerRow extends Component {
//
// Listeners
onPress = () => {
const {
implementation,
implementationName,
name
} = this.props;
this.props.onIndexerSelect({ implementation, implementationName, name });
};
//
// Render
render() {
const {
protocol,
privacy,
name,
language,
description,
isExistingIndexer
} = this.props;
return (
<TableRowButton onPress={this.onPress}>
<TableRowCell className={styles.protocol}>
<ProtocolLabel
protocol={protocol}
/>
</TableRowCell>
<TableRowCell>
{name}
{
isExistingIndexer ?
<Icon
className={styles.alreadyExistsIcon}
name={icons.CHECK_CIRCLE}
size={15}
title={translate('IndexerAlreadySetup')}
/> :
null
}
</TableRowCell>
<TableRowCell>
{language}
</TableRowCell>
<TableRowCell>
{description}
</TableRowCell>
<TableRowCell>
{translate(firstCharToUpper(privacy))}
</TableRowCell>
</TableRowButton>
);
}
}
SelectIndexerRow.propTypes = {
name: PropTypes.string.isRequired,
protocol: PropTypes.string.isRequired,
privacy: PropTypes.string.isRequired,
language: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
implementation: PropTypes.string.isRequired,
implementationName: PropTypes.string.isRequired,
onIndexerSelect: PropTypes.func.isRequired,
isExistingIndexer: PropTypes.bool.isRequired
};
export default SelectIndexerRow;

View File

@@ -0,0 +1,75 @@
import React, { useCallback } from 'react';
import Icon from 'Components/Icon';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRowButton from 'Components/Table/TableRowButton';
import { icons } from 'Helpers/Props';
import CapabilitiesLabel from 'Indexer/Index/Table/CapabilitiesLabel';
import ProtocolLabel from 'Indexer/Index/Table/ProtocolLabel';
import { IndexerCapabilities } from 'Indexer/Indexer';
import firstCharToUpper from 'Utilities/String/firstCharToUpper';
import translate from 'Utilities/String/translate';
import styles from './SelectIndexerRow.css';
interface SelectIndexerRowProps {
name: string;
protocol: string;
privacy: string;
language: string;
description: string;
capabilities: IndexerCapabilities;
implementation: string;
implementationName: string;
isExistingIndexer: boolean;
onIndexerSelect(...args: unknown[]): void;
}
function SelectIndexerRow(props: SelectIndexerRowProps) {
const {
name,
protocol,
privacy,
language,
description,
capabilities,
implementation,
implementationName,
isExistingIndexer,
onIndexerSelect,
} = props;
const onPress = useCallback(() => {
onIndexerSelect({ implementation, implementationName, name });
}, [implementation, implementationName, name, onIndexerSelect]);
return (
<TableRowButton onPress={onPress}>
<TableRowCell className={styles.protocol}>
<ProtocolLabel protocol={protocol} />
</TableRowCell>
<TableRowCell>
{name}
{isExistingIndexer ? (
<Icon
className={styles.alreadyExistsIcon}
name={icons.CHECK_CIRCLE}
size={15}
title={translate('IndexerAlreadySetup')}
/>
) : null}
</TableRowCell>
<TableRowCell>{language}</TableRowCell>
<TableRowCell>{description}</TableRowCell>
<TableRowCell>{translate(firstCharToUpper(privacy))}</TableRowCell>
<TableRowCell>
<CapabilitiesLabel capabilities={capabilities} />
</TableRowCell>
</TableRowButton>
);
}
export default SelectIndexerRow;

View File

@@ -1,18 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createExistingIndexerSelector from 'Store/Selectors/createExistingIndexerSelector';
import SelectIndexerRow from './SelectIndexerRow';
function createMapStateToProps() {
return createSelector(
createExistingIndexerSelector(),
(isExistingIndexer, dimensions) => {
return {
isExistingIndexer
};
}
);
}
export default connect(createMapStateToProps)(SelectIndexerRow);

View File

@@ -1,3 +1,4 @@
import { uniqBy } from 'lodash';
import React from 'react';
import Label from 'Components/Label';
import { IndexerCapabilities } from 'Indexer/Indexer';
@@ -23,14 +24,18 @@ function CapabilitiesLabel(props: CapabilitiesLabelProps) {
);
}
const nameList = Array.from(
new Set(filteredList.map((item) => item.name).sort())
const indexerCategories = uniqBy(filteredList, 'id').sort(
(a, b) => a.id - b.id
);
return (
<span>
{nameList.map((category) => {
return <Label key={category}>{category}</Label>;
{indexerCategories.map((category) => {
return (
<Label key={category.id} title={`${category.id}`}>
{category.name}
</Label>
);
})}
{filteredList.length === 0 ? <Label>{'None'}</Label> : null}

View File

@@ -1,43 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Label from 'Components/Label';
import Tooltip from 'Components/Tooltip/Tooltip';
import { kinds, tooltipPositions } from 'Helpers/Props';
function CategoryLabel({ categories }) {
const sortedCategories = categories.filter((cat) => cat.name !== undefined).sort((c) => c.id);
if (categories?.length === 0) {
return (
<Tooltip
anchor={<Label kind={kinds.DANGER}>Unknown</Label>}
tooltip="Please report this issue to the GitHub as this shouldn't be happening"
position={tooltipPositions.LEFT}
/>
);
}
return (
<span>
{
sortedCategories.map((category) => {
return (
<Label key={category.name}>
{category.name}
</Label>
);
})
}
</span>
);
}
CategoryLabel.defaultProps = {
categories: []
};
CategoryLabel.propTypes = {
categories: PropTypes.arrayOf(PropTypes.object).isRequired
};
export default CategoryLabel;

View File

@@ -0,0 +1,36 @@
import React from 'react';
import Label from 'Components/Label';
import Tooltip from 'Components/Tooltip/Tooltip';
import { kinds, tooltipPositions } from 'Helpers/Props';
import { IndexerCategory } from 'Indexer/Indexer';
import translate from 'Utilities/String/translate';
interface CategoryLabelProps {
categories: IndexerCategory[];
}
function CategoryLabel({ categories = [] }: CategoryLabelProps) {
if (categories?.length === 0) {
return (
<Tooltip
anchor={<Label kind={kinds.DANGER}>{translate('Unknown')}</Label>}
tooltip="Please report this issue to the GitHub as this shouldn't be happening"
position={tooltipPositions.LEFT}
/>
);
}
const sortedCategories = categories
.filter((cat) => cat.name !== undefined)
.sort((a, b) => a.id - b.id);
return (
<span>
{sortedCategories.map((category) => {
return <Label key={category.id}>{category.name}</Label>;
})}
</span>
);
}
export default CategoryLabel;

View File

@@ -3,7 +3,7 @@ import React from 'react';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import Icon from 'Components/Icon';
import { filterBuilderTypes, filterBuilderValueTypes, icons, sortDirections } from 'Helpers/Props';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, icons, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import getSectionState from 'Utilities/State/getSectionState';
@@ -169,6 +169,18 @@ export const defaultState = {
}
],
filterPredicates: {
peers: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const seeders = item.seeders || 0;
const leechers = item.leechers || 0;
const peers = seeders + leechers;
return predicate(peers, filterValue);
}
},
filterBuilderProps: [
{
name: 'title',

View File

@@ -105,7 +105,7 @@
"babel-loader": "9.1.3",
"babel-plugin-inline-classnames": "2.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"core-js": "3.32.1",
"core-js": "3.33.0",
"css-loader": "6.7.3",
"css-modules-typescript-loader": "4.0.1",
"eslint": "8.45.0",
@@ -122,7 +122,7 @@
"html-webpack-plugin": "5.5.1",
"loader-utils": "^3.2.1",
"mini-css-extract-plugin": "2.7.5",
"postcss": "8.4.23",
"postcss": "8.4.31",
"postcss-color-function": "4.1.0",
"postcss-loader": "7.3.0",
"postcss-mixins": "9.0.4",
@@ -141,7 +141,7 @@
"ts-loader": "9.4.2",
"typescript-plugin-css-modules": "5.0.1",
"url-loader": "4.1.1",
"webpack": "5.88.1",
"webpack": "5.89.0",
"webpack-cli": "5.1.4",
"webpack-livereload-plugin": "3.0.2"
}

View File

@@ -128,6 +128,16 @@ namespace NzbDrone.Common.Test.Http
response.Content.Should().NotBeNullOrWhiteSpace();
}
[Test]
public void should_throw_timeout_request()
{
var request = new HttpRequest($"https://{_httpBinHost}/delay/10");
request.RequestTimeout = new TimeSpan(0, 0, 5);
Assert.ThrowsAsync<WebException>(async () => await Subject.ExecuteAsync(request));
}
[Test]
public void should_execute_https_get()
{

View File

@@ -89,8 +89,16 @@ namespace NzbDrone.Common.Test.InstrumentationTests
// Download Station
[TestCase(@"webapi/entry.cgi?api=(removed)&version=2&method=login&account=01233210&passwd=mySecret&format=sid&session=DownloadStation")]
// Tracker Responses
[TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")]
// Announce URLs (passkeys) Magnet & Tracker
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210IMAveQL2tyu8xyui%2fannounce""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2ftracker.php%2f9pr04sg601233210IMAveQL2tyu8xyui%2fannounce""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce%2f9pr04sg601233210IMAveQL2tyu8xyui""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce.php%3fpasskey%3d9pr04sg601233210IMAveQL2tyu8xyui""}")]
[TestCase(@"tracker"":""https://xxx.yyy/9pr04sg601233210IMAveQL2tyu8xyui/announce""}")]
[TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210IMAveQL2tyu8xyui/announce""}")]
[TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210IMAveQL2tyu8xyui""}")]
[TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210IMAveQL2tyu8xyui""}")]
[TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210IMAveQL2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")]
// BroadcastheNet
[TestCase(@"method: ""getTorrents"", ""params"": [ ""mySecret"",")]

View File

@@ -160,7 +160,7 @@ namespace NzbDrone.Common.Extensions
{
if (text.IsNullOrWhiteSpace())
{
throw new ArgumentNullException("text");
throw new ArgumentNullException(nameof(text));
}
return text.IndexOfAny(Path.GetInvalidPathChars()) >= 0;

View File

@@ -107,52 +107,59 @@ namespace NzbDrone.Common.Http.Dispatchers
sw.Start();
using var responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
try
{
byte[] data = null;
try
using var responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cts.Token);
{
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
byte[] data = null;
try
{
await responseMessage.Content.CopyToAsync(request.ResponseStream, null, cts.Token);
}
else
{
data = responseMessage.Content.ReadAsByteArrayAsync(cts.Token).GetAwaiter().GetResult();
}
}
catch (Exception ex)
{
throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, null);
}
var headers = responseMessage.Headers.ToNameValueCollection();
headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
var responseCookies = new CookieContainer();
if (responseMessage.Headers.TryGetValues("Set-Cookie", out var cookieHeaders))
{
foreach (var responseCookieHeader in cookieHeaders)
{
try
if (request.ResponseStream != null && responseMessage.StatusCode == HttpStatusCode.OK)
{
cookies.SetCookies(responseMessage.RequestMessage.RequestUri, responseCookieHeader);
await responseMessage.Content.CopyToAsync(request.ResponseStream, null, cts.Token);
}
catch
else
{
// Ignore invalid cookies
data = await responseMessage.Content.ReadAsByteArrayAsync(cts.Token);
}
}
catch (Exception ex)
{
throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, null);
}
var headers = responseMessage.Headers.ToNameValueCollection();
headers.Add(responseMessage.Content.Headers.ToNameValueCollection());
var responseCookies = new CookieContainer();
if (responseMessage.Headers.TryGetValues("Set-Cookie", out var cookieHeaders))
{
foreach (var responseCookieHeader in cookieHeaders)
{
try
{
cookies.SetCookies(responseMessage.RequestMessage.RequestUri, responseCookieHeader);
}
catch
{
// Ignore invalid cookies
}
}
}
var cookieCollection = cookies.GetCookies(responseMessage.RequestMessage.RequestUri);
sw.Stop();
return new HttpResponse(request, new HttpHeader(headers), cookieCollection, data, sw.ElapsedMilliseconds, responseMessage.StatusCode, responseMessage.Version);
}
var cookieCollection = cookies.GetCookies(responseMessage.RequestMessage.RequestUri);
sw.Stop();
return new HttpResponse(request, new HttpHeader(headers), cookieCollection, data, sw.ElapsedMilliseconds, responseMessage.StatusCode, responseMessage.Version);
}
catch (OperationCanceledException ex) when (cts.IsCancellationRequested)
{
throw new WebException("Http request timed out", ex.InnerException, WebExceptionStatus.Timeout, null);
}
}

View File

@@ -30,7 +30,7 @@ namespace NzbDrone.Common.Instrumentation
new (@"""/(home|Users)/(?<secret>[^/""]+?)(/|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// NzbGet
new (@"""Name""\s*:\s*""[^""]*(username|password)""\s*,\s*""Value""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),

View File

@@ -0,0 +1,29 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace NzbDrone.Common.Serializer;
public class BooleanConverter : JsonConverter<bool>
{
public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.TokenType switch
{
JsonTokenType.True => true,
JsonTokenType.False => false,
JsonTokenType.Number => reader.GetInt64() switch
{
1 => true,
0 => false,
_ => throw new JsonException()
},
_ => throw new JsonException()
};
}
public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
{
writer.WriteBooleanValue(value);
}
}

View File

@@ -36,6 +36,7 @@ namespace NzbDrone.Common.Serializer
serializerSettings.Converters.Add(new STJTimeSpanConverter());
serializerSettings.Converters.Add(new STJUtcConverter());
serializerSettings.Converters.Add(new DictionaryStringObjectConverter());
serializerSettings.Converters.Add(new BooleanConverter());
}
public static T Deserialize<T>(string json)

View File

@@ -0,0 +1,17 @@
using System;
namespace NzbDrone.Common.TPL
{
public interface IDebounceManager
{
Debouncer CreateDebouncer(Action action, TimeSpan debounceDuration);
}
public class DebounceManager : IDebounceManager
{
public Debouncer CreateDebouncer(Action action, TimeSpan debounceDuration)
{
return new Debouncer(action, debounceDuration);
}
}
}

View File

@@ -4,11 +4,11 @@ namespace NzbDrone.Common.TPL
{
public class Debouncer
{
private readonly Action _action;
private readonly System.Timers.Timer _timer;
protected readonly Action _action;
protected readonly System.Timers.Timer _timer;
private volatile int _paused;
private volatile bool _triggered;
protected volatile int _paused;
protected volatile bool _triggered;
public Debouncer(Action action, TimeSpan debounceDuration)
{
@@ -27,7 +27,7 @@ namespace NzbDrone.Common.TPL
}
}
public void Execute()
public virtual void Execute()
{
lock (_timer)
{
@@ -39,7 +39,7 @@ namespace NzbDrone.Common.TPL
}
}
public void Pause()
public virtual void Pause()
{
lock (_timer)
{
@@ -48,7 +48,7 @@ namespace NzbDrone.Common.TPL
}
}
public void Resume()
public virtual void Resume()
{
lock (_timer)
{

View File

@@ -1,10 +1,14 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Messaging;
using NzbDrone.Common.TPL;
using NzbDrone.Core.HealthCheck;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.HealthCheck
{
@@ -19,10 +23,10 @@ namespace NzbDrone.Core.Test.HealthCheck
Mocker.SetConstant<IEnumerable<IProvideHealthCheck>>(new[] { _healthCheck });
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
Mocker.SetConstant<IDebounceManager>(Mocker.Resolve<DebounceManager>());
Mocker.GetMock<IServerSideNotificationService>()
.Setup(v => v.GetServerChecks())
.Returns(new List<Core.HealthCheck.HealthCheck>());
Mocker.GetMock<IDebounceManager>().Setup(s => s.CreateDebouncer(It.IsAny<Action>(), It.IsAny<TimeSpan>()))
.Returns<Action, TimeSpan>((a, t) => new MockDebouncer(a, t));
}
[Test]

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 19:20:19"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 20:20:19"));
torrentInfo.Size.Should().Be(8300512414);
torrentInfo.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -19,6 +19,11 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
[SetUp]
public void SetUp()
{
Subject.Definition = new IndexerDefinition
{
Name = "Newznab"
};
Subject.Settings = new NewznabSettings()
{
BaseUrl = "http://127.0.0.1:1234/",

View File

@@ -39,7 +39,7 @@ namespace NzbDrone.Core.Test.IndexerTests.OrpheusTests
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new[] { 3000 } })).Releases;
releases.Should().HaveCount(65);
releases.Should().HaveCount(50);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = releases.First() as TorrentInfo;

View File

@@ -6,9 +6,8 @@ using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.PassThePopcorn;
using NzbDrone.Core.Indexers.Definitions.PassThePopcorn;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
@@ -21,26 +20,22 @@ namespace NzbDrone.Core.Test.IndexerTests.PTPTests
[SetUp]
public void Setup()
{
Subject.Definition = new IndexerDefinition()
Subject.Definition = new IndexerDefinition
{
Name = "PTP",
Settings = new PassThePopcornSettings() { APIUser = "asdf", APIKey = "sad" }
Settings = new PassThePopcornSettings
{
APIUser = "asdf",
APIKey = "sad"
}
};
}
[TestCase("Files/Indexers/PTP/imdbsearch.json")]
public async Task should_parse_feed_from_PTP(string fileName)
{
var authResponse = new PassThePopcornAuthResponse { Result = "Ok" };
var authStream = new System.IO.StringWriter();
Json.Serialize(authResponse, authStream);
var responseJson = ReadAllText(fileName);
Mocker.GetMock<IIndexerHttpClient>()
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Post), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), authStream.ToString())));
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 { ContentType = HttpAccept.Json.Value }, new CookieCollection(), responseJson)));

View File

@@ -41,14 +41,16 @@ namespace NzbDrone.Core.Datastore
private static string GetConnectionString(string dbPath)
{
var connectionBuilder = new SQLiteConnectionStringBuilder();
connectionBuilder.DataSource = dbPath;
connectionBuilder.CacheSize = (int)-20000;
connectionBuilder.DateTimeKind = DateTimeKind.Utc;
connectionBuilder.JournalMode = OsInfo.IsOsx ? SQLiteJournalModeEnum.Truncate : SQLiteJournalModeEnum.Wal;
connectionBuilder.Pooling = true;
connectionBuilder.Version = 3;
var connectionBuilder = new SQLiteConnectionStringBuilder
{
DataSource = dbPath,
CacheSize = (int)-20000,
DateTimeKind = DateTimeKind.Utc,
JournalMode = OsInfo.IsOsx ? SQLiteJournalModeEnum.Truncate : SQLiteJournalModeEnum.Wal,
Pooling = true,
Version = 3,
BusyTimeout = 100
};
if (OsInfo.IsOsx)
{

View File

@@ -52,7 +52,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
.Configure<ProcessorOptions>(opt =>
{
opt.PreviewOnly = false;
opt.Timeout = TimeSpan.FromSeconds(60);
opt.Timeout = TimeSpan.FromMinutes(10);
})
.Configure<SelectingProcessorAccessorOptions>(cfg =>
{

View File

@@ -6,6 +6,7 @@ using NzbDrone.Common.Cache;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Messaging;
using NzbDrone.Common.Reflection;
using NzbDrone.Common.TPL;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
@@ -27,35 +28,35 @@ namespace NzbDrone.Core.HealthCheck
private readonly IProvideHealthCheck[] _startupHealthChecks;
private readonly IProvideHealthCheck[] _scheduledHealthChecks;
private readonly Dictionary<Type, IEventDrivenHealthCheck[]> _eventDrivenHealthChecks;
private readonly IServerSideNotificationService _serverSideNotificationService;
private readonly IEventAggregator _eventAggregator;
private readonly ICacheManager _cacheManager;
private readonly Logger _logger;
private readonly ICached<HealthCheck> _healthCheckResults;
private readonly HashSet<IProvideHealthCheck> _pendingHealthChecks;
private readonly Debouncer _debounce;
private bool _hasRunHealthChecksAfterGracePeriod;
private bool _isRunningHealthChecksAfterGracePeriod;
public HealthCheckService(IEnumerable<IProvideHealthCheck> healthChecks,
IServerSideNotificationService serverSideNotificationService,
IEventAggregator eventAggregator,
ICacheManager cacheManager,
IDebounceManager debounceManager,
IRuntimeInfo runtimeInfo,
Logger logger)
{
_healthChecks = healthChecks.ToArray();
_serverSideNotificationService = serverSideNotificationService;
_eventAggregator = eventAggregator;
_cacheManager = cacheManager;
_logger = logger;
_healthCheckResults = _cacheManager.GetCache<HealthCheck>(GetType());
_healthCheckResults = cacheManager.GetCache<HealthCheck>(GetType());
_pendingHealthChecks = new HashSet<IProvideHealthCheck>();
_debounce = debounceManager.CreateDebouncer(ProcessHealthChecks, TimeSpan.FromSeconds(5));
_startupHealthChecks = _healthChecks.Where(v => v.CheckOnStartup).ToArray();
_scheduledHealthChecks = _healthChecks.Where(v => v.CheckOnSchedule).ToArray();
_eventDrivenHealthChecks = GetEventDrivenHealthChecks();
_startupGracePeriodEndTime = runtimeInfo.StartTime.AddMinutes(15);
_startupGracePeriodEndTime = runtimeInfo.StartTime + TimeSpan.FromMinutes(15);
}
public List<HealthCheck> Results()
@@ -77,63 +78,93 @@ namespace NzbDrone.Core.HealthCheck
.ToDictionary(g => g.Key, g => g.ToArray());
}
private void PerformHealthCheck(IProvideHealthCheck[] healthChecks, bool performServerChecks = false)
private void ProcessHealthChecks()
{
var results = healthChecks.Select(c => c.Check())
.ToList();
List<IProvideHealthCheck> healthChecks;
if (performServerChecks)
lock (_pendingHealthChecks)
{
results.AddRange(_serverSideNotificationService.GetServerChecks());
healthChecks = _pendingHealthChecks.ToList();
_pendingHealthChecks.Clear();
}
foreach (var result in results)
_debounce.Pause();
try
{
if (result.Type == HealthCheckResult.Ok)
{
var previous = _healthCheckResults.Find(result.Source.Name);
if (previous != null)
var results = healthChecks.Select(c =>
{
_eventAggregator.PublishEvent(new HealthCheckRestoredEvent(previous, !_hasRunHealthChecksAfterGracePeriod));
}
_logger.Trace("Check health -> {0}", c.GetType().Name);
var result = c.Check();
_logger.Trace("Check health <- {0}", c.GetType().Name);
_healthCheckResults.Remove(result.Source.Name);
}
else
return result;
})
.ToList();
foreach (var result in results)
{
if (_healthCheckResults.Find(result.Source.Name) == null)
if (result.Type == HealthCheckResult.Ok)
{
_eventAggregator.PublishEvent(new HealthCheckFailedEvent(result, !_hasRunHealthChecksAfterGracePeriod));
}
var previous = _healthCheckResults.Find(result.Source.Name);
_healthCheckResults.Set(result.Source.Name, result);
if (previous != null)
{
_eventAggregator.PublishEvent(new HealthCheckRestoredEvent(previous, !_hasRunHealthChecksAfterGracePeriod));
}
_healthCheckResults.Remove(result.Source.Name);
}
else
{
if (_healthCheckResults.Find(result.Source.Name) == null)
{
_eventAggregator.PublishEvent(new HealthCheckFailedEvent(result, !_hasRunHealthChecksAfterGracePeriod));
}
_healthCheckResults.Set(result.Source.Name, result);
}
}
}
finally
{
_debounce.Resume();
}
_eventAggregator.PublishEvent(new HealthCheckCompleteEvent());
}
public void Execute(CheckHealthCommand message)
{
if (message.Trigger == CommandTrigger.Manual)
var healthChecks = message.Trigger == CommandTrigger.Manual ? _healthChecks : _scheduledHealthChecks;
lock (_pendingHealthChecks)
{
PerformHealthCheck(_healthChecks, true);
}
else
{
PerformHealthCheck(_scheduledHealthChecks, true);
foreach (var healthCheck in healthChecks)
{
_pendingHealthChecks.Add(healthCheck);
}
}
ProcessHealthChecks();
}
public void HandleAsync(ApplicationStartedEvent message)
{
PerformHealthCheck(_startupHealthChecks, true);
lock (_pendingHealthChecks)
{
foreach (var healthCheck in _startupHealthChecks)
{
_pendingHealthChecks.Add(healthCheck);
}
}
ProcessHealthChecks();
}
public void HandleAsync(IEvent message)
{
if (message is HealthCheckCompleteEvent)
if (message is HealthCheckCompleteEvent || message is ApplicationStartedEvent)
{
return;
}
@@ -144,7 +175,16 @@ namespace NzbDrone.Core.HealthCheck
{
_isRunningHealthChecksAfterGracePeriod = true;
PerformHealthCheck(_startupHealthChecks);
lock (_pendingHealthChecks)
{
foreach (var healthCheck in _startupHealthChecks)
{
_pendingHealthChecks.Add(healthCheck);
}
}
// Call it directly so it's not debounced and any alerts can be sent.
ProcessHealthChecks();
// Update after running health checks so new failure notifications aren't sent 2x.
_hasRunHealthChecksAfterGracePeriod = true;
@@ -176,11 +216,16 @@ namespace NzbDrone.Core.HealthCheck
if (eventDrivenHealthCheck.ShouldExecute(message, previouslyFailed))
{
filteredChecks.Add(eventDrivenHealthCheck.HealthCheck);
continue;
}
}
// TODO: Add debounce
PerformHealthCheck(filteredChecks.ToArray());
lock (_pendingHealthChecks)
{
filteredChecks.ForEach(h => _pendingHealthChecks.Add(h));
}
_debounce.Execute();
}
}
}

View File

@@ -9,50 +9,43 @@ using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
namespace NzbDrone.Core.HealthCheck
{
public interface IServerSideNotificationService
{
public List<HealthCheck> GetServerChecks();
}
public class ServerSideNotificationService : IServerSideNotificationService
public class ServerSideNotificationService : HealthCheckBase
{
private readonly IHttpClient _client;
private readonly IProwlarrCloudRequestBuilder _cloudRequestBuilder;
private readonly IConfigFileProvider _configFileProvider;
private readonly IHttpRequestBuilderFactory _cloudRequestBuilder;
private readonly Logger _logger;
private readonly ICached<List<HealthCheck>> _cache;
private readonly ICached<HealthCheck> _cache;
public ServerSideNotificationService(IHttpClient client,
IConfigFileProvider configFileProvider,
IProwlarrCloudRequestBuilder cloudRequestBuilder,
ICacheManager cacheManager,
Logger logger)
public ServerSideNotificationService(IHttpClient client, IProwlarrCloudRequestBuilder cloudRequestBuilder, IConfigFileProvider configFileProvider, ICacheManager cacheManager, ILocalizationService localizationService, Logger logger)
: base(localizationService)
{
_client = client;
_configFileProvider = configFileProvider;
_cloudRequestBuilder = cloudRequestBuilder.Services;
_cloudRequestBuilder = cloudRequestBuilder;
_logger = logger;
_cache = cacheManager.GetCache<List<HealthCheck>>(GetType());
_cache = cacheManager.GetCache<HealthCheck>(GetType());
}
public List<HealthCheck> GetServerChecks()
public override HealthCheck Check()
{
return _cache.Get("ServerChecks", RetrieveServerChecks, TimeSpan.FromHours(2));
}
private List<HealthCheck> RetrieveServerChecks()
private HealthCheck RetrieveServerChecks()
{
if (BuildInfo.IsDebug)
{
return new List<HealthCheck>();
return new HealthCheck(GetType());
}
var request = _cloudRequestBuilder.Create()
var request = _cloudRequestBuilder.Services.Create()
.Resource("/notification")
.AddQueryParam("version", BuildInfo.Version)
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
@@ -63,17 +56,22 @@ namespace NzbDrone.Core.HealthCheck
try
{
_logger.Trace("Getting server side health notifications");
_logger.Trace("Getting notifications");
var response = _client.Execute(request);
var result = Json.Deserialize<List<ServerNotificationResponse>>(response.Content);
return result.Select(x => new HealthCheck(GetType(), x.Type, x.Message, x.WikiUrl)).ToList();
var checks = result.Select(x => new HealthCheck(GetType(), x.Type, x.Message, x.WikiUrl)).ToList();
// Only one health check is supported, services returns an ordered list, so use the first one
return checks.FirstOrDefault() ?? new HealthCheck(GetType());
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to retrieve server notifications");
}
_logger.Error(ex, "Failed to retrieve notifications");
return new List<HealthCheck>();
return new HealthCheck(GetType());
}
}
}

View File

@@ -108,6 +108,7 @@ namespace NzbDrone.Core.IndexerSearch
GetNabElement("files", r.Files, protocol),
GetNabElement("grabs", r.Grabs, protocol),
GetNabElement("peers", t.Peers, protocol),
r.Year == 0 ? null : GetNabElement("year", r.Year, protocol),
GetNabElement("author", RemoveInvalidXMLChars(r.Author), protocol),
GetNabElement("booktitle", RemoveInvalidXMLChars(r.BookTitle), protocol),
GetNabElement("artist", RemoveInvalidXMLChars(r.Artist), protocol),

View File

@@ -67,7 +67,9 @@ namespace NzbDrone.Core.IndexerSearch
searchSpec.Year = request.year;
searchSpec.Genre = request.genre;
return new NewznabResults { Releases = await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec) };
var releases = await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
return new NewznabResults { Releases = DeDupeReleases(releases) };
}
private async Task<NewznabResults> MusicSearch(NewznabRequest request, List<int> indexerIds, bool interactiveSearch)
@@ -81,7 +83,9 @@ namespace NzbDrone.Core.IndexerSearch
searchSpec.Track = request.track;
searchSpec.Year = request.year;
return new NewznabResults { Releases = await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec) };
var releases = await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
return new NewznabResults { Releases = DeDupeReleases(releases) };
}
private async Task<NewznabResults> TvSearch(NewznabRequest request, List<int> indexerIds, bool interactiveSearch)
@@ -102,7 +106,9 @@ namespace NzbDrone.Core.IndexerSearch
searchSpec.Year = request.year;
searchSpec.Genre = request.genre;
return new NewznabResults { Releases = await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec) };
var releases = await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
return new NewznabResults { Releases = DeDupeReleases(releases) };
}
private async Task<NewznabResults> BookSearch(NewznabRequest request, List<int> indexerIds, bool interactiveSearch)
@@ -115,14 +121,18 @@ namespace NzbDrone.Core.IndexerSearch
searchSpec.Year = request.year;
searchSpec.Genre = request.genre;
return new NewznabResults { Releases = await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec) };
var releases = await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
return new NewznabResults { Releases = DeDupeReleases(releases) };
}
private async Task<NewznabResults> BasicSearch(NewznabRequest request, List<int> indexerIds, bool interactiveSearch)
{
var searchSpec = Get<BasicSearchCriteria>(request, indexerIds, interactiveSearch);
return new NewznabResults { Releases = await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec) };
var releases = await Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
return new NewznabResults { Releases = DeDupeReleases(releases) };
}
private TSpec Get<TSpec>(NewznabRequest query, List<int> indexerIds, bool interactiveSearch)
@@ -265,5 +275,13 @@ namespace NzbDrone.Core.IndexerSearch
return Array.Empty<ReleaseInfo>();
}
private List<ReleaseInfo> DeDupeReleases(IList<ReleaseInfo> releases)
{
// De-dupe reports by guid so duplicate results aren't returned. Pick the one with the higher indexer priority.
return releases.GroupBy(r => r.Guid)
.Select(r => r.OrderBy(v => v.IndexerPriority).First())
.ToList();
}
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using NLog;
@@ -5,6 +6,7 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Definitions.Gazelle;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.Definitions;
@@ -15,6 +17,9 @@ public class AlphaRatio : GazelleBase<AlphaRatioSettings>
public override string[] IndexerUrls => new[] { "https://alpharatio.cc/" };
public override string Description => "AlphaRatio(AR) is a Private Torrent Tracker for 0DAY / GENERAL";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override bool SupportsPagination => true;
public override int PageSize => 50;
public override TimeSpan RateLimit => TimeSpan.FromSeconds(3);
public AlphaRatio(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
@@ -39,6 +44,8 @@ public class AlphaRatio : GazelleBase<AlphaRatioSettings>
{
var caps = new IndexerCapabilities
{
LimitsDefault = PageSize,
LimitsMax = PageSize,
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
@@ -98,9 +105,9 @@ public class AlphaRatioRequestGenerator : GazelleRequestGenerator
_settings = settings;
}
protected override NameValueCollection GetBasicSearchParameters(string term, int[] categories)
protected override NameValueCollection GetBasicSearchParameters(SearchCriteriaBase searchCriteria, string term)
{
var parameters = base.GetBasicSearchParameters(term, categories);
var parameters = base.GetBasicSearchParameters(searchCriteria, term);
if (_settings.FreeleechOnly)
{
@@ -112,6 +119,12 @@ public class AlphaRatioRequestGenerator : GazelleRequestGenerator
parameters.Set("scene", "0");
}
if (searchCriteria.Limit is > 0 && searchCriteria.Offset is > 0)
{
var page = (int)(searchCriteria.Offset / searchCriteria.Limit) + 1;
parameters.Set("page", page.ToString());
}
return parameters;
}
}

View File

@@ -200,7 +200,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("div#content table > tbody > tr");
foreach (var row in rows)

View File

@@ -77,7 +77,7 @@ namespace NzbDrone.Core.Indexers.Definitions
else
{
var parser = new HtmlParser();
var document = await parser.ParseDocumentAsync(response.Content);
using var document = await parser.ParseDocumentAsync(response.Content);
var errorMessage = document.QuerySelector("#content .berror .berror_c")?.TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
@@ -433,7 +433,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var torrentInfos = new List<TorrentInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
foreach (var t in dom.QuerySelectorAll("#tabs .torrent_c > div"))
{
@@ -465,7 +465,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var torrentInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var links = dom.QuerySelectorAll(".searchitem > h3 > a[href], #dle-content > .story > .story_h > .lcol > h2 > a[href]");
foreach (var link in links)

View File

@@ -555,6 +555,7 @@ namespace NzbDrone.Core.Indexers.Definitions
MinimumRatio = 1,
MinimumSeedTime = minimumSeedTime,
Title = fileName,
Year = year.GetValueOrDefault(),
InfoUrl = details.AbsoluteUri,
Guid = guid.AbsoluteUri,
DownloadUrl = link.AbsoluteUri,
@@ -571,8 +572,6 @@ namespace NzbDrone.Core.Indexers.Definitions
};
releaseInfos.Add(release);
continue;
}
}
@@ -589,6 +588,7 @@ namespace NzbDrone.Core.Indexers.Definitions
MinimumRatio = 1,
MinimumSeedTime = minimumSeedTime,
Title = releaseTitle.Trim(),
Year = year.GetValueOrDefault(),
InfoUrl = details.AbsoluteUri,
Guid = guid.AbsoluteUri,
DownloadUrl = link.AbsoluteUri,
@@ -699,7 +699,7 @@ namespace NzbDrone.Core.Indexers.Definitions
[FieldDefinition(8, Label = "Enable Sonarr Compatibility", Type = FieldType.Checkbox, HelpText = "Makes Prowlarr try to add Season information into Release names, without this Sonarr can't match any Seasons, but it has a lot of false positives as well")]
public bool EnableSonarrCompatibility { get; set; }
[FieldDefinition(9, Label = "Use Filenames for Single Episodes", Type = FieldType.Checkbox, HelpText = "Makes Prowlarr replace AnimeBytes release names with the actual filename, this currently only works for single episode releases")]
[FieldDefinition(9, Label = "Use Filenames for Single Episodes", Type = FieldType.Checkbox, HelpText = "Add a release using the actual filename, this currently only works for single episode releases")]
public bool UseFilenameForSingleEpisodes { get; set; }
[FieldDefinition(10, Label = "Add Japanese title as a synonym", Type = FieldType.Checkbox, HelpText = "Makes Prowlarr add Japanese titles as synonyms, i.e kanji/hiragana/katakana.")]

View File

@@ -275,7 +275,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("table tr");
foreach (var (row, index) in rows.Skip(1).Select((v, i) => (v, i)))

View File

@@ -253,7 +253,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var torrentInfos = new List<TorrentInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
foreach (var t in dom.QuerySelectorAll("ul.media__tabs__nav > li > a"))
{
@@ -291,7 +291,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var torrentInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var links = dom.QuerySelectorAll("a.ads-list__item__title");
foreach (var link in links)

View File

@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Indexers.Definitions
if (CheckIfLoginNeeded(response))
{
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = parser.ParseDocument(response.Content);
var errorMessage = dom.QuerySelector("form#loginform")?.TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
@@ -206,7 +206,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var torrentInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var doc = parser.ParseDocument(indexerResponse.Content);
using var doc = parser.ParseDocument(indexerResponse.Content);
var rows = doc.QuerySelectorAll("table.torrent_table > tbody > tr.torrent");
foreach (var row in rows)
{

View File

@@ -37,7 +37,7 @@ public class AroLol : GazelleBase<AroLolSettings>
if (response.Content.Contains("loginform"))
{
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = parser.ParseDocument(response.Content);
var errorMessage = dom.QuerySelector("#loginform > .warning")?.TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");

View File

@@ -88,7 +88,7 @@ public class AudioBookBay : TorrentIndexerBase<NoAuthTorrentBaseSettings>
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = parser.ParseDocument(response.Content);
var hash = dom.QuerySelector("td:contains(\"Info Hash:\") ~ td")?.TextContent.Trim();
if (hash == null)
@@ -269,7 +269,7 @@ public class AudioBookBayParser : IParseIndexerResponse
{
var releaseInfos = new List<ReleaseInfo>();
var doc = ParseHtmlDocument(indexerResponse.Content);
using var doc = ParseHtmlDocument(indexerResponse.Content);
var rows = doc.QuerySelectorAll("div.post:has(div[class=\"postTitle\"])");
foreach (var row in rows)

View File

@@ -1,5 +1,5 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{
@@ -9,34 +9,34 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public string Download { get; set; }
public Dictionary<string, string> Category { get; set; }
[JsonProperty(PropertyName = "movie_tv")]
[JsonPropertyName("movie_tv")]
public AvistazIdInfo MovieTvinfo { get; set; }
[JsonProperty(PropertyName = "created_at")]
[JsonPropertyName("created_at")]
public string CreatedAt { get; set; }
[JsonProperty(PropertyName = "file_name")]
[JsonPropertyName("file_name")]
public string FileName { get; set; }
[JsonProperty(PropertyName = "info_hash")]
[JsonPropertyName("info_hash")]
public string InfoHash { get; set; }
public int? Leech { get; set; }
public int? Completed { get; set; }
public int? Seed { get; set; }
[JsonProperty(PropertyName = "file_size")]
[JsonPropertyName("file_size")]
public long? FileSize { get; set; }
[JsonProperty(PropertyName = "file_count")]
[JsonPropertyName("file_count")]
public int? FileCount { get; set; }
[JsonProperty(PropertyName = "download_multiply")]
[JsonPropertyName("download_multiply")]
public double? DownloadMultiply { get; set; }
[JsonProperty(PropertyName = "upload_multiply")]
[JsonPropertyName("upload_multiply")]
public double? UploadMultiply { get; set; }
[JsonProperty(PropertyName = "video_quality")]
[JsonPropertyName("video_quality")]
public string VideoQuality { get; set; }
public string Type { get; set; }
public List<AvistazLanguage> Audio { get; set; }
@@ -64,21 +64,10 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public string Tmdb { get; set; }
public string Tvdb { get; set; }
public string Imdb { get; set; }
public string Title { get; set; }
[JsonProperty(PropertyName = "tv_episode")]
public string TvEpisode { get; set; }
[JsonProperty(PropertyName = "tv_season")]
public string TVSeason { get; set; }
[JsonProperty(PropertyName = "tv_full_season")]
public bool TVFullSeason { get; set; }
}
public class AvistazAuthResponse
{
public string Token { get; set; }
public string Expiry { get; set; }
}
}

View File

@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Messaging.Events;
@@ -122,10 +123,11 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
.Accept(HttpAccept.Json)
.Build();
var response = await _httpClient.PostAsync<AvistazAuthResponse>(authLoginRequest);
var token = response.Resource.Token;
var response = await ExecuteAuth(authLoginRequest);
return token;
var authResponse = STJson.Deserialize<AvistazAuthResponse>(response.Content);
return authResponse.Token;
}
}
}

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Net;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
@@ -42,9 +43,9 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from indexer request, expected {HttpAccept.Json.Value}");
}
var jsonResponse = new HttpResponse<AvistazResponse>(indexerResponse.HttpResponse);
var jsonResponse = STJson.Deserialize<AvistazResponse>(indexerResponse.HttpResponse.Content);
foreach (var row in jsonResponse.Resource.Data)
foreach (var row in jsonResponse.Data)
{
var details = row.Url;
var link = row.Download;

View File

@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Indexers.Definitions
if (CheckIfLoginNeeded(response))
{
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = parser.ParseDocument(response.Content);
var messageEl = dom.QuerySelectorAll("#loginform");
var messages = new List<string>();
for (var i = 0; i < 13; i++)
@@ -242,7 +242,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var torrentInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("#torrent_table > tbody > tr.torrent");
foreach (var row in rows)

View File

@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = parser.ParseDocument(response.Content);
var downloadLink = dom.QuerySelector(".download_link")?.GetAttribute("href");
if (downloadLink.IsNullOrWhiteSpace())
@@ -82,7 +82,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var loginPage = await ExecuteAuth(new HttpRequest(loginUrl));
var parser = new HtmlParser();
var dom = await parser.ParseDocumentAsync(loginPage.Content);
using var dom = await parser.ParseDocumentAsync(loginPage.Content);
var loginKey = dom.QuerySelector("input[name=\"loginKey\"]");
if (loginKey != null)
{
@@ -102,7 +102,7 @@ namespace NzbDrone.Core.Indexers.Definitions
if (CheckIfLoginNeeded(response))
{
var htmlParser = new HtmlParser();
var document = await htmlParser.ParseDocumentAsync(response.Content);
using var document = await htmlParser.ParseDocumentAsync(response.Content);
var errorMessage = document.QuerySelector("#loginError, .error")?.TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
@@ -247,7 +247,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll(".torrents tr.torrent, .torrents tr.torrent_alt");
var currentCategories = new List<IndexerCategory> { NewznabStandardCategory.TVAnime };

View File

@@ -4,8 +4,8 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.Json.Serialization;
using FluentValidation;
using Newtonsoft.Json;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
@@ -19,6 +19,7 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Validation;
using static Newtonsoft.Json.Formatting;
namespace NzbDrone.Core.Indexers.Definitions
{
@@ -129,7 +130,7 @@ namespace NzbDrone.Core.Indexers.Definitions
if (cats.Count > 0)
{
body.Add("categories", string.Join(",", cats));
body.Add("categories", cats.Select(int.Parse).ToArray());
}
if (_settings.SearchTypes.Any())
@@ -142,7 +143,7 @@ namespace NzbDrone.Core.Indexers.Definitions
if (searchTypes.Any())
{
body.Add("types", string.Join(",", searchTypes));
body.Add("types", searchTypes.ToArray());
}
}
@@ -163,7 +164,7 @@ namespace NzbDrone.Core.Indexers.Definitions
Method = HttpMethod.Post
};
request.SetContent(body.ToJson());
request.ContentSummary = body.ToJson(Formatting.None);
request.ContentSummary = body.ToJson(None);
yield return new IndexerRequest(request);
}
@@ -245,16 +246,16 @@ namespace NzbDrone.Core.Indexers.Definitions
throw new IndexerAuthException("API Key invalid or not authorized");
}
var jsonResponse = new HttpResponse<BeyondHDResponse>(indexerHttpResponse);
var jsonResponse = STJson.Deserialize<BeyondHDResponse>(indexerResponse.Content);
if (jsonResponse.Resource.StatusCode == 0)
if (jsonResponse.StatusCode == 0)
{
throw new IndexerException(indexerResponse, $"Indexer Error: {jsonResponse.Resource.StatusMessage}");
throw new IndexerException(indexerResponse, $"Indexer Error: {jsonResponse.StatusMessage}");
}
var releaseInfos = new List<ReleaseInfo>();
foreach (var row in jsonResponse.Resource.Results)
foreach (var row in jsonResponse.Results)
{
var details = row.InfoUrl;
var link = row.DownloadLink;
@@ -412,10 +413,10 @@ namespace NzbDrone.Core.Indexers.Definitions
public class BeyondHDResponse
{
[JsonProperty(PropertyName = "status_code")]
[JsonPropertyName("status_code")]
public int StatusCode { get; set; }
[JsonProperty(PropertyName = "status_message")]
[JsonPropertyName("status_message")]
public string StatusMessage { get; set; }
public List<BeyondHDTorrent> Results { get; set; }
}
@@ -424,36 +425,42 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public string Name { get; set; }
[JsonProperty(PropertyName = "info_hash")]
[JsonPropertyName("info_hash")]
public string InfoHash { get; set; }
public string Category { get; set; }
public string Type { get; set; }
public long Size { get; set; }
[JsonProperty(PropertyName = "times_completed")]
[JsonPropertyName("times_completed")]
public int Grabs { get; set; }
public int Seeders { get; set; }
public int Leechers { get; set; }
[JsonProperty(PropertyName = "created_at")]
[JsonPropertyName("created_at")]
public string CreatedAt { get; set; }
[JsonProperty(PropertyName = "download_url")]
[JsonPropertyName("download_url")]
public string DownloadLink { get; set; }
[JsonProperty(PropertyName = "url")]
[JsonPropertyName("url")]
public string InfoUrl { get; set; }
[JsonProperty(PropertyName = "imdb_id")]
[JsonPropertyName("imdb_id")]
public string ImdbId { get; set; }
[JsonProperty(PropertyName = "tmdb_id")]
[JsonPropertyName("tmdb_id")]
public string TmdbId { get; set; }
public bool Freeleech { get; set; }
public bool Promo25 { get; set; }
public bool Promo50 { get; set; }
public bool Promo75 { get; set; }
public bool Limited { get; set; }
public bool Internal { get; set; }
}
}

View File

@@ -161,7 +161,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var doc = parser.ParseDocument(indexerResponse.Content);
using var doc = parser.ParseDocument(indexerResponse.Content);
var rows = doc.QuerySelectorAll("table.xMenuT > tbody > tr").Skip(1);
foreach (var row in rows)
{

View File

@@ -185,7 +185,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var torrentInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
foreach (var child in dom.QuerySelectorAll("#needseed"))
{
child.Remove();

View File

@@ -374,7 +374,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Cardigann
var newvalue = reReplaceRegexMatches.Groups[3].Value;
var replaceRegex = new Regex(regexp);
var input = (string)variables[variable];
var input = (string)variables[variable] ?? string.Empty;
var expanded = replaceRegex.Replace(input, newvalue);
if (modifier != null)

View File

@@ -598,7 +598,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Cardigann
}
var resultParser = new HtmlParser();
var resultDocument = resultParser.ParseDocument(loginResult.Content);
using var resultDocument = resultParser.ParseDocument(loginResult.Content);
foreach (var error in errorBlocks)
{
var selection = resultDocument.QuerySelector(error.Selector);
@@ -984,7 +984,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Cardigann
var selectorText = ApplyGoTemplateText(selector.Selector, variables);
var parser = new HtmlParser();
var resultDocument = parser.ParseDocument(response.Content);
using var resultDocument = parser.ParseDocument(response.Content);
var element = resultDocument.QuerySelector(selectorText);
if (element == null)
@@ -1045,7 +1045,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Cardigann
if (_definition.Login.Test?.Selector != null && (response.Headers.ContentType?.Contains("text/html") ?? true))
{
var parser = new HtmlParser();
var document = parser.ParseDocument(response.Content);
using var document = parser.ParseDocument(response.Content);
var selection = document.QuerySelectorAll(_definition.Login.Test.Selector);
if (selection.Length == 0)

View File

@@ -9,7 +9,8 @@ namespace NzbDrone.Core.Indexers.Definitions;
public class DICMusic : GazelleBase<DICMusicSettings>
{
public override string Name => "DICMusic";
public override string[] IndexerUrls => new[] { "https://dicmusic.club/" };
public override string[] IndexerUrls => new[] { "https://dicmusic.com/" };
public override string[] LegacyUrls => new[] { "https://dicmusic.club/" };
public override string Description => "DICMusic is a CHINESE Private Torrent Tracker for MUSIC";
public override string Language => "zh-CN";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;

View File

@@ -8,12 +8,12 @@ namespace NzbDrone.Core.Indexers.Definitions.FileList;
public class FileList : TorrentIndexerBase<FileListSettings>
{
public override string Name => "FileList.io";
public override string[] IndexerUrls => new[]
public override string[] IndexerUrls => new[] { "https://filelist.io/" };
public override string[] LegacyUrls => new[]
{
"https://filelist.io/",
"https://filelist.io",
"https://flro.org/"
};
public override string[] LegacyUrls => new[] { "https://filelist.io" };
public override string Description => "FileList (FL) is a ROMANIAN Private Torrent Tracker for 0DAY / GENERAL";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override bool SupportsRss => true;

View File

@@ -1,28 +1,40 @@
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace NzbDrone.Core.Indexers.Definitions.FileList;
public class FileListTorrent
{
public string Id { get; set; }
public uint Id { get; set; }
public string Name { get; set; }
public long Size { get; set; }
public int Leechers { get; set; }
public int Seeders { get; set; }
[JsonProperty(PropertyName = "times_completed")]
[JsonPropertyName("times_completed")]
public uint TimesCompleted { get; set; }
public uint Comments { get; set; }
public uint Files { get; set; }
[JsonProperty(PropertyName = "imdb")]
[JsonPropertyName("imdb")]
public string ImdbId { get; set; }
public bool Internal { get; set; }
[JsonProperty(PropertyName = "freeleech")]
[JsonPropertyName("freeleech")]
public bool FreeLeech { get; set; }
[JsonProperty(PropertyName = "doubleup")]
[JsonPropertyName("doubleup")]
public bool DoubleUp { get; set; }
[JsonProperty(PropertyName = "upload_date")]
[JsonPropertyName("upload_date")]
public string UploadDate { get; set; }
public string Category { get; set; }
[JsonProperty(PropertyName = "small_description")]
[JsonPropertyName("small_description")]
public string SmallDescription { get; set; }
}

View File

@@ -3,8 +3,8 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using Newtonsoft.Json;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model;
@@ -35,7 +35,7 @@ public class FileListParser : IParseIndexerResponse
var releaseInfos = new List<ReleaseInfo>();
var results = JsonConvert.DeserializeObject<List<FileListTorrent>>(indexerResponse.Content);
var results = STJson.Deserialize<List<FileListTorrent>>(indexerResponse.Content);
foreach (var row in results)
{
@@ -54,7 +54,7 @@ public class FileListParser : IParseIndexerResponse
}
var imdbId = 0;
if (row.ImdbId != null && row.ImdbId.Length > 2)
if (row.ImdbId is { Length: > 2 })
{
imdbId = int.Parse(row.ImdbId.Substring(2));
}
@@ -64,7 +64,7 @@ public class FileListParser : IParseIndexerResponse
releaseInfos.Add(new TorrentInfo
{
Guid = string.Format("FileList-{0}", id),
Guid = $"FileList-{id}",
Title = row.Name,
Size = row.Size,
Categories = _categories.MapTrackerCatDescToNewznab(row.Category),
@@ -72,7 +72,7 @@ public class FileListParser : IParseIndexerResponse
InfoUrl = GetInfoUrl(id),
Seeders = row.Seeders,
Peers = row.Leechers + row.Seeders,
PublishDate = DateTime.Parse(row.UploadDate + " +0300", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal),
PublishDate = DateTime.Parse(row.UploadDate + " +0200", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal),
Description = row.SmallDescription,
Genres = row.SmallDescription.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList(),
ImdbId = imdbId,
@@ -91,21 +91,21 @@ public class FileListParser : IParseIndexerResponse
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
private string GetDownloadUrl(string torrentId)
private string GetDownloadUrl(uint torrentId)
{
var url = new HttpUri(_settings.BaseUrl)
.CombinePath("/download.php")
.AddQueryParam("id", torrentId)
.AddQueryParam("id", torrentId.ToString())
.AddQueryParam("passkey", _settings.Passkey);
return url.FullUri;
}
private string GetInfoUrl(string torrentId)
private string GetInfoUrl(uint torrentId)
{
var url = new HttpUri(_settings.BaseUrl)
.CombinePath("/details.php")
.AddQueryParam("id", torrentId);
.AddQueryParam("id", torrentId.ToString());
return url.FullUri;
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
@@ -25,6 +26,31 @@ public class FileListRequestGenerator : IIndexerRequestGenerator
{
parameters.Set("action", "search-torrents");
var searchQuery = searchCriteria.SanitizedSearchTerm.Trim();
if (DateTime.TryParseExact($"{searchCriteria.Season} {searchCriteria.Episode}", "yyyy MM/dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var showDate))
{
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
{
// Skip ID searches for daily episodes
return pageableRequests;
}
searchQuery = $"{searchQuery} {showDate:yyyy.MM.dd}".Trim();
}
else
{
if (searchCriteria.Season.HasValue)
{
parameters.Set("season", searchCriteria.Season.ToString());
}
if (searchCriteria.Episode.IsNotNullOrWhiteSpace())
{
parameters.Set("episode", searchCriteria.Episode);
}
}
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
{
parameters.Set("type", "imdb");
@@ -33,17 +59,7 @@ public class FileListRequestGenerator : IIndexerRequestGenerator
else if (searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
{
parameters.Set("type", "name");
parameters.Set("query", searchCriteria.SanitizedSearchTerm.Trim());
}
if (searchCriteria.Season.HasValue)
{
parameters.Set("season", searchCriteria.Season.ToString());
}
if (searchCriteria.Episode.IsNotNullOrWhiteSpace())
{
parameters.Set("episode", searchCriteria.Episode);
parameters.Set("query", searchQuery);
}
}

View File

@@ -70,7 +70,7 @@ public class FunFile : TorrentIndexerBase<UserPassTorrentBaseSettings>
if (CheckIfLoginNeeded(response))
{
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = await parser.ParseDocumentAsync(response.Content);
var errorMessage = dom.QuerySelector("td.mf_content")?.TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
@@ -276,7 +276,7 @@ public class FunFileParser : IParseIndexerResponse
var releaseInfos = new List<TorrentInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("table.mainframe table[cellpadding=\"2\"] > tbody > tr:has(td.row3)");
foreach (var row in rows)

View File

@@ -46,7 +46,7 @@ public class GazelleRequestGenerator : IIndexerRequestGenerator
{
var pageableRequests = new IndexerPageableRequestChain();
var parameters = GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
var parameters = GetBasicSearchParameters(searchCriteria, searchCriteria.SanitizedSearchTerm);
if (searchCriteria.ImdbId != null)
{
@@ -62,7 +62,7 @@ public class GazelleRequestGenerator : IIndexerRequestGenerator
{
var pageableRequests = new IndexerPageableRequestChain();
var parameters = GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
var parameters = GetBasicSearchParameters(searchCriteria, searchCriteria.SanitizedSearchTerm);
if (searchCriteria.Artist.IsNotNullOrWhiteSpace() && searchCriteria.Artist != "VA")
{
@@ -88,7 +88,7 @@ public class GazelleRequestGenerator : IIndexerRequestGenerator
{
var pageableRequests = new IndexerPageableRequestChain();
var parameters = GetBasicSearchParameters(searchCriteria.SanitizedTvSearchString, searchCriteria.Categories);
var parameters = GetBasicSearchParameters(searchCriteria, searchCriteria.SanitizedTvSearchString);
if (searchCriteria.ImdbId != null)
{
@@ -104,7 +104,7 @@ public class GazelleRequestGenerator : IIndexerRequestGenerator
{
var pageableRequests = new IndexerPageableRequestChain();
var parameters = GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
var parameters = GetBasicSearchParameters(searchCriteria, searchCriteria.SanitizedSearchTerm);
pageableRequests.Add(GetRequest(parameters));
return pageableRequests;
@@ -114,7 +114,7 @@ public class GazelleRequestGenerator : IIndexerRequestGenerator
{
var pageableRequests = new IndexerPageableRequestChain();
var parameters = GetBasicSearchParameters(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
var parameters = GetBasicSearchParameters(searchCriteria, searchCriteria.SanitizedSearchTerm);
pageableRequests.Add(GetRequest(parameters));
return pageableRequests;
@@ -123,7 +123,7 @@ public class GazelleRequestGenerator : IIndexerRequestGenerator
// hook to adjust the search term
protected virtual string GetSearchTerm(string term) => term;
protected virtual NameValueCollection GetBasicSearchParameters(string term, int[] categories)
protected virtual NameValueCollection GetBasicSearchParameters(SearchCriteriaBase searchCriteria, string term)
{
var parameters = new NameValueCollection
{
@@ -139,9 +139,10 @@ public class GazelleRequestGenerator : IIndexerRequestGenerator
parameters.Set("searchstr", searchTerm.Replace(".", " "));
}
if (categories != null)
if (searchCriteria.Categories != null && searchCriteria.Categories.Any())
{
var queryCats = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
var queryCats = Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
if (queryCats.Any())
{
queryCats.ForEach(cat => parameters.Set($"filter_cat[{cat}]", "1"));

View File

@@ -89,7 +89,7 @@ public class GreatPosterWallRequestGenerator : GazelleRequestGenerator
public override IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var parameters = GetBasicSearchParameters(searchCriteria.SearchTerm, searchCriteria.Categories);
var parameters = GetBasicSearchParameters(searchCriteria, searchCriteria.SearchTerm);
if (searchCriteria.ImdbId != null)
{
@@ -101,9 +101,9 @@ public class GreatPosterWallRequestGenerator : GazelleRequestGenerator
return pageableRequests;
}
protected override NameValueCollection GetBasicSearchParameters(string term, int[] categories)
protected override NameValueCollection GetBasicSearchParameters(SearchCriteriaBase searchCriteria, string term)
{
var parameters = base.GetBasicSearchParameters(term, categories);
var parameters = base.GetBasicSearchParameters(searchCriteria, term);
if (_settings.FreeleechOnly)
{

View File

@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Indexers.Definitions
if (CheckIfLoginNeeded(response))
{
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = parser.ParseDocument(response.Content);
var errorMessages = dom
.QuerySelectorAll("table.lista td.lista span[style*=\"#FF0000\"], table.lista td.header:contains(\"login attempts\")")
.Select(r => r.TextContent.Trim())

View File

@@ -74,7 +74,7 @@ namespace NzbDrone.Core.Indexers.Definitions
if (response.Content != null && !response.Content.ContainsIgnoreCase("If your browser doesn't have javascript enabled"))
{
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = await parser.ParseDocumentAsync(response.Content);
var errorMessage = dom.QuerySelector("div > font[color=\"#FF0000\"]")?.TextContent.Trim();
@@ -256,7 +256,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var userInfo = dom.QuerySelector("table.navus tr");
var userRank = userInfo?.Children[1].TextContent.Replace("Rank:", string.Empty).Trim();

View File

@@ -291,7 +291,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var torrentInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var doc = parser.ParseDocument(indexerResponse.Content);
using var doc = parser.ParseDocument(indexerResponse.Content);
var rows = doc.QuerySelectorAll("table[id=\"torrents\"] > tbody > tr");
foreach (var row in rows)

View File

@@ -266,7 +266,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("table#sortabletable > tbody > tr:has(a[href*=\"details.php?id=\"])");
foreach (var row in rows)

View File

@@ -77,7 +77,7 @@ public class Libble : TorrentIndexerBase<LibbleSettings>
if (CheckIfLoginNeeded(response))
{
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = parser.ParseDocument(response.Content);
var errorMessage = dom.QuerySelector("#loginform > .warning")?.TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
@@ -238,7 +238,7 @@ public class LibbleParser : IParseIndexerResponse
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var doc = parser.ParseDocument(indexerResponse.Content);
using var doc = parser.ParseDocument(indexerResponse.Content);
var groups = doc.QuerySelectorAll("table#torrent_table > tbody > tr.group:has(strong > a[href*=\"torrents.php?id=\"])");
foreach (var group in groups)

View File

@@ -177,7 +177,7 @@ public class MoreThanTVParser : IParseIndexerResponse
try
{
var parser = new HtmlParser();
var document = parser.ParseDocument(indexerResponse.Content);
using var document = parser.ParseDocument(indexerResponse.Content);
var torrents = document.QuerySelectorAll("#torrent_table > tbody > tr.torrent");
var movies = new[] { "movie" };
var tv = new[] { "season", "episode" };

View File

@@ -1,14 +1,17 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Newtonsoft.Json;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
@@ -65,6 +68,26 @@ namespace NzbDrone.Core.Indexers.Definitions
return Task.FromResult(request);
}
protected override IList<ReleaseInfo> CleanupReleases(IEnumerable<ReleaseInfo> releases, SearchCriteriaBase searchCriteria)
{
var cleanReleases = base.CleanupReleases(releases, searchCriteria);
return FilterReleasesByQuery(cleanReleases, searchCriteria).ToList();
}
protected override IEnumerable<ReleaseInfo> FilterReleasesByQuery(IEnumerable<ReleaseInfo> releases, SearchCriteriaBase searchCriteria)
{
if (!searchCriteria.IsRssSearch &&
searchCriteria.IsIdSearch &&
searchCriteria is TvSearchCriteria tvSearchCriteria &&
tvSearchCriteria.EpisodeSearchString.IsNotNullOrWhiteSpace())
{
releases = releases.Where(r => r.Title.IsNotNullOrWhiteSpace() && r.Title.ContainsIgnoreCase(tvSearchCriteria.EpisodeSearchString)).ToList();
}
return releases;
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
@@ -208,7 +231,7 @@ namespace NzbDrone.Core.Indexers.Definitions
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request");
}
var jsonResponse = new HttpResponse<JsonRpcResponse<NebulanceTorrents>>(indexerResponse.HttpResponse).Resource;
var jsonResponse = STJson.Deserialize<JsonRpcResponse<NebulanceTorrents>>(indexerResponse.HttpResponse.Content);
if (jsonResponse.Error != null || jsonResponse.Result == null)
{
@@ -226,9 +249,11 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var details = _settings.BaseUrl + "torrents.php?id=" + row.TorrentId;
var title = row.ReleaseTitle.IsNotNullOrWhiteSpace() ? row.ReleaseTitle : row.GroupName;
var release = new TorrentInfo
{
Title = row.ReleaseTitle,
Title = title,
Guid = details,
InfoUrl = details,
PosterUrl = row.Banner,
@@ -240,12 +265,18 @@ namespace NzbDrone.Core.Indexers.Definitions
Grabs = ParseUtil.CoerceInt(row.Snatch),
Seeders = ParseUtil.CoerceInt(row.Seed),
Peers = ParseUtil.CoerceInt(row.Seed) + ParseUtil.CoerceInt(row.Leech),
Scene = row.Tags?.ContainsIgnoreCase("scene"),
MinimumRatio = 0, // ratioless
MinimumSeedTime = row.Category.ToLower() == "season" ? 432000 : 86400, // 120 hours for seasons and 24 hours for episodes
DownloadVolumeFactor = 0, // ratioless tracker
UploadVolumeFactor = 1
};
if (row.TvMazeId.IsNotNullOrWhiteSpace())
{
release.TvMazeId = ParseUtil.CoerceInt(row.TvMazeId);
}
torrentInfos.Add(release);
}
@@ -297,24 +328,28 @@ namespace NzbDrone.Core.Indexers.Definitions
public class NebulanceTorrent
{
[JsonProperty(PropertyName = "rls_name")]
[JsonPropertyName("rls_name")]
public string ReleaseTitle { get; set; }
public string Title { get; set; }
[JsonProperty(PropertyName = "cat")]
[JsonPropertyName("cat")]
public string Category { get; set; }
public string Size { get; set; }
public string Seed { get; set; }
public string Leech { get; set; }
public string Snatch { get; set; }
public string Download { get; set; }
[JsonProperty(PropertyName = "file_list")]
[JsonPropertyName("file_list")]
public string[] FileList { get; set; }
[JsonProperty(PropertyName = "series_banner")]
[JsonPropertyName("group_name")]
public string GroupName { get; set; }
[JsonPropertyName("series_banner")]
public string Banner { get; set; }
[JsonProperty(PropertyName = "group_id")]
[JsonPropertyName("group_id")]
public string TorrentId { get; set; }
[JsonProperty(PropertyName = "rls_utc")]
[JsonPropertyName("series_id")]
public string TvMazeId { get; set; }
[JsonPropertyName("rls_utc")]
public string PublishDateUtc { get; set; }
public IEnumerable<string> Tags { get; set; }
}
public class NebulanceTorrents

View File

@@ -31,6 +31,7 @@ namespace NzbDrone.Core.Indexers.Newznab
{
return new NewznabRequestGenerator(_capabilitiesProvider)
{
Definition = Definition,
PageSize = PageSize,
Settings = Settings
};
@@ -102,10 +103,7 @@ namespace NzbDrone.Core.Indexers.Newznab
yield return GetDefinition("NzbPlanet", GetSettings("https://api.nzbplanet.net"));
yield return GetDefinition("NZBStars", GetSettings("https://nzbstars.com"));
yield return GetDefinition("OZnzb", GetSettings("https://api.oznzb.com"));
yield return GetDefinition("SimplyNZBs", GetSettings("https://simplynzbs.com"));
yield return GetDefinition("SpotNZB", GetSettings("https://spotnzb.xyz"));
yield return GetDefinition("Tabula Rasa", GetSettings("https://www.tabula-rasa.pw", apiPath: @"/api/v1/api"));
yield return GetDefinition("VeryCouch LazyMuch", GetSettings("https://api.verycouch.com"));
yield return GetDefinition("Generic Newznab", GetSettings(""));
}
}

View File

@@ -129,6 +129,8 @@ namespace NzbDrone.Core.Indexers.Newznab
capabilities.SearchParams.AddIfNotNull(searchParam);
}
}
capabilities.SupportsRawSearch = xmlBasicSearch.Attribute("searchEngine")?.Value == "raw";
}
else
{

View File

@@ -13,10 +13,11 @@ namespace NzbDrone.Core.Indexers.Newznab
public class NewznabRequestGenerator : IIndexerRequestGenerator
{
private readonly INewznabCapabilitiesProvider _capabilitiesProvider;
public ProviderDefinition Definition { get; set; }
public int MaxPages { get; set; }
public int PageSize { get; set; }
public NewznabSettings Settings { get; set; }
public ProviderDefinition Definition { get; set; }
public NewznabRequestGenerator(INewznabCapabilitiesProvider capabilitiesProvider)
{

View File

@@ -263,7 +263,7 @@ public class NorBitsParser : IParseIndexerResponse
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("#torrentTable > tbody > tr").Skip(1).ToCollection();

View File

@@ -29,6 +29,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
public override bool SupportsRedirect => true;
public override TimeSpan RateLimit => TimeSpan.FromSeconds(3);
public Orpheus(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
@@ -49,10 +50,24 @@ namespace NzbDrone.Core.Indexers.Definitions
return new OrpheusParser(Settings, Capabilities.Categories);
}
protected override IList<ReleaseInfo> CleanupReleases(IEnumerable<ReleaseInfo> releases, SearchCriteriaBase searchCriteria)
{
var cleanReleases = base.CleanupReleases(releases, searchCriteria);
if (searchCriteria.IsRssSearch)
{
cleanReleases = cleanReleases.Take(50).ToList();
}
return cleanReleases;
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
LimitsDefault = 50,
LimitsMax = 50,
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q, MusicSearchParam.Artist, MusicSearchParam.Album, MusicSearchParam.Year
@@ -200,6 +215,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
if (queryCats.Any())
{
queryCats.ForEach(cat => parameters.Set($"filter_cat[{cat}]", "1"));

View File

@@ -3,7 +3,7 @@ using NLog;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Indexers.PassThePopcorn
namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
{
public class PassThePopcorn : TorrentIndexerBase<PassThePopcornSettings>
{
@@ -29,21 +29,23 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new PassThePopcornRequestGenerator
{
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger
};
return new PassThePopcornRequestGenerator(Settings, Capabilities);
}
public override IParseIndexerResponse GetParser()
{
return new PassThePopcornParser(Settings);
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
SearchParams = new List<SearchParam>
LimitsDefault = PageSize,
LimitsMax = PageSize,
TvSearchParams = new List<TvSearchParam>
{
SearchParam.Q
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
@@ -58,31 +60,19 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies, "Feature Film");
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesForeign);
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesOther);
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD);
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesHD);
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies3D);
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesBluRay);
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesDVD);
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesWEBDL);
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.Movies, "Short Film");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TV, "Miniseries");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TV, "Stand-up Comedy");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TV, "Live Performance");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.Movies, "Stand-up Comedy");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Movies, "Live Performance");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.Movies, "Movie Collection");
return caps;
}
public override IParseIndexerResponse GetParser()
{
return new PassThePopcornParser(Settings, Capabilities, _logger);
}
}
public class PassThePopcornFlag : IndexerFlag
{
public static IndexerFlag Golden => new IndexerFlag("golden", "Release follows Golden Popcorn quality rules");
public static IndexerFlag Approved => new IndexerFlag("approved", "Release approved by PTP");
public static IndexerFlag Golden => new ("golden", "Release follows Golden Popcorn quality rules");
public static IndexerFlag Approved => new ("approved", "Release approved by PTP");
}
}

View File

@@ -1,15 +1,28 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
namespace NzbDrone.Core.Indexers.PassThePopcorn
namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
{
public class Director
public class PassThePopcornResponse
{
public string Name { get; set; }
public string Id { get; set; }
public string TotalResults { get; set; }
public List<PassThePopcornMovie> Movies { get; set; }
public string Page { get; set; }
public string AuthKey { get; set; }
public string PassKey { get; set; }
}
public class Torrent
public class PassThePopcornMovie
{
public string GroupId { get; set; }
public string Title { get; set; }
public string Year { get; set; }
public string Cover { get; set; }
public List<string> Tags { get; set; }
public string ImdbId { get; set; }
public List<PassThePopcornTorrent> Torrents { get; set; }
}
public class PassThePopcornTorrent
{
public int Id { get; set; }
public string Quality { get; set; }
@@ -19,7 +32,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public string Resolution { get; set; }
public bool Scene { get; set; }
public string Size { get; set; }
public DateTime UploadTime { get; set; }
public string UploadTime { get; set; }
public string RemasterTitle { get; set; }
public string Snatched { get; set; }
public string Seeders { get; set; }
@@ -29,32 +42,4 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public bool GoldenPopcorn { get; set; }
public string FreeleechType { get; set; }
}
public class Movie
{
public string GroupId { get; set; }
public string Title { get; set; }
public string Year { get; set; }
public string Cover { get; set; }
public List<string> Tags { get; set; }
public List<Director> Directors { get; set; }
public string ImdbId { get; set; }
public List<Torrent> Torrents { get; set; }
}
public class PassThePopcornResponse
{
public string TotalResults { get; set; }
public List<Movie> Movies { get; set; }
public string Page { get; set; }
public string AuthKey { get; set; }
public string PassKey { get; set; }
}
public class PassThePopcornAuthResponse
{
public string Result { get; set; }
public string Popcron { get; set; }
public string AntiCsrfToken { get; set; }
}
}

View File

@@ -1,43 +1,36 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using NLog;
using System.Text.RegularExpressions;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.PassThePopcorn
namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
{
public class PassThePopcornParser : IParseIndexerResponse
{
private readonly IndexerCapabilities _capabilities;
private readonly PassThePopcornSettings _settings;
private readonly Logger _logger;
public PassThePopcornParser(PassThePopcornSettings settings, IndexerCapabilities capabilities, Logger logger)
private static Regex SeasonRegex => new (@"\bS\d{2,3}(E\d{2,3})?\b", RegexOptions.Compiled);
public PassThePopcornParser(PassThePopcornSettings settings)
{
_settings = settings;
_capabilities = capabilities;
_logger = logger;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
var indexerHttpResponse = indexerResponse.HttpResponse;
if (indexerHttpResponse.StatusCode != HttpStatusCode.OK)
var httpResponse = indexerResponse.HttpResponse;
if (httpResponse.StatusCode != HttpStatusCode.OK)
{
// Remove cookie cache
if (indexerHttpResponse.HasHttpRedirect && indexerHttpResponse.RedirectUrl
.ContainsIgnoreCase("login.php"))
{
CookiesUpdater(null, null);
throw new IndexerAuthException("We are being redirected to the PTP login page. Most likely your session expired or was killed. Recheck your cookie or credentials and try testing the indexer.");
}
if (indexerHttpResponse.StatusCode == HttpStatusCode.Forbidden)
if (httpResponse.StatusCode == HttpStatusCode.Forbidden)
{
throw new RequestLimitReachedException(indexerResponse, "PTP Query Limit Reached. Please try again later.");
}
@@ -45,19 +38,13 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request");
}
if (indexerHttpResponse.Headers.ContentType != HttpAccept.Json.Value)
if (httpResponse.Headers.ContentType != HttpAccept.Json.Value)
{
if (indexerHttpResponse.Request.Url.Path.ContainsIgnoreCase("login.php"))
{
CookiesUpdater(null, null);
throw new IndexerAuthException("We are currently on the login page. Most likely your session expired or was killed. Try testing the indexer in the settings.");
}
// Remove cookie cache
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from indexer request, expected {HttpAccept.Json.Value}");
}
var jsonResponse = STJson.Deserialize<PassThePopcornResponse>(indexerResponse.Content);
if (jsonResponse.TotalResults == "0" ||
jsonResponse.TotalResults.IsNullOrWhiteSpace() ||
jsonResponse.Movies == null)
@@ -81,51 +68,39 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
if (torrent.Checked)
{
flags.Add(PassThePopcornFlag.Approved); //title = $"{title} ✔";
flags.Add(PassThePopcornFlag.Approved);
}
if (torrent.Scene)
var categories = new List<IndexerCategory> { NewznabStandardCategory.Movies };
if (title != null && SeasonRegex.Match(title).Success)
{
flags.Add(IndexerFlag.Scene);
categories.Add(NewznabStandardCategory.TV);
}
var free = !(torrent.FreeleechType is null);
// 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(),
ImdbId = result.ImdbId.IsNotNullOrWhiteSpace() ? int.Parse(result.ImdbId) : 0,
IndexerFlags = flags,
MinimumRatio = 1,
MinimumSeedTime = 345600,
DownloadVolumeFactor = free ? 0 : 1,
UploadVolumeFactor = 1,
Categories = new List<IndexerCategory> { NewznabStandardCategory.Movies }
});
}
catch (Exception e)
{
_logger.Error(e, "Encountered exception parsing PTP torrent: {" +
$"Size: {torrent.Size}" +
$"UploadTime: {torrent.UploadTime}" +
$"Seeders: {torrent.Seeders}" +
$"Leechers: {torrent.Leechers}" +
$"ReleaseName: {torrent.ReleaseName}" +
$"ID: {torrent.Id}" +
"}. Please immediately report this info on https://github.com/Prowlarr/Prowlarr/issues/1584.");
throw;
}
Guid = $"PassThePopcorn-{id}",
Title = title,
Year = int.Parse(result.Year),
InfoUrl = GetInfoUrl(result.GroupId, id),
DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey),
Categories = categories,
Size = long.Parse(torrent.Size),
Grabs = int.Parse(torrent.Snatched),
Seeders = int.Parse(torrent.Seeders),
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
PublishDate = DateTime.Parse(torrent.UploadTime + " +0000", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal),
ImdbId = result.ImdbId.IsNotNullOrWhiteSpace() ? int.Parse(result.ImdbId) : 0,
Scene = torrent.Scene,
IndexerFlags = flags,
DownloadVolumeFactor = torrent.FreeleechType is "Freeleech" ? 0 : 1,
UploadVolumeFactor = 1,
MinimumRatio = 1,
MinimumSeedTime = 345600,
Genres = result.Tags ?? new List<string>(),
PosterUrl = GetPosterUrl(result.Cover)
});
}
}
@@ -155,5 +130,17 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
return url.FullUri;
}
private static string GetPosterUrl(string cover)
{
if (cover.IsNotNullOrWhiteSpace() &&
Uri.TryCreate(cover, UriKind.Absolute, out var posterUri) &&
(posterUri.Scheme == Uri.UriSchemeHttp || posterUri.Scheme == Uri.UriSchemeHttps))
{
return posterUri.AbsoluteUri;
}
return null;
}
}
}

View File

@@ -1,22 +1,24 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using NLog;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.Indexers.PassThePopcorn
namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
{
public class PassThePopcornRequestGenerator : IIndexerRequestGenerator
{
public PassThePopcornSettings Settings { get; set; }
private readonly PassThePopcornSettings _settings;
private readonly IndexerCapabilities _capabilities;
public IDictionary<string, string> Cookies { get; set; }
public IIndexerHttpClient HttpClient { get; set; }
public Logger Logger { get; set; }
public PassThePopcornRequestGenerator(PassThePopcornSettings settings, IndexerCapabilities capabilities)
{
_settings = settings;
_capabilities = capabilities;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
@@ -28,57 +30,12 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
}
else
{
pageableRequests.Add(GetRequest($"{searchCriteria.SearchTerm}", searchCriteria));
pageableRequests.Add(GetRequest($"{searchCriteria.SanitizedSearchTerm}", searchCriteria));
}
return pageableRequests;
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
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.Set("freetorrent", "1");
}
var request =
new IndexerRequest(
$"{Settings.BaseUrl.Trim().TrimEnd('/')}/torrents.php?{queryParams.GetQueryString()}",
HttpAccept.Json);
request.HttpRequest.Headers["ApiUser"] = Settings.APIUser;
request.HttpRequest.Headers["ApiKey"] = Settings.APIKey;
if (Settings.APIKey.IsNullOrWhiteSpace())
{
foreach (var cookie in Cookies)
{
request.HttpRequest.Cookies[cookie.Key] = cookie.Value;
}
CookiesUpdater(Cookies, DateTime.Now.AddDays(30));
}
yield return request;
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
@@ -86,7 +43,11 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest($"{searchCriteria.SanitizedTvSearchString}", searchCriteria));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
@@ -98,9 +59,53 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest($"{searchCriteria.SearchTerm}", searchCriteria));
pageableRequests.Add(GetRequest($"{searchCriteria.SanitizedSearchTerm}", searchCriteria));
return pageableRequests;
}
private IEnumerable<IndexerRequest> GetRequest(string searchTerm, SearchCriteriaBase searchCriteria)
{
var parameters = new NameValueCollection
{
{ "action", "advanced" },
{ "json", "noredirect" },
{ "grouping", "0" },
{ "searchstr", searchTerm }
};
if (_settings.FreeleechOnly)
{
parameters.Set("freetorrent", "1");
}
var queryCats = _capabilities.Categories
.MapTorznabCapsToTrackers(searchCriteria.Categories)
.Select(int.Parse)
.Distinct()
.ToList();
if (searchCriteria.IsRssSearch && queryCats.Any())
{
queryCats.ForEach(cat => parameters.Set($"filter_cat[{cat}]", "1"));
}
if (searchCriteria.Limit is > 0 && searchCriteria.Offset is > 0)
{
var page = (int)(searchCriteria.Offset / searchCriteria.Limit) + 1;
parameters.Set("page", page.ToString());
}
var searchUrl = $"{_settings.BaseUrl.Trim().TrimEnd('/')}/torrents.php?{parameters.GetQueryString()}";
var request = new IndexerRequest(searchUrl, HttpAccept.Json);
request.HttpRequest.Headers.Add("ApiUser", _settings.APIUser);
request.HttpRequest.Headers.Add("ApiKey", _settings.APIKey);
yield return request;
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
}

View File

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

View File

@@ -19,7 +19,7 @@ using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions;
[Obsolete("PirateTheNet has shutdown 2023-10-14")]
public class PirateTheNet : TorrentIndexerBase<UserPassTorrentBaseSettings>
{
public override string Name => "PirateTheNet";
@@ -234,7 +234,7 @@ public class PirateTheNetParser : IParseIndexerResponse
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("table.main > tbody > tr");
foreach (var row in rows.Skip(1))

View File

@@ -171,7 +171,7 @@ public class PixelHDParser : IParseIndexerResponse
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var groups = dom.QuerySelectorAll("div.browsePoster");
foreach (var group in groups)

View File

@@ -85,7 +85,7 @@ public class PreToMe : TorrentIndexerBase<PreToMeSettings>
if (CheckIfLoginNeeded(response))
{
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = parser.ParseDocument(response.Content);
var errorMessage = dom.QuerySelector("table.body_table font[color~=\"red\"]")?.TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
@@ -340,7 +340,7 @@ public class PreToMeParser : IParseIndexerResponse
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("table > tbody > tr.browse");
foreach (var row in rows)

View File

@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
public override string Name => "PrivateHD";
public override string[] IndexerUrls => new[] { "https://privatehd.to/" };
public override string Description => "PrivateHD is a Private Torrent Tracker for HD MOVIES / TV and the sister-site of AvistaZ, CinemaZ, ExoticaZ, and AnimeTorrents";
public override string Description => "PrivateHD (PHD) is a Private Torrent Tracker for HD MOVIES / TV and the sister-site of AvistaZ, CinemaZ, ExoticaZ, and AnimeTorrents";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public PrivateHD(IIndexerRepository indexerRepository,

View File

@@ -29,6 +29,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
public override bool SupportsRedirect => true;
public override TimeSpan RateLimit => TimeSpan.FromSeconds(3);
public Redacted(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
@@ -63,10 +64,24 @@ namespace NzbDrone.Core.Indexers.Definitions
return Task.FromResult(request);
}
protected override IList<ReleaseInfo> CleanupReleases(IEnumerable<ReleaseInfo> releases, SearchCriteriaBase searchCriteria)
{
var cleanReleases = base.CleanupReleases(releases, searchCriteria);
if (searchCriteria.IsRssSearch)
{
cleanReleases = cleanReleases.Take(50).ToList();
}
return cleanReleases;
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
LimitsDefault = 50,
LimitsMax = 50,
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q, MusicSearchParam.Artist, MusicSearchParam.Album, MusicSearchParam.Year
@@ -172,6 +187,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
if (queryCats.Any())
{
queryCats.ForEach(cat => parameters.Set($"filter_cat[{cat}]", "1"));

View File

@@ -249,7 +249,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var torrentInfos = new List<TorrentInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("#torrents-table > tbody > tr");
foreach (var row in rows.Skip(1))

View File

@@ -63,7 +63,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = parser.ParseDocument(response.Content);
var magnetLink = dom.QuerySelector("table.attach a.magnet-link[href^=\"magnet:?\"]")?.GetAttribute("href");
if (magnetLink == null)
@@ -104,7 +104,7 @@ namespace NzbDrone.Core.Indexers.Definitions
if (!response.Content.Contains("id=\"logged-in-username\""))
{
var parser = new HtmlParser();
var document = await parser.ParseDocumentAsync(response.Content);
using var document = await parser.ParseDocumentAsync(response.Content);
var errorMessage = document.QuerySelector("h4.warnColor1.tCenter.mrg_16, div.msg-main")?.TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "RuTracker Auth Failed");
@@ -1580,7 +1580,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var doc = parser.ParseDocument(indexerResponse.Content);
using var doc = parser.ParseDocument(indexerResponse.Content);
var rows = doc.QuerySelectorAll("table#tor-tbl > tbody > tr");
foreach (var row in rows)

View File

@@ -225,7 +225,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var table = dom.QuerySelector("table.movehere");
if (table == null)

View File

@@ -78,7 +78,7 @@ public class Shazbat : TorrentIndexerBase<ShazbatSettings>
if (CheckIfLoginNeeded(response))
{
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = await parser.ParseDocumentAsync(response.Content);
var errorMessage = dom.QuerySelector("div#fail .modal-body")?.TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
@@ -223,7 +223,7 @@ public class ShazbatParser : IParseIndexerResponse
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var hasGlobalFreeleech = dom.QuerySelector("span:contains(\"Freeleech until:\"):has(span.datetime)") != null;
@@ -303,7 +303,7 @@ public class ShazbatParser : IParseIndexerResponse
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
if (!hasGlobalFreeleech)
{

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
@@ -16,12 +17,11 @@ using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete("Site unavailable")]
public class Shizaproject : TorrentIndexerBase<NoAuthTorrentBaseSettings>
{
public override string Name => "ShizaProject";
public override string[] IndexerUrls => new string[] { "https://shiza-project.com/" };
public override string Description => "Shizaproject is russian anime voiceover group and eponymous anime tracker.";
public override string[] IndexerUrls => new[] { "https://shiza-project.com/" };
public override string Description => "ShizaProject Tracker is a Public RUSSIAN tracker and release group for ANIME";
public override string Language => "ru-RU";
public override Encoding Encoding => Encoding.UTF8;
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new ShizaprojectRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
return new ShizaprojectRequestGenerator { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
@@ -47,20 +47,22 @@ 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
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "TV");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "TV_SPECIAL");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVAnime, "ONA");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVAnime, "OVA");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Movies, "MOVIE");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.Movies, "SHORT_MOVIE");
return caps;
}
}
@@ -93,6 +95,7 @@ namespace NzbDrone.Core.Indexers.Definitions
publishedAt
slug
torrents {
synopsis
downloaded
seeders
leechers
@@ -112,7 +115,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var queryCollection = new NameValueCollection
{
{ "query", query.Replace('\n', ' ').Trim() },
{ "variables", Newtonsoft.Json.JsonConvert.SerializeObject(variables) }
{ "variables", JsonConvert.SerializeObject(variables) }
};
var requestUrl = string.Format("{0}/graphql?", Settings.BaseUrl.TrimEnd('/')) + queryCollection.GetQueryString();
@@ -175,25 +178,26 @@ namespace NzbDrone.Core.Indexers.Definitions
_categories = categories;
}
private string composeTitle(ShizaprojectNode n, ShizaprojectTorrent tr)
private string ComposeTitle(ShizaprojectNode n, ShizaprojectTorrent tr)
{
var title = string.Format("{0} / {1}", n.Name, n.OriginalName);
foreach (var tl in n.AlternativeNames)
var allNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
title += " / " + tl;
n.Name,
n.OriginalName
};
allNames.UnionWith(n.AlternativeNames.ToHashSet());
var title = $"{string.Join(" / ", allNames)} {tr.Synopsis}";
if (tr.VideoQualities.Length > 0)
{
title += $" [{string.Join(" ", tr.VideoQualities)}]";
}
title += " [";
foreach (var q in tr.VideoQualities)
{
title += " " + q;
}
title += " ]";
return title;
}
private DateTime getActualPublishDate(ShizaprojectNode n, ShizaprojectTorrent t)
private DateTime GetActualPublishDate(ShizaprojectNode n, ShizaprojectTorrent t)
{
if (n.PublishedAt == null)
{
@@ -205,7 +209,7 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
private string getResolution(string[] qualities)
private string GetResolution(string[] qualities)
{
var resPrefix = "RESOLUTION_";
var res = Array.Find(qualities, s => s.StartsWith(resPrefix));
@@ -234,7 +238,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var torrentInfo = new TorrentInfo
{
Title = composeTitle(e.Node, tr),
Title = ComposeTitle(e.Node, tr),
InfoUrl = string.Format("{0}/releases/{1}/", _settings.BaseUrl.TrimEnd('/'), e.Node.Slug),
DownloadVolumeFactor = 0,
UploadVolumeFactor = 1,
@@ -242,12 +246,12 @@ namespace NzbDrone.Core.Indexers.Definitions
Peers = tr.Leechers + tr.Seeders,
Grabs = tr.Downloaded,
Categories = _categories.MapTrackerCatDescToNewznab(e.Node.Type),
PublishDate = getActualPublishDate(e.Node, tr),
PublishDate = GetActualPublishDate(e.Node, tr),
Guid = tr.File.Url,
DownloadUrl = tr.File.Url,
MagnetUrl = tr.MagnetUri,
Size = tr.Size,
Resolution = getResolution(tr.VideoQualities)
Resolution = GetResolution(tr.VideoQualities)
};
torrentInfos.Add(torrentInfo);
@@ -310,6 +314,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class ShizaprojectTorrent
{
public string Synopsis { get; set; }
public int Downloaded { get; set; }
public int Seeders { get; set; }
public int Leechers { get; set; }

View File

@@ -96,7 +96,7 @@ public class SpeedCD : TorrentIndexerBase<SpeedCDSettings>
if (CheckIfLoginNeeded(response))
{
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = parser.ParseDocument(response.Content);
var errorMessage = dom.QuerySelector("h5")?.TextContent.Trim();
if (response.Content.Contains("Wrong Captcha!"))
@@ -323,7 +323,7 @@ public class SpeedCDParser : IParseIndexerResponse
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("div.boxContent > table > tbody > tr");
foreach (var row in rows)

View File

@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Indexers.Definitions
if (CheckIfLoginNeeded(response))
{
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = parser.ParseDocument(response.Content);
var errorMessage = dom.QuerySelector("form#loginform")?.TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
@@ -224,7 +224,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var torrentInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var doc = parser.ParseDocument(indexerResponse.Content);
using var doc = parser.ParseDocument(indexerResponse.Content);
// get params to build download link (user could be banned without those params)
var rssFeedUri = new Uri(_settings.BaseUrl + doc.QuerySelector("link[href^=\"/feeds.php?feed=\"]")

View File

@@ -74,7 +74,7 @@ namespace NzbDrone.Core.Indexers.Definitions
if (CheckIfLoginNeeded(response))
{
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = await parser.ParseDocumentAsync(response.Content);
var errorMessage = dom.QuerySelector("table.forumline table span.gen")?.FirstChild?.TextContent;
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
@@ -367,7 +367,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var dom = parser.ParseDocument(indexerResponse.Content);
using var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("table.forumline > tbody > tr[class*=prow]");
foreach (var row in rows)

View File

@@ -71,7 +71,7 @@ namespace NzbDrone.Core.Indexers.Definitions
if (CheckIfLoginNeeded(response))
{
var parser = new HtmlParser();
var dom = parser.ParseDocument(response.Content);
using var dom = parser.ParseDocument(response.Content);
var errorMessage = dom.QuerySelector("td.embedded")?.TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
@@ -245,7 +245,7 @@ namespace NzbDrone.Core.Indexers.Definitions
var torrentInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
var doc = parser.ParseDocument(indexerResponse.Content);
using var doc = parser.ParseDocument(indexerResponse.Content);
var rows = doc.QuerySelectorAll("table > tbody:has(tr > td.colhead) > tr:not(:has(td.colhead))");
foreach (var row in rows)
{

View File

@@ -77,14 +77,6 @@ namespace NzbDrone.Core.Indexers.Definitions
}
};
caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.TVAnime, "Anime");
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.PC, "Appz/Packs");
caps.Categories.AddCategoryMapping(42, NewznabStandardCategory.AudioAudiobook, "Audio Books");
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.Books, "Books");
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.TVDocumentary, "Documentary");
caps.Categories.AddCategoryMapping(47, NewznabStandardCategory.Other, "Fonts");
caps.Categories.AddCategoryMapping(43, NewznabStandardCategory.PCMac, "Mac");
caps.Categories.AddCategoryMapping(96, NewznabStandardCategory.MoviesUHD, "Movie/4K");
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.MoviesSD, "Movies/480p");
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.MoviesBluRay, "Movies/Bluray");
@@ -97,31 +89,43 @@ namespace NzbDrone.Core.Indexers.Definitions
caps.Categories.AddCategoryMapping(48, NewznabStandardCategory.Movies, "Movies/x265");
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD, "Movies/XviD");
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.AudioMP3, "Music/Audio");
caps.Categories.AddCategoryMapping(23, NewznabStandardCategory.AudioForeign, "Music/Non-English");
caps.Categories.AddCategoryMapping(41, NewznabStandardCategory.Audio, "Music/Packs");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.AudioVideo, "Music/Video");
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.Audio, "Music/Flac");
caps.Categories.AddCategoryMapping(45, NewznabStandardCategory.AudioOther, "Podcast");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.PCGames, "PC/Games");
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.ConsolePS3, "PS3");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.ConsolePSP, "PSP");
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.ConsoleWii, "Wii");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.ConsoleXBox360, "Xbox-360");
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.TVSD, "TV/480p");
caps.Categories.AddCategoryMapping(32, NewznabStandardCategory.TVHD, "TV/Bluray");
caps.Categories.AddCategoryMapping(31, NewznabStandardCategory.TVSD, "TV/DVD-R");
caps.Categories.AddCategoryMapping(33, NewznabStandardCategory.TVSD, "TV/DVD-Rip");
caps.Categories.AddCategoryMapping(46, NewznabStandardCategory.TVSD, "TV/Mobile");
caps.Categories.AddCategoryMapping(82, NewznabStandardCategory.TVForeign, "TV/Non-English");
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.TV, "TV/Packs");
caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.TVSD, "TV/SD/x264");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TVHD, "TV/x264");
caps.Categories.AddCategoryMapping(34, NewznabStandardCategory.TVUHD, "TV/x265");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVSD, "TV/XviD");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.PCGames, "PC/Games");
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.ConsolePS3, "PS");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.ConsolePSP, "PSP");
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.ConsoleNDS, "Nintendo");
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.ConsoleXBox, "Xbox");
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.AudioMP3, "Music/Audio");
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.Audio, "Music/Flac");
caps.Categories.AddCategoryMapping(23, NewznabStandardCategory.AudioForeign, "Music/Non-English");
caps.Categories.AddCategoryMapping(41, NewznabStandardCategory.Audio, "Music/Packs");
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.AudioVideo, "Music/Video");
caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.TVAnime, "Anime");
caps.Categories.AddCategoryMapping(42, NewznabStandardCategory.AudioAudiobook, "Audio Books");
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.Books, "Books");
caps.Categories.AddCategoryMapping(102, NewznabStandardCategory.BooksForeign, "Books/Non-English");
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.TVDocumentary, "Documentary");
caps.Categories.AddCategoryMapping(95, NewznabStandardCategory.TVDocumentary, "Educational");
caps.Categories.AddCategoryMapping(47, NewznabStandardCategory.Other, "Fonts");
caps.Categories.AddCategoryMapping(43, NewznabStandardCategory.PCMac, "Mac");
caps.Categories.AddCategoryMapping(45, NewznabStandardCategory.AudioOther, "Podcast");
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.PC, "Softwa/Packs");
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.PC, "Software");
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.XXX, "XXX/0Day");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.XXX, "XXX/Movies");
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.XXXPack, "XXX/Packs");

View File

@@ -40,7 +40,7 @@ namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
var torrentInfo = new TorrentInfo
{
Guid = GetGuid(torrent),
Title = torrent.release_name,
Title = WebUtility.HtmlDecode(torrent.release_name),
Categories = new List<IndexerCategory> { NewznabStandardCategory.Movies },
Size = torrent.size * 1000 * 1000,
DownloadUrl = torrent.download_url,

View File

@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(BuildRequest(searchCriteria.SearchTerm, searchCriteria.ImdbId));
pageableRequests.Add(BuildRequest(searchCriteria.SearchTerm, searchCriteria.FullImdbId));
return pageableRequests;
}

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
}
}
public class TorrentPotatoSettings : IIndexerSettings
public class TorrentPotatoSettings : ITorrentIndexerSettings
{
private static readonly TorrentPotatoSettingsValidator Validator = new ();

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