Compare commits

...

95 Commits

Author SHA1 Message Date
Bogdan
343d7088c9 Fixed: Don't die on info indexer when the definition is missing 2023-11-19 01:36:50 +02:00
Bogdan
709dfe453b Fix AB tests 2023-11-18 06:08:12 +02:00
Bogdan
3130fac106 New: (AnimeBytes) Filter old releases on RSS 2023-11-18 05:07:10 +02:00
Weblate
28004dfae1 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
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: Jordy <prive@jordyhoebergen.nl>
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: bai0012 <baicongrui@gmail.com>
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/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
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/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-11-18 03:13:20 +02:00
Bogdan
9b34c89bc8 Fixed: Enforce validation warnings when testing providers
(cherry picked from commit c3b4126d0c4f449a41e2cf7ea438b20e25370995)
2023-11-17 05:26:04 +02:00
Mark McDowall
28e90acd0d Rename 'ReturnUrl' to 'returnUrl' for forms auth redirection
(cherry picked from commit 812712e2843a738054c065a6d5c1b7c81c5f8e7b)
2023-11-17 05:25:41 +02:00
Erik P
9d11d7e17f Fixed: (PTP) Add IMDb ID to TV Search capabilities (#1920)
Co-authored-by: Erik Persson <erik@erikpersson.me>
2023-11-16 19:33:53 +02:00
Bogdan
2cbdb5bcba New: (HDBits) Add Use Filenames option 2023-11-16 18:50:24 +02:00
Bogdan
118bfb8c28 Fixed: (AvistaZ) Increase rate limit to 5 seconds 2023-11-16 18:50:20 +02:00
Bogdan
942477ecf9 Fixed: (HDBits) Add labels for codecs and mediums 2023-11-14 17:21:07 +02:00
Bogdan
4b4589ed27 Improvements to download factor and show Freeleech only for HDB 2023-11-13 16:23:10 +02:00
Bogdan
bd0609639e New: (HDBits) Add pagination support 2023-11-13 02:54:28 +02:00
Bogdan
ccdad3a44c Bump version to 1.10.4 2023-11-12 16:49:35 +02:00
Bogdan
d99da0481b Fix AvistaZ tests 2023-11-12 15:22:35 +02:00
Bogdan
da1965b18e Fixed: (AvistaZ) Fix PublishDate timezone
Fixes #1917
2023-11-12 14:58:56 +02:00
Bogdan
493114f4e8 Fixed: Record status for notifications on tests 2023-11-10 05:34:15 +02:00
Mark McDowall
6969326092 Don't store status results for invalid providers
(cherry picked from commit de23182d593e2284972103d505e66dd8d812dfdb)
(cherry picked from commit 44d8dbaac81706691124ae5f8317289f0a3e5d73)
2023-11-10 04:01:04 +02:00
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
148 changed files with 1830 additions and 1291 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.2'
majorVersion: '1.10.4'
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

@@ -3,6 +3,7 @@ import React, { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import { createSelector } from 'reselect';
import Alert from 'Components/Alert';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import DescriptionListItemDescription from 'Components/DescriptionList/DescriptionListItemDescription';
@@ -23,7 +24,7 @@ import TagListConnector from 'Components/TagListConnector';
import { kinds } from 'Helpers/Props';
import DeleteIndexerModal from 'Indexer/Delete/DeleteIndexerModal';
import EditIndexerModalConnector from 'Indexer/Edit/EditIndexerModalConnector';
import Indexer from 'Indexer/Indexer';
import Indexer, { IndexerCapabilities } from 'Indexer/Indexer';
import { createIndexerSelectorForHook } from 'Store/Selectors/createIndexerSelector';
import translate from 'Utilities/String/translate';
import IndexerHistory from './History/IndexerHistory';
@@ -63,7 +64,7 @@ function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
fields,
tags,
protocol,
capabilities,
capabilities = {} as IndexerCapabilities,
} = indexer as Indexer;
const { onModalClose } = props;
@@ -207,7 +208,7 @@ function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
descriptionClassName={styles.description}
title={translate('RawSearchSupported')}
data={
capabilities.supportsRawSearch
capabilities?.supportsRawSearch
? translate('Yes')
: translate('No')
}
@@ -216,12 +217,12 @@ function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
descriptionClassName={styles.description}
title={translate('SearchTypes')}
data={
capabilities.searchParams.length === 0 ? (
translate('NotSupported')
) : (
capabilities?.searchParams?.length > 0 ? (
<Label kind={kinds.PRIMARY}>
{capabilities.searchParams[0]}
</Label>
) : (
translate('NotSupported')
)
}
/>
@@ -229,60 +230,60 @@ function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
descriptionClassName={styles.description}
title={translate('TVSearchTypes')}
data={
capabilities.tvSearchParams.length === 0
? translate('NotSupported')
: capabilities.tvSearchParams.map((p) => {
capabilities?.tvSearchParams?.length > 0
? capabilities.tvSearchParams.map((p) => {
return (
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
: translate('NotSupported')
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('MovieSearchTypes')}
data={
capabilities.movieSearchParams.length === 0
? translate('NotSupported')
: capabilities.movieSearchParams.map((p) => {
capabilities?.movieSearchParams?.length > 0
? capabilities.movieSearchParams.map((p) => {
return (
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
: translate('NotSupported')
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('BookSearchTypes')}
data={
capabilities.bookSearchParams.length === 0
? translate('NotSupported')
: capabilities.bookSearchParams.map((p) => {
capabilities?.bookSearchParams?.length > 0
? capabilities.bookSearchParams.map((p) => {
return (
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
: translate('NotSupported')
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('MusicSearchTypes')}
data={
capabilities.musicSearchParams.length === 0
? translate('NotSupported')
: capabilities.musicSearchParams.map((p) => {
capabilities?.musicSearchParams?.length > 0
? capabilities.musicSearchParams.map((p) => {
return (
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
: translate('NotSupported')
}
/>
</DescriptionList>
@@ -338,7 +339,11 @@ function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
})}
</Table>
</FieldSet>
) : null}
) : (
<Alert kind={kinds.INFO}>
{translate('NoIndexerCategories')}
</Alert>
)}
</div>
</TabPanel>
<TabPanel>

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

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

@@ -43,7 +43,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AnimeBytesTests
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { { "Content-Type", "application/json" } }, new CookieCollection(), recentFeed)));
var releases = (await Subject.Fetch(new BasicSearchCriteria { Categories = new[] { 2000, 5000 } })).Releases;
var releases = (await Subject.Fetch(new BasicSearchCriteria { SearchTerm = "test", Categories = new[] { 2000, 5000 } })).Releases;
releases.Should().HaveCount(33);
releases.First().Should().BeOfType<TorrentInfo>();

View File

@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://avistaz.to/torrent/187240-japan-sinks-people-of-hope-2021-s01e05-720p-nf-web-dl-ddp20-x264-seikel");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-14 21:26:21"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-11-14 22:26:21"));
torrentInfo.Size.Should().Be(935127615);
torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://exoticaz.to/torrent/64040-ssis-419-my-first-experience-is-yua-mikami-from-the-day-i-lost-my-virginity-i-was-devoted-to-sex");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-06-11 15:04:50"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2022-06-11 10:04:50"));
torrentInfo.Size.Should().Be(7085405541);
torrentInfo.InfoHash.Should().Be("asdjfiasdf54asd7f4a2sdf544asdf");
torrentInfo.MagnetUrl.Should().Be(null);

View File

@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.IndexerTests.AvistazTests
torrentInfo.InfoUrl.Should().Be("https://privatehd.to/torrent/78506-godzilla-2014-2160p-uhd-bluray-remux-hdr-hevc-atmos-triton");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-03-21 04:24:49"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2021-03-21 05:24:49"));
torrentInfo.Size.Should().Be(69914591044);
torrentInfo.InfoHash.Should().Be("a879261d4e6e792402f92401141a21de70d51bf2");
torrentInfo.MagnetUrl.Should().Be(null);

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

@@ -68,5 +68,16 @@ namespace NzbDrone.Core.Test.IndexerTests
VerifyNoUpdate();
}
[Test]
public void should_not_record_failure_for_unknown_provider()
{
Subject.RecordFailure(0);
Mocker.GetMock<IIndexerStatusRepository>()
.Verify(v => v.FindByProviderId(1), Times.Never);
VerifyNoUpdate();
}
}
}

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

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

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

@@ -91,6 +91,11 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var cleanReleases = base.CleanupReleases(releases, searchCriteria);
if (searchCriteria.IsRssSearch)
{
cleanReleases = cleanReleases.Where(r => r.PublishDate > DateTime.Now.AddDays(-1)).ToList();
}
return cleanReleases.Select(r => (ReleaseInfo)r.Clone()).ToList();
}
@@ -555,6 +560,7 @@ namespace NzbDrone.Core.Indexers.Definitions
MinimumRatio = 1,
MinimumSeedTime = minimumSeedTime,
Title = fileName,
Year = year.GetValueOrDefault(),
InfoUrl = details.AbsoluteUri,
Guid = guid.AbsoluteUri,
DownloadUrl = link.AbsoluteUri,
@@ -587,6 +593,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,

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

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

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;
@@ -16,7 +17,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
public override bool SupportsSearch => true;
public override bool SupportsPagination => true;
public override int PageSize => 50;
public override TimeSpan RateLimit => TimeSpan.FromSeconds(4);
public override TimeSpan RateLimit => TimeSpan.FromSeconds(5);
public override IndexerCapabilities Capabilities => SetCapabilities();
protected virtual string LoginUrl => Settings.BaseUrl + "api/v1/jackett/auth";
private IIndexerRepository _indexerRepository;
@@ -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;
@@ -13,7 +14,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
{
public class AvistazParserBase : IParseIndexerResponse
{
protected virtual string TimezoneOffset => "-04:00"; // Avistaz does not specify a timezone & returns server time
protected virtual string TimezoneOffset => "-05:00"; // Avistaz does not specify a timezone & returns server time
private readonly HashSet<string> _hdResolutions = new () { "1080p", "1080i", "720p" };
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
@@ -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

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

@@ -54,6 +54,8 @@ namespace NzbDrone.Core.Indexers.Definitions
{
private readonly IndexerCapabilitiesCategories _categories;
protected override string TimezoneOffset => "+01:00";
public ExoticaZParser(IndexerCapabilitiesCategories categories)
{
_categories = categories;

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

@@ -12,9 +12,10 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
public override string[] LegacyUrls => new[] { "https://hdbits.org" };
public override string Description => "Best HD Tracker";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
public override bool SupportsRedirect => true;
public override int PageSize => 30;
public override bool SupportsPagination => true;
public override int PageSize => 100;
public override IndexerCapabilities Capabilities => SetCapabilities();
public HDBits(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
@@ -45,14 +46,14 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
}
};
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.Audio, "Audio Track");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVDocumentary, "Documentary");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.Other, "Misc/Demo");
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies, "Movie");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TV, "TV");
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVDocumentary, "Documentary");
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.Audio, "Music");
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.TVSport, "Sport");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TV, "TV");
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.Audio, "Audio Track");
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.XXX, "XXX");
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.Other, "Misc/Demo");
return caps;
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace NzbDrone.Core.Indexers.Definitions.HDBits
@@ -7,19 +8,15 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
{
[JsonProperty(Required = Required.Always)]
public string Username { get; set; }
[JsonProperty(Required = Required.Always)]
public string Passkey { get; set; }
public string Hash { get; set; }
public string Search { get; set; }
public int[] Category { get; set; }
public int[] Codec { get; set; }
public int[] Medium { get; set; }
public IEnumerable<int> Category { get; set; }
public IEnumerable<int> Codec { get; set; }
public IEnumerable<int> Medium { get; set; }
public int? Origin { get; set; }
[JsonProperty(PropertyName = "imdb")]
@@ -33,13 +30,9 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
[JsonProperty(PropertyName = "snatched_only")]
public bool? SnatchedOnly { get; set; }
public int? Limit { get; set; }
public int? Page { get; set; }
public TorrentQuery Clone()
{
return MemberwiseClone() as TorrentQuery;
}
}
public class HDBitsResponse

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model;
@@ -14,6 +15,8 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
private readonly HDBitsSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private readonly List<int> _halfLeechMediums = new () { (int)HdBitsMedium.Bluray, (int)HdBitsMedium.Remux, (int)HdBitsMedium.Capture };
public HDBitsParser(HDBitsSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
@@ -22,7 +25,6 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
var indexerHttpResponse = indexerResponse.HttpResponse;
if (indexerHttpResponse.StatusCode == HttpStatusCode.Forbidden)
@@ -42,18 +44,25 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
throw new IndexerException(indexerResponse, "HDBits API request returned status code {0}: {1}", jsonResponse.Status, jsonResponse.Message ?? string.Empty);
}
var responseData = jsonResponse.Data as JArray;
if (responseData == null)
if (jsonResponse.Data is not JArray responseData)
{
throw new IndexerException(indexerResponse, "Indexer API call response missing result data");
}
var releaseInfos = new List<ReleaseInfo>();
var queryResults = responseData.ToObject<TorrentQueryResponse[]>();
foreach (var result in queryResults)
{
// Skip non-freeleech results when freeleech only is set
if (_settings.FreeleechOnly && result.FreeLeech != "yes")
{
continue;
}
var id = result.Id;
var internalRelease = result.TypeOrigin == 1 ? true : false;
var internalRelease = result.TypeOrigin == 1;
var flags = new HashSet<IndexerFlag>();
@@ -62,10 +71,10 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
flags.Add(IndexerFlag.Internal);
}
torrentInfos.Add(new HDBitsInfo()
releaseInfos.Add(new HDBitsInfo
{
Guid = string.Format("HDBits-{0}", id),
Title = result.Name,
Guid = $"HDBits-{id}",
Title = GetTitle(result),
Size = result.Size,
Categories = _categories.MapTrackerCatToNewznab(result.TypeCategory.ToString()),
InfoHash = result.Hash,
@@ -77,19 +86,55 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
Peers = result.Leechers + result.Seeders,
PublishDate = result.Added.ToUniversalTime(),
Internal = internalRelease,
Year = result.ImdbInfo?.Year ?? 0,
ImdbId = result.ImdbInfo?.Id ?? 0,
TvdbId = result.TvdbInfo?.Id ?? 0,
DownloadVolumeFactor = result.FreeLeech == "yes" ? 0 : 1,
UploadVolumeFactor = 1,
DownloadVolumeFactor = GetDownloadVolumeFactor(result),
UploadVolumeFactor = GetUploadVolumeFactor(result),
IndexerFlags = flags
});
}
return torrentInfos.ToArray();
return releaseInfos.ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
private string GetTitle(TorrentQueryResponse item)
{
return _settings.UseFilenames && item.FileName.IsNotNullOrWhiteSpace()
? item.FileName.Replace(".torrent", "", StringComparison.InvariantCultureIgnoreCase)
: item.Name;
}
private double GetDownloadVolumeFactor(TorrentQueryResponse item)
{
if (item.FreeLeech == "yes")
{
return 0;
}
// 100% Neutral Leech: all XXX content.
if (item.TypeCategory == 7)
{
return 0;
}
// 50% Free Leech: all full discs, remuxes, captures and all internal encodes, also all TV and Documentary content.
if (_halfLeechMediums.Contains(item.TypeMedium) || item.TypeOrigin == 1 || item.TypeCategory is 2 or 3)
{
return 0.5;
}
return 1;
}
private static double GetUploadVolumeFactor(TorrentQueryResponse item)
{
// 100% Neutral Leech: all XXX content.
return item.TypeCategory == 7 ? 0 : 1;
}
private string GetDownloadUrl(string torrentId)
{
var url = new HttpUri(_settings.BaseUrl)

View File

@@ -21,12 +21,8 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
{
var pageableRequests = new IndexerPageableRequestChain();
var query = new TorrentQuery();
var imdbId = ParseUtil.GetImdbId(searchCriteria.ImdbId).GetValueOrDefault(0);
if (searchCriteria.Categories?.Length > 0)
{
query.Category = Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories).Select(int.Parse).ToArray();
}
var imdbId = ParseUtil.GetImdbId(searchCriteria.ImdbId).GetValueOrDefault(0);
if (imdbId == 0 && searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
{
@@ -39,37 +35,11 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
query.ImdbInfo.Id = imdbId;
}
pageableRequests.Add(GetRequest(query));
pageableRequests.Add(GetRequest(query, searchCriteria));
return pageableRequests;
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
private IEnumerable<IndexerRequest> GetRequest(TorrentQuery query)
{
var request = new HttpRequestBuilder(Settings.BaseUrl)
.Resource("/api/torrents")
.Build();
request.Method = HttpMethod.Post;
const string appJson = "application/json";
request.Headers.Accept = appJson;
request.Headers.ContentType = appJson;
query.Username = Settings.Username;
query.Passkey = Settings.ApiKey;
query.Codec = Settings.Codecs.ToArray();
query.Medium = Settings.Mediums.ToArray();
request.SetContent(query.ToJson());
request.ContentSummary = query.ToJson(Formatting.None);
yield return new IndexerRequest(request);
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
@@ -79,14 +49,10 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
{
var pageableRequests = new IndexerPageableRequestChain();
var query = new TorrentQuery();
var tvdbId = searchCriteria.TvdbId.GetValueOrDefault(0);
var imdbId = ParseUtil.GetImdbId(searchCriteria.ImdbId).GetValueOrDefault(0);
if (searchCriteria.Categories?.Length > 0)
{
query.Category = Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories).Select(int.Parse).ToArray();
}
if (tvdbId == 0 && imdbId == 0 && searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
{
query.Search = searchCriteria.SanitizedTvSearchString;
@@ -114,7 +80,7 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
query.ImdbInfo.Id = imdbId;
}
pageableRequests.Add(GetRequest(query));
pageableRequests.Add(GetRequest(query, searchCriteria));
return pageableRequests;
}
@@ -129,19 +95,56 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
var pageableRequests = new IndexerPageableRequestChain();
var query = new TorrentQuery();
if (searchCriteria.Categories?.Length > 0)
{
query.Category = Capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories).Select(int.Parse).ToArray();
}
if (searchCriteria.SearchTerm.IsNotNullOrWhiteSpace())
{
query.Search = searchCriteria.SanitizedSearchTerm;
}
pageableRequests.Add(GetRequest(query));
pageableRequests.Add(GetRequest(query, searchCriteria));
return pageableRequests;
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
private IEnumerable<IndexerRequest> GetRequest(TorrentQuery query, SearchCriteriaBase searchCriteria)
{
var request = new HttpRequestBuilder(Settings.BaseUrl)
.Resource("/api/torrents")
.Build();
request.Method = HttpMethod.Post;
const string appJson = "application/json";
request.Headers.Accept = appJson;
request.Headers.ContentType = appJson;
query.Username = Settings.Username;
query.Passkey = Settings.ApiKey;
query.Codec = Settings.Codecs.ToArray();
query.Medium = Settings.Mediums.ToArray();
if (searchCriteria.Categories?.Length > 0)
{
query.Category = Capabilities.Categories
.MapTorznabCapsToTrackers(searchCriteria.Categories)
.Distinct()
.Select(int.Parse)
.ToArray();
}
query.Limit = 100;
if (searchCriteria.Limit is > 0 && searchCriteria.Offset is > 0)
{
query.Page = (int)(searchCriteria.Offset / searchCriteria.Limit);
}
request.SetContent(query.ToJson());
request.ContentSummary = query.ToJson(Formatting.None);
yield return new IndexerRequest(request);
}
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
@@ -10,6 +11,7 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
{
public HDBitsSettingsValidator()
{
RuleFor(c => c.Username).NotEmpty();
RuleFor(c => c.ApiKey).NotEmpty();
}
}
@@ -20,8 +22,10 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
public HDBitsSettings()
{
Codecs = System.Array.Empty<int>();
Mediums = System.Array.Empty<int>();
Codecs = Array.Empty<int>();
Mediums = Array.Empty<int>();
FreeleechOnly = false;
UseFilenames = false;
}
[FieldDefinition(2, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
@@ -30,45 +34,49 @@ namespace NzbDrone.Core.Indexers.Definitions.HDBits
[FieldDefinition(3, Label = "API Key", HelpText = "Site API Key", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
[FieldDefinition(4, Label = "Codecs", Type = FieldType.TagSelect, SelectOptions = typeof(HdBitsCodec), Advanced = true, HelpText = "Options: h264, Mpeg2, VC1, Xvid. If unspecified, all options are used.")]
[FieldDefinition(4, Label = "Codecs", Type = FieldType.Select, SelectOptions = typeof(HdBitsCodec), Advanced = true, HelpText = "If unspecified, all options are used.")]
public IEnumerable<int> Codecs { get; set; }
[FieldDefinition(5, Label = "Mediums", Type = FieldType.TagSelect, SelectOptions = typeof(HdBitsMedium), Advanced = true, HelpText = "Options: BluRay, Encode, Capture, Remux, WebDL. If unspecified, all options are used.")]
[FieldDefinition(5, Label = "Mediums", Type = FieldType.Select, SelectOptions = typeof(HdBitsMedium), Advanced = true, HelpText = "If unspecified, all options are used.")]
public IEnumerable<int> Mediums { get; set; }
[FieldDefinition(6, Label = "Freeleech Only", Type = FieldType.Checkbox, Advanced = true, HelpText = "Show freeleech releases only")]
public bool FreeleechOnly { get; set; }
[FieldDefinition(7, Label = "Use Filenames", Type = FieldType.Checkbox, HelpText = "Check this option if you want to use torrent filenames as release titles")]
public bool UseFilenames { get; set; }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
public enum HdBitsCategory
{
Movie = 1,
Tv = 2,
Documentary = 3,
Music = 4,
Sport = 5,
Audio = 6,
Xxx = 7,
MiscDemo = 8
}
public enum HdBitsCodec
{
[FieldOption("H.264")]
H264 = 1,
[FieldOption("MPEG-2")]
Mpeg2 = 2,
[FieldOption("VC-1")]
Vc1 = 3,
[FieldOption("XviD")]
Xvid = 4,
[FieldOption("HEVC")]
HEVC = 5
}
public enum HdBitsMedium
{
[FieldOption("Blu-ray/HD DVD")]
Bluray = 1,
[FieldOption("Encode")]
Encode = 3,
[FieldOption("Capture")]
Capture = 4,
[FieldOption("Remux")]
Remux = 5,
[FieldOption("WEB-DL")]
WebDl = 6
}
}

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)
{
@@ -242,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);
}
@@ -299,25 +328,28 @@ namespace NzbDrone.Core.Indexers.Definitions
public class NebulanceTorrent
{
[JsonProperty(PropertyName = "rls_name")]
[JsonPropertyName("rls_name")]
public string ReleaseTitle { 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 = "group_name")]
[JsonPropertyName("group_name")]
public string GroupName { get; set; }
[JsonProperty(PropertyName = "series_banner")]
[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, TvSearchParam.ImdbId
},
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,18 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
var pageableRequests = new IndexerPageableRequestChain();
if (searchCriteria.ImdbId.IsNotNullOrWhiteSpace())
{
pageableRequests.Add(GetRequest(searchCriteria.FullImdbId, searchCriteria));
}
else
{
pageableRequests.Add(GetRequest($"{searchCriteria.SanitizedTvSearchString}", searchCriteria));
}
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
@@ -98,9 +66,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;
@@ -94,6 +95,7 @@ namespace NzbDrone.Core.Indexers.Definitions
publishedAt
slug
torrents {
synopsis
downloaded
seeders
leechers
@@ -113,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();
@@ -176,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)
{
@@ -206,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));
@@ -235,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,
@@ -243,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);
@@ -311,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=\"]")

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