Compare commits

...

56 Commits

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

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

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

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

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

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

Mock debouncer for testing

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

Co-authored-by: Timo <Tclemens@live.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: blankhang <blankhang@gmail.com>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-10-11 02:52:16 +03:00
Servarr
bdae7a2cdc Automated API Docs update 2023-10-11 02:51:54 +03:00
Mark McDowall
5e8d3542f4 Log executing health check
(cherry picked from commit 78b39bd2fecda60e04a1fef17ae17f62bd2b6914)
2023-10-11 02:33:38 +03:00
Mark McDowall
d9d2aa8493 Sync label styling for FormLabel
(cherry picked from commit 87e0a7983a437a4d166aa8b9c9eaf78ea5431969)
2023-10-11 02:30:18 +03:00
Stevie Robinson
09bf1500d6 New: Additional tooltips for icon buttons
(cherry picked from commit 8c07f0d3d19a48ed96d1ded54399c66bf2977b2a)
2023-10-11 02:24:32 +03:00
Mark McDowall
34464160cb Paging params in API docs
(cherry picked from commit bfaa7291e14a8d3847ef2154a52c363944560803)
2023-10-11 02:17:27 +03:00
Bogdan
bada5fe309 New: Add TorrentNetwork 2023-10-10 13:29:26 +03:00
Bogdan
b088febbc4 Use async for http client test 2023-10-10 03:05:16 +03:00
Bogdan
1a307b8e21 Fix test for http client 2023-10-10 03:02:35 +03:00
Bogdan
32db2af0ea Improved http timeout handling
Co-authored-by: Qstick <qstick@gmail.com>
2023-10-10 02:41:05 +03:00
Bogdan
e602862102 Fixed: (FileList) Remove dead domain 2023-10-08 21:31:38 +03:00
Bogdan
bd5336e4c4 Bump version to 1.9.4 2023-10-08 07:08:25 +03:00
74 changed files with 960 additions and 683 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.3'
majorVersion: '1.10.3'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,17 @@
using System;
namespace NzbDrone.Common.TPL
{
public interface IDebounceManager
{
Debouncer CreateDebouncer(Action action, TimeSpan debounceDuration);
}
public class DebounceManager : IDebounceManager
{
public Debouncer CreateDebouncer(Action action, TimeSpan debounceDuration)
{
return new Debouncer(action, debounceDuration);
}
}
}

View File

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

View File

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

View File

@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.IndexerTests.FileListTests
torrentInfo.InfoUrl.Should().Be("https://filelist.io/details.php?id=665873");
torrentInfo.CommentUrl.Should().BeNullOrEmpty();
torrentInfo.Indexer.Should().Be(Subject.Definition.Name);
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 19:20:19"));
torrentInfo.PublishDate.Should().Be(DateTime.Parse("2020-01-25 20:20:19"));
torrentInfo.Size.Should().Be(8300512414);
torrentInfo.InfoHash.Should().Be(null);
torrentInfo.MagnetUrl.Should().Be(null);

View File

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

View File

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

View File

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

@@ -555,6 +555,7 @@ namespace NzbDrone.Core.Indexers.Definitions
MinimumRatio = 1,
MinimumSeedTime = minimumSeedTime,
Title = fileName,
Year = year.GetValueOrDefault(),
InfoUrl = details.AbsoluteUri,
Guid = guid.AbsoluteUri,
DownloadUrl = link.AbsoluteUri,
@@ -587,6 +588,7 @@ namespace NzbDrone.Core.Indexers.Definitions
MinimumRatio = 1,
MinimumSeedTime = minimumSeedTime,
Title = releaseTitle.Trim(),
Year = year.GetValueOrDefault(),
InfoUrl = details.AbsoluteUri,
Guid = guid.AbsoluteUri,
DownloadUrl = link.AbsoluteUri,

View File

@@ -130,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())
@@ -143,7 +143,7 @@ namespace NzbDrone.Core.Indexers.Definitions
if (searchTypes.Any())
{
body.Add("types", string.Join(",", searchTypes));
body.Add("types", searchTypes.ToArray());
}
}

View File

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

View File

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

View File

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

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

@@ -265,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);
}
@@ -339,8 +345,11 @@ namespace NzbDrone.Core.Indexers.Definitions
public string Banner { get; set; }
[JsonPropertyName("group_id")]
public string TorrentId { get; set; }
[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

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

@@ -29,12 +29,12 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new PassThePopcornRequestGenerator(Settings);
return new PassThePopcornRequestGenerator(Settings, Capabilities);
}
public override IParseIndexerResponse GetParser()
{
return new PassThePopcornParser(Settings, _logger);
return new PassThePopcornParser(Settings);
}
private IndexerCapabilities SetCapabilities()
@@ -43,6 +43,10 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
{
LimitsDefault = PageSize,
LimitsMax = PageSize,
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
@@ -56,18 +60,11 @@ namespace NzbDrone.Core.Indexers.Definitions.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;
}

View File

@@ -2,7 +2,7 @@ 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;
@@ -14,12 +14,12 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
public class PassThePopcornParser : IParseIndexerResponse
{
private readonly PassThePopcornSettings _settings;
private readonly Logger _logger;
public PassThePopcornParser(PassThePopcornSettings settings, Logger logger)
private static Regex SeasonRegex => new (@"\bS\d{2,3}(E\d{2,3})?\b", RegexOptions.Compiled);
public PassThePopcornParser(PassThePopcornSettings settings)
{
_settings = settings;
_logger = logger;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
@@ -57,6 +57,7 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
foreach (var torrent in result.Torrents)
{
var id = torrent.Id;
var title = torrent.ReleaseName;
var flags = new HashSet<IndexerFlag>();
@@ -70,14 +71,21 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
flags.Add(PassThePopcornFlag.Approved);
}
var categories = new List<IndexerCategory> { NewznabStandardCategory.Movies };
if (title != null && SeasonRegex.Match(title).Success)
{
categories.Add(NewznabStandardCategory.TV);
}
torrentInfos.Add(new TorrentInfo
{
Guid = $"PassThePopcorn-{id}",
Title = torrent.ReleaseName,
Title = title,
Year = int.Parse(result.Year),
InfoUrl = GetInfoUrl(result.GroupId, id),
DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey),
Categories = new List<IndexerCategory> { NewznabStandardCategory.Movies },
Categories = categories,
Size = long.Parse(torrent.Size),
Grabs = int.Parse(torrent.Snatched),
Seeders = int.Parse(torrent.Seeders),

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
@@ -11,10 +12,12 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
public class PassThePopcornRequestGenerator : IIndexerRequestGenerator
{
private readonly PassThePopcornSettings _settings;
private readonly IndexerCapabilities _capabilities;
public PassThePopcornRequestGenerator(PassThePopcornSettings settings)
public PassThePopcornRequestGenerator(PassThePopcornSettings settings, IndexerCapabilities capabilities)
{
_settings = settings;
_capabilities = capabilities;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
@@ -27,7 +30,7 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
}
else
{
pageableRequests.Add(GetRequest($"{searchCriteria.SearchTerm}", searchCriteria));
pageableRequests.Add(GetRequest($"{searchCriteria.SanitizedSearchTerm}", searchCriteria));
}
return pageableRequests;
@@ -40,7 +43,11 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest($"{searchCriteria.SanitizedTvSearchString}", searchCriteria));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
@@ -52,32 +59,43 @@ namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetRequest($"{searchCriteria.SearchTerm}", searchCriteria));
pageableRequests.Add(GetRequest($"{searchCriteria.SanitizedSearchTerm}", searchCriteria));
return pageableRequests;
}
private IEnumerable<IndexerRequest> GetRequest(string searchParameters, SearchCriteriaBase searchCriteria)
private IEnumerable<IndexerRequest> GetRequest(string searchTerm, SearchCriteriaBase searchCriteria)
{
var parameters = new NameValueCollection
{
{ "action", "advanced" },
{ "json", "noredirect" },
{ "grouping", "0" },
{ "searchstr", searchParameters }
{ "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());
}
if (_settings.FreeleechOnly)
{
parameters.Set("freetorrent", "1");
}
var searchUrl = $"{_settings.BaseUrl.Trim().TrimEnd('/')}/torrents.php?{parameters.GetQueryString()}";
var request = new IndexerRequest(searchUrl, HttpAccept.Json);

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";

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

@@ -31,6 +31,7 @@ namespace NzbDrone.Core.Indexers.Torznab
{
return new NewznabRequestGenerator(_capabilitiesProvider)
{
Definition = Definition,
PageSize = PageSize,
Settings = Settings
};
@@ -83,9 +84,10 @@ namespace NzbDrone.Core.Indexers.Torznab
{
get
{
yield return GetDefinition("AnimeTosho", "Anime NZB/DDL mirror", GetSettings("https://feed.animetosho.org"));
yield return GetDefinition("MoreThanTV", "Private torrent tracker for TV / MOVIES", GetSettings("https://www.morethantv.me", apiPath: @"/api/torznab"));
yield return GetDefinition("Generic Torznab", "A Newznab-like api for torrents.", GetSettings(""));
yield return GetDefinition("AnimeTosho", "Anime NZB/DDL mirror", settings: GetSettings("https://feed.animetosho.org"));
yield return GetDefinition("MoreThanTV", "Private torrent tracker for TV / MOVIES", settings: GetSettings("https://www.morethantv.me", apiPath: @"/api/torznab"));
yield return GetDefinition("Torrent Network", "Torrent Network (TN) is a GERMAN Private site for TV / MOVIES / GENERAL", language: "de-DE", settings: GetSettings("https://tntracker.org", apiPath: @"/api/torznab/api"));
yield return GetDefinition("Generic Torznab", "A Newznab-like api for torrents.", settings: GetSettings(""));
}
}
@@ -95,16 +97,17 @@ namespace NzbDrone.Core.Indexers.Torznab
_capabilitiesProvider = capabilitiesProvider;
}
private IndexerDefinition GetDefinition(string name, string description, TorznabSettings settings)
private IndexerDefinition GetDefinition(string name, string description, string language = null, TorznabSettings settings = null)
{
return new IndexerDefinition
{
Enable = true,
Name = name,
Description = description,
Language = language ?? "en-US",
Implementation = GetType().Name,
Settings = settings,
Protocol = DownloadProtocol.Usenet,
Protocol = DownloadProtocol.Torrent,
SupportsRss = SupportsRss,
SupportsSearch = SupportsSearch,
SupportsRedirect = SupportsRedirect,

View File

@@ -207,7 +207,7 @@ namespace NzbDrone.Core.Indexers
definition.Privacy = provider.Privacy;
definition.Description ??= provider.Description;
definition.Encoding = provider.Encoding;
definition.Language = provider.Language;
definition.Language ??= provider.Language;
definition.Capabilities = provider.Capabilities;
}
}

View File

@@ -3,7 +3,7 @@
"AcceptConfirmationModal": "Bestätigung akzeptieren Modal",
"Actions": "Aktionen",
"Add": "Hinzufügen",
"AddDownloadClient": "Zum Downloader hinzufügen",
"AddDownloadClient": "Downloadmanager hinzufügen",
"AddDownloadClientToProwlarr": "Durch das Hinzufügen eines Download-Clients kann Prowlarr während einer manuellen Suche Releases direkt über die Benutzeroberfläche senden.",
"AddIndexer": "Indexer hinzufügen",
"AddIndexerProxy": "Indexer Proxy hinzufügen",
@@ -497,12 +497,15 @@
"WhatsNew": "Was gibt's Neues?",
"minutes": "Minuten",
"DeleteAppProfileMessageText": "Qualitätsprofil '{0}' wirklich löschen?",
"AddConnection": "Sammlung bearbeiten",
"AddConnection": "Verbindung hinzufügen",
"NotificationStatusAllClientHealthCheckMessage": "Wegen Fehlern sind keine Applikationen verfügbar",
"NotificationStatusSingleClientHealthCheckMessage": "Applikationen wegen folgender Fehler nicht verfügbar: {0}",
"AuthBasic": "Einfach (Browser Popup)",
"AuthForm": "Formular (Login Seite)",
"DisabledForLocalAddresses": "Für Lokale Adressen deaktivieren",
"None": "Keine",
"ResetAPIKeyMessageText": "Bist du sicher, dass du den API-Schlüssel zurücksetzen willst?"
"ResetAPIKeyMessageText": "Bist du sicher, dass du den API-Schlüssel zurücksetzen willst?",
"AddCustomFilter": "Eigenen Filter hinzufügen",
"AddApplication": "Application hinzufügen",
"AddCategory": "Kategorie hinzufügen"
}

View File

@@ -12,7 +12,7 @@
"Events": "Événements",
"Edit": "Modifier",
"DownloadClientStatusCheckAllClientMessage": "Aucun client de téléchargement n'est disponible en raison d'échecs",
"DownloadClients": "Clients de télécharg.",
"DownloadClients": "Clients de téléchargements",
"Dates": "Dates",
"Date": "Date",
"Delete": "Supprimer",
@@ -28,19 +28,19 @@
"About": "À propos",
"IndexerStatusCheckSingleClientMessage": "Indexeurs indisponibles en raison d'échecs : {0}",
"DownloadClientStatusCheckSingleClientMessage": "Clients de Téléchargement indisponibles en raison d'échecs: {0}",
"SetTags": "Définir les étiquettes",
"SetTags": "Définir des balises",
"ReleaseStatus": "Statut de la version",
"UpdateCheckUINotWritableMessage": "Impossible d'installer la mise à jour car le dossier d'interface utilisateur '{0}' n'est pas accessible en écriture par l'utilisateur '{1}'.",
"UpdateCheckStartupTranslocationMessage": "Impossible d'installer la mise à jour car le dossier de démarrage '{0}' se trouve dans un dossier App Translocation.",
"UpdateCheckStartupNotWritableMessage": "Impossible d'installer la mise à jour car le dossier de démarrage '{0}' n'est pas accessible en écriture par l'utilisateur '{1}'.",
"UnselectAll": "Tout désélectionner",
"UISettingsSummary": "Date, langue, et perceptions des couleurs",
"TagsSettingsSummary": "Voir toutes les étiquettes et leur utilisation. Les étiquettes inutilisées peuvent être supprimées",
"TagsSettingsSummary": "Voir toutes les balises et comment elles sont utilisées. Les balises inutilisées peuvent être supprimées",
"Style": "Style",
"Status": "État",
"Sort": "Trier",
"Size": "Taille",
"ShowAdvanced": "Afficher param. av.",
"ShowAdvanced": "Afficher les paramètres avancés",
"Settings": "Paramètres",
"SelectAll": "Tout sélectionner",
"Security": "Sécurité",
@@ -49,7 +49,7 @@
"SaveChanges": "Sauvegarder les modifications",
"RestoreBackup": "Restaurer la sauvegarde",
"ReleaseBranchCheckOfficialBranchMessage": "La branche {0} n'est pas une branche de version Prowlarr valide, vous ne recevrez pas de mises à jour",
"Refresh": "Actualiser",
"Refresh": "Rafraîchir",
"Queue": "File d'attente",
"ProxyCheckResolveIpMessage": "Impossible de résoudre l'adresse IP de l'hôte proxy configuré {0}",
"ProxyCheckFailedToTestMessage": "Échec du test du proxy : {0}",
@@ -57,19 +57,19 @@
"Proxy": "Proxy",
"Protocol": "Protocole",
"Options": "Options",
"NoChanges": "Aucune modification",
"NoChanges": "Aucuns changements",
"NoChange": "Pas de changement",
"MoreInfo": "Plus d'informations",
"Grabbed": "Récupéré",
"Grabbed": "Saisie",
"DownloadClientsSettingsSummary": "Configuration des clients de téléchargement pour intégration dans la recherche de l'interface utilisateur de Prowlarr",
"DownloadClient": "Client de Téléchargement",
"DownloadClient": "Client de téléchargement",
"Logging": "Enregistrement",
"LogFiles": "Fichiers Log",
"View": "Vue",
"LogFiles": "Fichiers journaux",
"View": "Vues",
"Updates": "Mises à jour",
"UI": "UI",
"Tasks": "Tâches",
"Tags": "Étiquettes",
"Tags": "Tags",
"System": "Système",
"LastWriteTime": "Heure de la dernière écriture",
"Language": "Langue",
@@ -82,7 +82,7 @@
"ConnectSettingsSummary": "Notifications et scripts personnalisés",
"Added": "Ajouté",
"Actions": "Actions",
"Info": "Info",
"Info": "Information",
"Error": "Erreur",
"ConnectionLost": "Connexion perdue",
"Component": "Composant",
@@ -98,16 +98,16 @@
"TestAll": "Tout tester",
"Test": "Tester",
"TableOptionsColumnsMessage": "Choisissez quelles colonnes sont visibles et dans quel ordre elles apparaissent",
"TableOptions": "Paramètres de table",
"TableOptions": "Options des tableaux",
"Source": "Source",
"Shutdown": "Éteindre",
"Seeders": "Seeders",
"Save": "Sauvegarder",
"Restart": "Redémarrer",
"Reload": "Recharger",
"Peers": "Pairs",
"Peers": "Peers",
"PageSize": "Pagination",
"Ok": "OK",
"Ok": "Ok",
"OAuthPopupMessage": "Les pop-ups sont bloquées par votre navigateur",
"Name": "Nom",
"Message": "Message",
@@ -116,8 +116,8 @@
"HealthNoIssues": "Aucun problème avec votre configuration",
"SystemTimeCheckMessage": "L'heure du système est décalée de plus d'un jour. Les tâches planifiées peuvent ne pas s'exécuter correctement tant que l'heure ne sera pas corrigée",
"SettingsShowRelativeDates": "Afficher les dates relatives",
"UnsavedChanges": "Modifications non sauvegardées",
"ShowSearchHelpText": "Affiche le bouton de recherche au survol du curseur",
"UnsavedChanges": "Modifications non enregistrées",
"ShowSearchHelpText": "Afficher le bouton de recherche au survol",
"ShowSearch": "Afficher la recherche",
"SettingsTimeFormat": "Format de l'heure",
"SettingsShowRelativeDatesHelpText": "Afficher les dates relatives (aujourd'hui, hier, etc.) ou absolues",
@@ -136,7 +136,7 @@
"BypassProxyForLocalAddresses": "Contourner le proxy pour les adresses locales",
"Branch": "Branche",
"BindAddressHelpText": "Adresse IP valide, localhost ou « * » pour toutes les interfaces",
"BindAddress": "Adresse d'attache",
"BindAddress": "Adresse de liaison",
"Backups": "Sauvegardes",
"BackupRetentionHelpText": "Les sauvegardes automatiques plus anciennes que la période de conservation seront automatiquement effacées",
"BackupIntervalHelpText": "Intervalle entre les sauvegardes automatiques",
@@ -148,15 +148,15 @@
"ApiKey": "Clé API",
"AnalyticsEnabledHelpText": "Envoyer des informations anonymes sur l'utilisation et les erreurs vers les serveurs de Prowlarr. Cela inclut des informations sur votre navigateur, quelle page Prowlarr WebUI vous utilisez, les rapports d'erreurs, ainsi que le système d'exploitation et sa version. Nous utiliserons ces informations pour prioriser les nouvelles fonctionnalités et les corrections de bugs.",
"IgnoredAddresses": "Adresses ignorées",
"Hostname": "Nom d'hôte",
"GeneralSettings": "Réglages Généraux",
"Fixed": "Corrigé",
"Hostname": "Hostname",
"GeneralSettings": "Réglages généraux",
"Fixed": "Fixé",
"EnableSslHelpText": " Nécessite un redémarrage en tant qu'administrateur pour être effectif",
"EnableSSL": "Activer le SSL",
"EnableInteractiveSearch": "Activer la recherche interactive",
"EnableAutomaticSearch": "Activer la recherche automatique",
"Enable": "Activer",
"DownloadClientSettings": "Réglages Clients de téléchargement",
"DownloadClientSettings": "Télécharger les paramètres client",
"Docker": "Docker",
"DeleteTag": "Supprimer le tag",
"DeleteNotification": "Supprimer la notification",
@@ -190,7 +190,7 @@
"UnableToLoadNotifications": "Impossible de charger les notifications",
"Version": "Version",
"Username": "Nom d'utilisateur",
"UseProxy": "Utiliser un proxy",
"UseProxy": "Utiliser le proxy",
"Usenet": "Usenet",
"UrlBaseHelpText": "Pour la prise en charge du proxy inverse, la valeur par défaut est vide",
"URLBase": "Base URL",
@@ -198,17 +198,17 @@
"Mode": "Mode",
"Mechanism": "Mécanisme",
"Manual": "Manuel",
"MaintenanceRelease": "Version de maintenance : corrections de bugs et autres améliorations. Voir historique des changements Github pour plus d'informations",
"MaintenanceRelease": "Version de maintenance : corrections de bugs et autres améliorations. Voir l'historique des validations Github pour plus de détails",
"Logs": "Journaux",
"LogLevelTraceHelpTextWarning": "La journalisation des traces ne doit être activée que temporairement",
"LogLevel": "Niveau du journal",
"LogLevel": "Niveau de journalisation",
"IncludeHealthWarningsHelpText": "Inclure avertissements santé",
"FocusSearchBox": "Placer le curseur sur la barre de recherche",
"ProxyBypassFilterHelpText": "Utiliser ',' comme séparateur et '*.' comme caractère générique pour les sous-domaines",
"Uptime": "Durée de fonctionnent",
"UpdateScriptPathHelpText": "Chemin vers un script personnalisé qui prend un package de mise à jour extraite et gère le reste du processus de mise à jour",
"ProxyBypassFilterHelpText": "Utilisez ',' comme séparateur et '*.' comme caractère générique pour les sous-domaines",
"Uptime": "Disponibilité",
"UpdateScriptPathHelpText": "Chemin d'accès à un script personnalisé qui prend un package de mise à jour extrait et gère le reste du processus de mise à jour",
"UpdateMechanismHelpText": "Utiliser le programme de mise à jour intégré de Prowlarr ou un script",
"UpdateAutomaticallyHelpText": "Télécharger et installer automatiquement les mises à jour. Vous pourrez toujours installer à partir de System : Updates",
"UpdateAutomaticallyHelpText": "Téléchargez et installez automatiquement les mises à jour. Vous pourrez toujours installer à partir du système : mises à jour",
"UnableToLoadUISettings": "Impossible de charger les paramètres de l'interface utilisateur",
"UnableToLoadTags": "Impossible de charger les étiquettes",
"UnableToLoadHistory": "Impossible de charger l'historique",
@@ -221,7 +221,7 @@
"TagIsNotUsedAndCanBeDeleted": "L'étiquette n'est pas utilisée et peut être supprimée",
"TagsHelpText": "S'applique aux indexeurs avec au moins une étiquette correspondante",
"StartTypingOrSelectAPathBelow": "Commencer à écrire ou sélectionner un chemin ci-dessous",
"NoTagsHaveBeenAddedYet": "Aucune étiquette n'a encore été ajoutée",
"NoTagsHaveBeenAddedYet": "Aucune identification n'a été ajoutée pour l'instant",
"IndexerFlags": "Indicateurs d'indexeur",
"DeleteTagMessageText": "Voulez-vous vraiment supprimer l'étiquette « {label} » ?",
"UISettings": "Paramètres UI",
@@ -250,28 +250,28 @@
"RestartNow": "Redémarrer maintenant",
"ResetAPIKey": "Réinitialiser la clé API",
"Reset": "Réinitialiser",
"RemovingTag": "Suppression du tag",
"ExistingTag": "Tag existant",
"RemovingTag": "Supprimer la balise",
"ExistingTag": "Balise existante",
"RemoveFilter": "Supprimer le filtre",
"RemovedFromTaskQueue": "Supprimé de la file d'attente des tâches",
"RefreshMovie": "Actualiser le film",
"ReadTheWikiForMoreInformation": "Consultez le Wiki pour plus d'informations",
"ReadTheWikiForMoreInformation": "Lisez le wiki pour plus d'informations",
"ProwlarrSupportsAnyIndexer": "Prowlarr prend en charge de nombreux indexeurs en plus de tout indexeur qui utilise la norme Newznab/Torznab en utilisant « Generic Newznab » (pour usenet) ou « Generic Torznab » (pour les torrents). Recherchez et sélectionnez votre indexeur ci-dessous.",
"ProwlarrSupportsAnyDownloadClient": "Prowlarr prend en charge tout client de téléchargement qui utilise le standard Newznab, ainsi que d'autres clients de téléchargement répertoriés ci-dessous.",
"ProxyUsernameHelpText": "Il vous suffit de saisir un nom d'utilisateur et un mot de passe si vous en avez besoin. Sinon, laissez-les vides.",
"ProxyType": "Type de proxy",
"ProxyPasswordHelpText": "Il vous suffit de saisir un nom d'utilisateur et un mot de passe si vous en avez besoin. Sinon, laissez-les vides.",
"ProxyUsernameHelpText": "Il vous suffit de saisir un nom d'utilisateur et un mot de passe si nécessaire. Sinon, laissez-les vides.",
"ProxyType": "Type de mandataire",
"ProxyPasswordHelpText": "Il vous suffit de saisir un nom d'utilisateur et un mot de passe si nécessaire. Sinon, laissez-les vides.",
"Priority": "Priorité",
"PortNumber": "Numéro de port",
"Port": "Port",
"Password": "Mot de passe",
"PageSizeHelpText": "Nombre d'éléments à afficher sur chaque page",
"PackageVersion": "Version du package",
"PackageVersion": "Version du paquet",
"OpenBrowserOnStart": "Ouvrir le navigateur au démarrage",
"NoUpdatesAreAvailable": "Aucune mise à jour n'est disponible",
"NotificationTriggers": "Déclencheurs de notification",
"NotificationTriggers": "Déclencheurs de notifications",
"NoLogFiles": "Aucun fichier journal",
"NoLeaveIt": "Non, laisse-le",
"NoLeaveIt": "Non, laisse tomber",
"NoBackupsAreAvailable": "Aucune sauvegarde n'est disponible",
"New": "Nouveau",
"NetCore": ".NET Core",
@@ -339,7 +339,7 @@
"Apps": "Applications",
"Auth": "Auth",
"Category": "Catégorie",
"Custom": "Personnalisé",
"Custom": "Customisé",
"DeleteAppProfile": "Supprimer le profil de l'application",
"Description": "Description",
"Donations": "Dons",
@@ -383,8 +383,8 @@
"HistoryCleanupDaysHelpText": "Définir sur 0 pour désactiver le nettoyage automatique",
"HistoryCleanupDaysHelpTextWarning": "Les fichiers dans la corbeille plus anciens que le nombre de jours sélectionné seront nettoyés automatiquement",
"OnGrab": "Récupéré à la sortie",
"OnHealthIssue": "Lors d'un problème de santé",
"TestAllIndexers": "Tester tous les indexeurs",
"OnHealthIssue": "Sur la question de la santé",
"TestAllIndexers": "Testez tous les indexeurs",
"UserAgentProvidedByTheAppThatCalledTheAPI": "User-Agent fourni par l'application qui a appelé l'API",
"Database": "Base de données",
"HistoryCleanup": "Nettoyage de l'historique",
@@ -404,7 +404,7 @@
"Website": "Site internet",
"AudioSearch": "Recherche de musique",
"BookSearch": "Recherche de livres",
"OnApplicationUpdate": "Lors de la mise à jour de l'app",
"OnApplicationUpdate": "Sur la mise à jour de l'application",
"OnApplicationUpdateHelpText": "Lors de la mise à jour de l'app",
"IndexerNoDefCheckMessage": "Les indexeurs ne sont pas définis et ne fonctionneront pas :Merci de les retirer et (ou) les ajouter à nouveau à Prowlarr",
"MovieSearch": "Recherche de films",
@@ -430,7 +430,7 @@
"Duration": "Durée",
"LastDuration": "Dernière durée",
"InstanceName": "Nom de l'instance",
"InstanceNameHelpText": "Nom de l'instance dans l'onglet du navigateur et pour le nom d'application dans Syslog",
"InstanceNameHelpText": "Nom de l'instance dans l'onglet et pour le nom de l'application Syslog",
"ApplicationLongTermStatusCheckSingleClientMessage": "Applications indisponibles en raison de défaillances depuis plus de 6 heures : {0}",
"ApplicationLongTermStatusCheckAllClientMessage": "Toutes les applications sont indisponibles en raison de défaillances depuis plus de 6 heures",
"Ended": "Terminé",
@@ -506,7 +506,7 @@
"ApplyTagsHelpTextHowToApplyIndexers": "Comment appliquer des étiquettes aux indexeurs sélectionnés",
"ApplyTagsHelpTextRemove": "Supprimer : supprime les étiquettes renseignées",
"ApplyTagsHelpTextReplace": "Remplacer : remplace les étiquettes par les étiquettes renseignées (ne pas renseigner d'étiquette pour toutes les effacer)",
"DeleteSelectedDownloadClients": "Supprimer le client de téléchargement",
"DeleteSelectedDownloadClients": "Supprimer le(s) client(s) de téléchargement",
"DeleteSelectedDownloadClientsMessageText": "Voulez-vous vraiment supprimer {count} client(s) de téléchargement sélectionné(s) ?",
"StopSelecting": "Effacer la sélection",
"UpdateAvailable": "Une nouvelle mise à jour est disponible",
@@ -535,17 +535,17 @@
"AddConnectionImplementation": "Ajouter une connexion - {implementationName}",
"AddApplicationImplementation": "Ajouter une application - {implementationName}",
"AddIndexerImplementation": "Ajouter un indexeur - {implementationName}",
"EditConnectionImplementation": "Ajouter une connexion - {implementationName}",
"NotificationStatusAllClientHealthCheckMessage": "Toutes les notifications sont indisponibles en raison de dysfonctionnements",
"NotificationStatusSingleClientHealthCheckMessage": "Notifications indisponibles en raison de dysfonctionnements : {0}",
"EditConnectionImplementation": "Modifier la connexion - {implementationName}",
"NotificationStatusAllClientHealthCheckMessage": "Toutes les notifications ne sont pas disponibles en raison d'échecs",
"NotificationStatusSingleClientHealthCheckMessage": "Notifications indisponibles en raison d'échecs : {0}",
"EditApplicationImplementation": "Ajouter une condition - {implementationName}",
"EditIndexerImplementation": "Ajouter une condition - {implementationName}",
"EditIndexerImplementation": "Modifier l'indexeur - {implementationName}",
"EditIndexerProxyImplementation": "Modifier un proxy d'indexeur - {implementationName}",
"AuthBasic": "Basique (fenêtre surgissante du navigateur)",
"AuthForm": "Formulaire (page de connexion)",
"DisabledForLocalAddresses": "Désactivée pour les adresses IP locales",
"None": "Aucun",
"ResetAPIKeyMessageText": "Voulez-vous réinitialiser votre clé d'API ?",
"ResetAPIKeyMessageText": "Êtes-vous sûr de vouloir réinitialiser votre clé API ?",
"AddIndexerProxyImplementation": "Ajouter un proxy d'indexeur - {implementationName}",
"IndexerStatus": "État de l'indexeur",
"ManageApplications": "Gérer les applications",
@@ -559,7 +559,7 @@
"AppUpdated": "{appName} mis à jour",
"AppUpdatedVersion": "{appName} a été mis à jour vers la version `{version}`, pour profiter des derniers changements, vous devrez relancer {appName}",
"IndexerDownloadClientHelpText": "Préciser quel client de téléchargement est utilisé pour les saisies créées au sein de Prowlarr provenant de cet indexeur",
"Implementation": "Implémentation",
"Implementation": "Mise en œuvre",
"SearchCountIndexers": "Rechercher {count} indexeur(s)",
"SearchAllIndexers": "Rechercher tous les indexeurs",
"NewznabUrl": "URL Newznab",
@@ -593,5 +593,9 @@
"ActiveIndexers": "Indexeurs actifs",
"ActiveApps": "Applications actives",
"AuthenticationRequiredUsernameHelpTextWarning": "Saisir un nouveau nom d'utilisateur",
"Clone": "Cloner"
"Clone": "Cloner",
"PackSeedTime": "Temps de Seed",
"ApplicationTagsHelpText": "Synchroniser les indexeurs avec cette application qui n'ont aucune balise ou qui ont une ou plusieurs balises correspondantes",
"OnHealthRestored": "Sur la santé restaurée",
"OnHealthRestoredHelpText": "Sur la santé restaurée"
}

View File

@@ -47,7 +47,7 @@
"CertificateValidationHelpText": "Módosítsa a HTTPS tanúsítás szigorúságát",
"CertificateValidation": "Tanúsítvány érvényesítése",
"CancelPendingTask": "Biztosan törlöd ezt a függőben lévő feladatot?",
"Cancel": "Vissza",
"Cancel": "Mégse",
"BypassProxyForLocalAddresses": "Proxy megkerülése a helyi hálózatos címekhez",
"BranchUpdateMechanism": "A külső frissítési mechanizmus által használt ágazat",
"BranchUpdate": "Ágazattípus a Prowlarr frissítéseihez",
@@ -55,19 +55,19 @@
"BindAddressHelpText": "Érvényes IP-cím, localhost vagy '*' minden interfészhez",
"BindAddress": "Kapcsolási Cím",
"BeforeUpdate": "Alkalmazásfrissítés előtt",
"Backups": "Biztonsági Mentés",
"Backups": "Biztonsági mentések",
"BackupRetentionHelpText": "A megőrzési időnél régebbi automatikus biztonsági másolatok automatikusan törlésre kerülnek",
"BackupNow": "Biztonsági Mentés Most",
"BackupIntervalHelpText": "Időeltérés a biztonsági mentések között",
"BackupFolderHelpText": "Az elérési útvonalak a Prowlarr AppData könyvtárában lesznek",
"Backup": "Biztonsági Mentés",
"AutomaticSearch": "Automatikus Keresés",
"AutomaticSearch": "Automatikus keresés",
"Automatic": "Automatikus",
"AnalyticsEnabledHelpText": "Küldjön névtelen használati és hibainformációkat a Prowlarr szervereire. Ez magában foglalja a böngészőjéről szóló információkat, mely Prowlarr WebUI oldalakat használja, a hibajelentést, valamint az operációs rendszer adatait. Ezeket az információkat a funkciók és a hibajavítások rangsorolására használjuk fel.",
"AuthenticationMethodHelpText": "Felhasználónév és Jelszó szükséges a Prowlarr-hoz való hozzáféréshez",
"Authentication": "Hitelesítés",
"ApplyTags": "Címkék alkalmazása",
"Age": "Kora",
"Age": "Kor",
"ApiKey": "API Kulcs",
"All": "Összes",
"AcceptConfirmationModal": "Változás Megerősítése",
@@ -459,7 +459,7 @@
"TheLatestVersionIsAlreadyInstalled": "A Prowlarr legújabb verziója már telepítva van",
"Remove": "Eltávolítás",
"Replace": "Kicserél",
"ApplicationURL": "Alkalmazás URL-je",
"ApplicationURL": "Alkalmazás URL",
"ApplicationUrlHelpText": "Az alkalmazás külső URL-címe, beleértve a http(s)://-t, a portot és az URL-alapot",
"More": "Több",
"Publisher": "Kiadó",
@@ -501,5 +501,7 @@
"AuthForm": "Felhasználó (Bejelentkezési oldal)",
"DisabledForLocalAddresses": "Letiltva a helyi címeknél",
"None": "Nincs",
"ResetAPIKeyMessageText": "Biztos hogy vissza szeretnéd állítani az API-Kulcsod?"
"ResetAPIKeyMessageText": "Biztos hogy vissza szeretnéd állítani az API-Kulcsod?",
"AuthenticationRequiredPasswordHelpTextWarning": "Adjon meg új jelszót",
"AuthenticationRequiredUsernameHelpTextWarning": "Adjon meg új felhasználónevet"
}

View File

@@ -444,5 +444,7 @@
"AuthForm": "Formulários (página de início de sessão)",
"DisabledForLocalAddresses": "Desativado para endereços locais",
"None": "Nenhum",
"ResetAPIKeyMessageText": "Tem a certeza que quer repor a Chave da API?"
"ResetAPIKeyMessageText": "Tem a certeza que quer repor a Chave da API?",
"ActiveApps": "Aplicações Ativadas",
"ActiveIndexers": "Indexadores ativados"
}

View File

@@ -4,10 +4,10 @@
"Actions": "Ações",
"Add": "Adicionar",
"AddApplication": "Adicionar Aplicativo",
"AddCustomFilter": "Adicionar Filtro Personalizado",
"AddDownloadClient": "Adicionar Cliente de Download",
"AddCustomFilter": "Adicionar filtro personalizado",
"AddDownloadClient": "Adicionar cliente de download",
"AddDownloadClientToProwlarr": "Adicionar um cliente de download possibilita que o Prowlarr envie lançamentos diretamente da interface ao executar uma pesquisa manual.",
"AddIndexer": "Adicionar Indexador",
"AddIndexer": "Adicionar indexador",
"AddIndexerProxy": "Adicionar Proxy ao Indexador",
"AddNewIndexer": "Adicionar novo indexador",
"AddRemoveOnly": "Adicionar e remover apenas",
@@ -16,13 +16,13 @@
"Added": "Adicionado",
"AddedToDownloadClient": "Lançamento adicionado ao cliente",
"AddingTag": "Adicionar tag",
"Age": "Idade",
"Age": "Tempo de vida",
"Album": "Álbum",
"All": "Todos",
"AllIndexersHiddenDueToFilter": "Todos os indexadores estão ocultos devido ao filtro aplicado.",
"Analytics": "Analítica",
"Analytics": "Análises",
"AnalyticsEnabledHelpText": "Envie informações anônimas de uso e erro para os servidores do Prowlarr. Isso inclui informações sobre seu navegador, quais páginas da interface Web do Prowlarr você usa, relatórios de erros, e a versão do sistema operacional e do tempo de execução. Usaremos essas informações para priorizar recursos e correções de bugs.",
"ApiKey": "Chave API",
"ApiKey": "Chave da API",
"ApiKeyValidationHealthCheckMessage": "Atualize sua chave de API para ter pelo menos {0} caracteres. Você pode fazer isso através das configurações ou do arquivo de configuração",
"AppDataDirectory": "Diretório AppData",
"AppDataLocationHealthCheckMessage": "A atualização não será possível para evitar a exclusão de AppData na atualização",
@@ -34,8 +34,8 @@
"ApplicationLongTermStatusCheckSingleClientMessage": "Aplicativos indisponíveis devido a falhas por mais de 6 horas: {0}",
"ApplicationStatusCheckAllClientMessage": "Não há aplicativos disponíveis devido a falhas",
"ApplicationStatusCheckSingleClientMessage": "Aplicativos indisponíveis devido a falhas: {0}",
"ApplicationURL": "URL do Aplicativo",
"ApplicationUrlHelpText": "A URL externa deste aplicativo, incluindo http(s)://, porta e base da URL",
"ApplicationURL": "URL do aplicativo",
"ApplicationUrlHelpText": "A URL externa deste aplicativo, incluindo http(s)://, porta e URL base",
"Applications": "Aplicativos",
"Apply": "Aplicar",
"ApplyTags": "Aplicar Tags",
@@ -45,13 +45,13 @@
"AudioSearch": "Pesquisar Áudio",
"Auth": "Autenticação",
"Authentication": "Autenticação",
"AuthenticationMethodHelpText": "Exigir nome de usuário e senha para acessar {appName}",
"AuthenticationRequired": "Autentificação Requerida",
"AuthenticationMethodHelpText": "Exigir nome de usuário e senha para acessar o {appName}",
"AuthenticationRequired": "Autenticação exigida",
"AuthenticationRequiredHelpText": "Altere para quais solicitações a autenticação é necessária. Não mude a menos que você entenda os riscos.",
"AuthenticationRequiredWarning": "Para evitar o acesso remoto sem autenticação, {appName} agora exige que a autenticação esteja habilitada. Opcionalmente, você pode desabilitar a autenticação de endereços locais.",
"Author": "Autor",
"Automatic": "Automático",
"AutomaticSearch": "Pesquisa Automática",
"AutomaticSearch": "Pesquisa automática",
"AverageResponseTimesMs": "Tempos Médios de Resposta do Indexador (ms)",
"Backup": "Backup",
"BackupFolderHelpText": "Os caminhos relativos estarão no diretório AppData do Prowlarr",
@@ -60,7 +60,7 @@
"BackupRetentionHelpText": "Backups automáticos anteriores ao período de retenção serão limpos automaticamente",
"Backups": "Backups",
"BeforeUpdate": "Antes da atualização",
"BindAddress": "Fixar Endereço",
"BindAddress": "Fixar endereço",
"BindAddressHelpText": "Endereço IP válido, localhost ou '*' para todas as interfaces",
"Book": "Livro",
"BookSearch": "Pesquisar Livro",
@@ -73,26 +73,26 @@
"CancelPendingTask": "Tem certeza de que deseja cancelar essa tarefa pendente?",
"Categories": "Categorias",
"Category": "Categoria",
"CertificateValidation": "Validação de Certificado",
"CertificateValidation": "Validação de certificado",
"CertificateValidationHelpText": "Alterar o quão estrita é a validação da certificação HTTPS",
"ChangeHasNotBeenSavedYet": "A alteração ainda não foi salva",
"Clear": "Limpar",
"ClearHistory": "Limpar histórico",
"ClearHistoryMessageText": "Tem certeza de que deseja limpar o histórico do Prowlarr?",
"ClientPriority": "Prioridade do Cliente",
"CloneProfile": "Clonar Perfil",
"ClientPriority": "Prioridade do cliente",
"CloneProfile": "Clonar perfil",
"Close": "Fechar",
"CloseCurrentModal": "Fechar modal atual",
"Columns": "Colunas",
"Component": "Componente",
"Connect": "Notificações",
"ConnectSettings": "Configurações de Conexão",
"ConnectSettings": "Configurações de conexão",
"ConnectSettingsSummary": "Notificações e scripts personalizados",
"ConnectionLost": "Conexão Perdida",
"ConnectionLost": "Conexão perdida",
"Connections": "Conexões",
"CouldNotConnectSignalR": "Não é possível conectar ao SignalR, a interface não atualizará",
"Custom": "Personalizar",
"CustomFilters": "Filtros Personalizados",
"Custom": "Personalizado",
"CustomFilters": "Filtros personalizados",
"DBMigration": "Migração de banco de dados",
"Database": "Banco de dados",
"Date": "Data",
@@ -104,11 +104,11 @@
"DeleteBackup": "Excluir Backup",
"DeleteBackupMessageText": "Tem certeza de que deseja excluir o backup '{name}'?",
"DeleteClientCategory": "Excluir Categoria de Cliente de Download",
"DeleteDownloadClient": "Excluir Cliente de Download",
"DeleteDownloadClient": "Excluir cliente de download",
"DeleteDownloadClientMessageText": "Tem certeza de que deseja excluir o cliente de download '{name}'?",
"DeleteIndexerProxy": "Apagar Proxy do Indexador",
"DeleteIndexerProxyMessageText": "Tem certeza de que deseja excluir o proxy do indexador '{name}'?",
"DeleteNotification": "Excluir Notificação",
"DeleteNotification": "Excluir notificação",
"DeleteNotificationMessageText": "Tem certeza de que deseja excluir a notificação '{name}'?",
"DeleteTag": "Excluir tag",
"DeleteTagMessageText": "Tem certeza de que deseja excluir a tag '{label}'?",
@@ -120,9 +120,9 @@
"Discord": "Discord",
"Docker": "Docker",
"Donations": "Doações",
"DownloadClient": "Cliente de Download",
"DownloadClient": "Cliente de download",
"DownloadClientCategory": "Categoria de Download do Cliente",
"DownloadClientSettings": "Configurações do Cliente de Download",
"DownloadClientSettings": "Configurações do cliente de download",
"DownloadClientStatusCheckAllClientMessage": "Todos os clientes de download estão indisponíveis devido a falhas",
"DownloadClientStatusCheckSingleClientMessage": "Clientes de download indisponíveis devido a falhas: {0}",
"DownloadClients": "Clientes de download",
@@ -137,7 +137,7 @@
"EnableAutomaticSearchHelpText": "Será usado ao realizar pesquisas automáticas pela interface ou pelo Prowlarr",
"EnableIndexer": "Habilitar indexador",
"EnableInteractiveSearch": "Ativar pesquisa interativa",
"EnableInteractiveSearchHelpText": "Será usado quando a pesquisa interativa for usada",
"EnableInteractiveSearchHelpText": "Será usado com a pesquisa interativa",
"EnableRss": "Habilitar RSS",
"EnableRssHelpText": "Habilitar feed RSS para o indexador",
"EnableSSL": "Habilitar SSL",
@@ -149,7 +149,7 @@
"Episode": "Episódio",
"Error": "Erro",
"ErrorLoadingContents": "Erro ao carregar o conteúdo",
"EventType": "Tipo de Evento",
"EventType": "Tipo de evento",
"Events": "Eventos",
"Exception": "Exceção",
"ExistingTag": "Etiqueta existente",
@@ -175,7 +175,7 @@
"Grabs": "Obtenções",
"Health": "Saúde",
"HealthNoIssues": "Nenhum problema com sua configuração",
"HideAdvanced": "Ocultar Avançado",
"HideAdvanced": "Ocultar opções avançadas",
"History": "Histórico",
"HistoryCleanup": "Histórico de Limpeza",
"HistoryCleanupDaysHelpText": "Defina como 0 para desabilitar a limpeza automática",
@@ -183,9 +183,9 @@
"HistoryDetails": "Detalhes do histórico",
"HomePage": "Página inicial",
"Host": "Host",
"Hostname": "Hostname",
"Hostname": "Nome do host",
"Id": "ID",
"IgnoredAddresses": "Endereços Ignorados",
"IgnoredAddresses": "Endereços ignorados",
"IllRestartLater": "Reiniciarei mais tarde",
"IncludeHealthWarningsHelpText": "Incluir avisos de integridade",
"IncludeManualGrabsHelpText": "Incluir Capturas Manuais feitas no Prowlarr",
@@ -203,7 +203,7 @@
"IndexerName": "Nome do Indexador",
"IndexerNoDefCheckMessage": "Os indexadores não têm definição e não funcionarão: {0}. Por favor, remova e (ou) adicione novamente ao Prowlarr",
"IndexerObsoleteCheckMessage": "Os seguintes indexadores são obsoletos ou foram atualizados: {0}. Remova-os e/ou adicione-os novamente ao Prowlarr",
"IndexerPriority": "Prioridade do Indexador",
"IndexerPriority": "Prioridade do indexador",
"IndexerPriorityHelpText": "Prioridade do Indexador de 1 (Mais Alta) a 50 (Mais Baixa). Padrão: 25.",
"IndexerProxies": "Proxies do Indexador",
"IndexerProxy": "Proxy do Indexador",
@@ -392,7 +392,7 @@
"SettingsShowRelativeDatesHelpText": "Mostrar datas absolutas ou relativas (Hoje, Ontem, etc.)",
"SettingsSqlLoggingHelpText": "Registrar todas as consultas de SQL do Prowlarr",
"SettingsTimeFormat": "Formato de hora",
"ShowAdvanced": "Mostrar Avançado",
"ShowAdvanced": "Mostrar opções avançadas",
"ShowSearch": "Mostrar pesquisa",
"ShowSearchHelpText": "Mostrar botão de pesquisa ao passar o mouse",
"Shutdown": "Desligar",
@@ -456,13 +456,13 @@
"URLBase": "URL base",
"UnableToAddANewAppProfilePleaseTryAgain": "Não foi possível adicionar um novo perfil de aplicativo, tente novamente.",
"UnableToAddANewApplicationPleaseTryAgain": "Não foi possível adicionar um novo aplicativo, tente novamente.",
"UnableToAddANewDownloadClientPleaseTryAgain": "Não foi possível adicionar um novo cliente de download, tente novamente.",
"UnableToAddANewIndexerPleaseTryAgain": "Não foi possível adicionar um novo indexador, tente novamente.",
"UnableToAddANewDownloadClientPleaseTryAgain": "Não foi possível adicionar um novo cliente de download. Tente novamente.",
"UnableToAddANewIndexerPleaseTryAgain": "Não foi possível adicionar um novo indexador. Tente novamente.",
"UnableToAddANewIndexerProxyPleaseTryAgain": "Não foi possível adicionar um novo proxy indexador, tente novamente.",
"UnableToAddANewNotificationPleaseTryAgain": "Não foi possível adicionar uma nova notificação, tente novamente.",
"UnableToAddANewNotificationPleaseTryAgain": "Não foi possível adicionar uma nova notificação. Tente novamente.",
"UnableToLoadAppProfiles": "Não foi possível carregar os perfis de aplicativos",
"UnableToLoadApplicationList": "Não é possível carregar a lista de aplicativos",
"UnableToLoadBackups": "Não é possível carregar backups",
"UnableToLoadBackups": "Não foi possível carregar os backups",
"UnableToLoadDevelopmentSettings": "Não foi possível carregar as configurações de desenvolvimento",
"UnableToLoadDownloadClients": "Não foi possível carregar os clientes de download",
"UnableToLoadGeneralSettings": "Não foi possível carregar as configurações gerais",
@@ -556,39 +556,39 @@
"days": "dias",
"IndexerDownloadClientHealthCheckMessage": "Indexadores com clientes de download inválidos: {0}.",
"DeleteAppProfileMessageText": "Tem certeza de que deseja excluir o perfil do aplicativo '{name}'?",
"AppUpdated": "{appName} Atualizado",
"AppUpdated": "{appName} atualizado",
"AppUpdatedVersion": "{appName} foi atualizado para a versão `{version}`, para obter as alterações mais recentes, você precisará recarregar {appName}",
"ConnectionLostToBackend": "{appName} perdeu sua conexão com o backend e precisará ser recarregado para restaurar a funcionalidade.",
"ConnectionLostToBackend": "{appName} perdeu a conexão com o backend e precisará ser recarregado para restaurar a funcionalidade.",
"RecentChanges": "Mudanças Recentes",
"WhatsNew": "O que há de novo?",
"ConnectionLostReconnect": "{appName} tentará se conectar automaticamente ou você pode clicar em recarregar abaixo.",
"AddApplicationImplementation": "Adicionar Aplicativo - {implementationName}",
"AddConnectionImplementation": "Adicionar Conexão - {implementationName}",
"AddConnectionImplementation": "Adicionar conexão - {implementationName}",
"EditApplicationImplementation": "Editar Aplicativo - {implementationName}",
"AddCategory": "Adicionar Categoria",
"AddConnection": "Adicionar Conexão",
"AddDownloadClientImplementation": "Adicionar Cliente de Download - {implementationName}",
"AddIndexerImplementation": "Adicionar Indexador - {implementationName}",
"AddConnection": "Adicionar conexão",
"AddDownloadClientImplementation": "Adicionar cliente de download - {implementationName}",
"AddIndexerImplementation": "Adicionar indexador - {implementationName}",
"AddIndexerProxyImplementation": "Adicionar Proxy do Indexador - {implementationName}",
"EditCategory": "Editar Categoria",
"EditConnectionImplementation": "Editar Conexão - {implementationName}",
"EditDownloadClientImplementation": "Editar Cliente de Download - {implementationName}",
"EditIndexerImplementation": "Editar Indexador - {implementationName}",
"EditConnectionImplementation": "Editar conexão - {implementationName}",
"EditDownloadClientImplementation": "Editar cliente de download - {implementationName}",
"EditIndexerImplementation": "Editar indexador - {implementationName}",
"EditIndexerProxyImplementation": "Editar Proxy do Indexador - {implementationName}",
"NotificationStatusAllClientHealthCheckMessage": "Todas as notificações estão indisponíveis devido a falhas",
"NotificationStatusSingleClientHealthCheckMessage": "Notificações indisponíveis devido a falhas: {0}",
"AuthForm": "Formulário (Página de login)",
"AuthenticationMethod": "Método de Autenticação",
"AuthForm": "Formulário (página de login)",
"AuthenticationMethod": "Método de autenticação",
"AuthenticationMethodHelpTextWarning": "Selecione um método de autenticação válido",
"AuthenticationRequiredPasswordHelpTextWarning": "Insira uma nova senha",
"AuthenticationRequiredPasswordHelpTextWarning": "Digite uma nova senha",
"AuthenticationRequiredUsernameHelpTextWarning": "Digite um novo nome de usuário",
"Clone": "Clonar",
"DefaultNameCopiedProfile": "{name} - Cópia",
"DisabledForLocalAddresses": "Desativado para Endereços Locais",
"DisabledForLocalAddresses": "Desabilitado para endereços locais",
"External": "Externo",
"None": "Vazio",
"ResetAPIKeyMessageText": "Tem certeza de que deseja redefinir sua chave de API?",
"AuthBasic": "Básico (Balão do Navegador)",
"AuthBasic": "Básico (pop-up do navegador)",
"ActiveIndexers": "Indexadores Ativos",
"ActiveApps": "Apps Ativos",
"IndexerHistoryLoadError": "Erro ao carregar o histórico do indexador",

View File

@@ -121,7 +121,7 @@
"SettingsEnableColorImpairedModeHelpText": "Stil modificat pentru a permite utilizatorilor cu deficiențe de culoare să distingă mai bine informațiile codificate prin culoare",
"SettingsLongDateFormat": "Format de dată lungă",
"SettingsShortDateFormat": "Format scurt de dată",
"SettingsTimeFormat": "Format de timp",
"SettingsTimeFormat": "Format ora",
"RSSIsNotSupportedWithThisIndexer": "RSS nu este suportat de acest indexator",
"ShowSearchHelpText": "Afișați butonul de căutare pe hover",
"UILanguageHelpText": "Limba pe care Prowlarr o va folosi pentru interfața de utilizare",
@@ -451,5 +451,6 @@
"AddDownloadClientImplementation": "Adăugați client de descărcare - {implementationName}",
"AddIndexerImplementation": "Adăugați Indexator - {implementationName}",
"AddIndexerProxyImplementation": "Adăugați proxy indexator - {implementationName}",
"Album": "Album"
"Album": "Album",
"AppUpdated": "{appName} actualizat"
}

View File

@@ -4,7 +4,7 @@
"Actions": "动作",
"Add": "添加",
"AddApplication": "添加应用程序",
"AddCustomFilter": "添加自定义过滤",
"AddCustomFilter": "添加自定义过滤",
"AddDownloadClient": "添加下载客户端",
"AddDownloadClientToProwlarr": "添加下载客户端允许 Prowlarr 在进行手动搜索时从 UI直接发送结果.",
"AddIndexer": "添加索引器",
@@ -56,7 +56,7 @@
"BackupFolderHelpText": "相对路径将在 Prowlarr 的 AppData 目录下",
"BackupIntervalHelpText": "自动备份时间间隔",
"BackupNow": "马上备份",
"BackupRetentionHelpText": "早于保留周期的自动备份将被自动清",
"BackupRetentionHelpText": "超过保留期限的自动备份将被自动清",
"Backups": "历史备份",
"BeforeUpdate": "更新前",
"BindAddress": "绑定地址",
@@ -91,7 +91,7 @@
"Connections": "连接",
"CouldNotConnectSignalR": "无法连接至SignalR不会升级UI",
"Custom": "自定义",
"CustomFilters": "自定义过滤",
"CustomFilters": "自定义过滤",
"DBMigration": "数据库迁移版本",
"Database": "数据库",
"Date": "日期",
@@ -108,7 +108,7 @@
"DeleteIndexerProxy": "删除搜刮器代理",
"DeleteIndexerProxyMessageText": "您确定要删除索引器代理“{name}”吗?",
"DeleteNotification": "删除消息推送",
"DeleteNotificationMessageText": "您确定要删除消息推送 “{name}” 吗?",
"DeleteNotificationMessageText": "您确定要删除通知“{name}”吗?",
"DeleteTag": "删除标签",
"DeleteTagMessageText": "您确定要删除标签 '{label}' 吗?",
"Description": "描述",
@@ -116,7 +116,7 @@
"DevelopmentSettings": "开发设置",
"Disabled": "禁用",
"DisabledUntil": "禁用Until",
"Discord": "冲突",
"Discord": "分歧",
"Docker": "Docker",
"Donations": "赞助",
"DownloadClient": "下载客户端",
@@ -147,7 +147,7 @@
"Ended": "已完结",
"Episode": "集",
"Error": "错误",
"ErrorLoadingContents": "读取内容错误",
"ErrorLoadingContents": "加载内容出错",
"EventType": "事件类型",
"Events": "事件",
"Exception": "例外",
@@ -224,7 +224,7 @@
"InstanceNameHelpText": "选项卡及日志应用名称",
"InteractiveSearch": "手动搜索",
"Interval": "间隔",
"KeyboardShortcuts": "键盘快捷键",
"KeyboardShortcuts": "快捷键",
"Label": "标签",
"Language": "语言",
"LastDuration": "上一次用时",
@@ -250,7 +250,7 @@
"MinimumSeeders": "最少播种量",
"MinimumSeedersHelpText": "用于索引器抓取的应用程序所需的最小播种量",
"Mode": "模式",
"More": "更多",
"More": "更多",
"MoreInfo": "更多信息",
"MovieIndexScrollBottom": "影片索引:滚动到底部",
"MovieIndexScrollTop": "影片索引:滚动到顶部",
@@ -334,7 +334,7 @@
"ReleaseStatus": "发布状态",
"Reload": "重新加载",
"Remove": "移除",
"RemoveFilter": "移除过滤条件",
"RemoveFilter": "移除过滤",
"RemovedFromTaskQueue": "从任务队列中移除",
"RemovingTag": "移除标签",
"RepeatSearch": "再次搜索",
@@ -480,7 +480,7 @@
"UserAgentProvidedByTheAppThatCalledTheAPI": "由调用API的应用程序提供的User-Agent",
"Username": "用户名",
"Version": "版本",
"View": "视图",
"View": "查看",
"VipExpiration": "VIP过期",
"Warn": "警告",
"Website": "‎网站‎",
@@ -557,16 +557,16 @@
"AddConnection": "添加连接",
"AddIndexerImplementation": "添加索引器 - {implementationName}",
"AddCategory": "添加目录",
"AddConnectionImplementation": "添加集合 - {implementationName}",
"AddDownloadClientImplementation": "添加下载客户端 - {implementationName}",
"AddConnectionImplementation": "添加连接- {implementationName}",
"AddDownloadClientImplementation": "添加下载客户端- {implementationName}",
"AddIndexerProxyImplementation": "添加搜刮器代理-{实体名称}",
"AppUpdated": "{appName} 升级",
"EditDownloadClientImplementation": "添加下载客户端 - {implementationName}",
"EditIndexerImplementation": "添加索引器 - {implementationName}",
"EditDownloadClientImplementation": "编辑下载客户端- {implementationName}",
"EditIndexerImplementation": "编辑索引器- {implementationName}",
"EditIndexerProxyImplementation": "添加搜刮器代理-{实体名称}",
"AppUpdatedVersion": "{appName} 已经更新到 {version} 版本,重新加载 {appName} 使更新生效",
"EditApplicationImplementation": "添加应用-{实体名称}",
"EditConnectionImplementation": "添加集合 - {implementationName}",
"EditConnectionImplementation": "编辑连接- {implementationName}",
"NotificationStatusAllClientHealthCheckMessage": "由于故障所用应用程序都不可用",
"NotificationStatusSingleClientHealthCheckMessage": "由于故障应用程序不可用",
"AuthBasic": "基础(浏览器弹出对话框)",

View File

@@ -7,7 +7,7 @@ using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Profiles
{
public interface IProfileService
public interface IAppProfileService
{
AppSyncProfile Add(AppSyncProfile profile);
void Update(AppSyncProfile profile);
@@ -18,7 +18,7 @@ namespace NzbDrone.Core.Profiles
AppSyncProfile GetDefaultProfile(string name);
}
public class AppSyncProfileService : IProfileService,
public class AppSyncProfileService : IAppProfileService,
IHandle<ApplicationStartedEvent>
{
private readonly IAppProfileRepository _profileRepository;

View File

@@ -0,0 +1,22 @@
using FluentValidation.Validators;
using NzbDrone.Core.Profiles;
namespace NzbDrone.Core.Validation
{
public class AppProfileExistsValidator : PropertyValidator
{
private readonly IAppProfileService _appProfileService;
public AppProfileExistsValidator(IAppProfileService appProfileService)
{
_appProfileService = appProfileService;
}
protected override string GetDefaultMessageTemplate() => "App Profile does not exist";
protected override bool IsValid(PropertyValidatorContext context)
{
return context?.PropertyValue == null || _appProfileService.Exists((int)context.PropertyValue);
}
}
}

View File

@@ -0,0 +1,27 @@
using FluentValidation.Validators;
using NzbDrone.Core.Download;
namespace NzbDrone.Core.Validation
{
public class DownloadClientExistsValidator : PropertyValidator
{
private readonly IDownloadClientFactory _downloadClientFactory;
public DownloadClientExistsValidator(IDownloadClientFactory downloadClientFactory)
{
_downloadClientFactory = downloadClientFactory;
}
protected override string GetDefaultMessageTemplate() => "Download Client does not exist";
protected override bool IsValid(PropertyValidatorContext context)
{
if (context?.PropertyValue == null || (int)context.PropertyValue == 0)
{
return true;
}
return _downloadClientFactory.Exists((int)context.PropertyValue);
}
}
}

View File

@@ -158,6 +158,8 @@ namespace NzbDrone.Host
{
{ apikeyQuery, Array.Empty<string>() }
});
c.DescribeAllParametersInCamelCase();
});
services

View File

@@ -72,7 +72,7 @@ namespace NzbDrone.Integration.Test.Client
{
// cache control header gets reordered on net core
var headers = response.Headers;
((string)headers.Single(c => c.Name == "Cache-Control").Value).Split(',').Select(x => x.Trim())
((string)headers.SingleOrDefault(c => c.Name == "Cache-Control")?.Value ?? string.Empty).Split(',').Select(x => x.Trim())
.Should().BeEquivalentTo("no-store, no-cache".Split(',').Select(x => x.Trim()));
headers.Single(c => c.Name == "Pragma").Value.Should().Be("no-cache");
headers.Single(c => c.Name == "Expires").Value.Should().Be("-1");
@@ -93,7 +93,7 @@ namespace NzbDrone.Integration.Test.Client
return Get<List<TResource>>(request);
}
public PagingResource<TResource> GetPaged(int pageNumber, int pageSize, string sortKey, string sortDir, string filterKey = null, string filterValue = null)
public PagingResource<TResource> GetPaged(int pageNumber, int pageSize, string sortKey, string sortDir, string filterKey = null, object filterValue = null)
{
var request = BuildRequest();
request.AddParameter("page", pageNumber);
@@ -103,8 +103,7 @@ namespace NzbDrone.Integration.Test.Client
if (filterKey != null && filterValue != null)
{
request.AddParameter("filterKey", filterKey);
request.AddParameter("filterValue", filterValue);
request.AddParameter(filterKey, filterValue);
}
return Get<PagingResource<TResource>>(request);

View File

@@ -47,7 +47,7 @@ namespace NzbDrone.Integration.Test
protected override void InitializeTestTarget()
{
WaitForCompletion(() => Tasks.All().SelectList(x => x.TaskName).Contains("CheckHealth"));
WaitForCompletion(() => Tasks.All().SelectList(x => x.TaskName).Contains("CheckHealth"), 20000);
Indexers.Post(new Prowlarr.Api.V1.Indexers.IndexerResource
{
@@ -56,6 +56,7 @@ namespace NzbDrone.Integration.Test
Implementation = nameof(FileList),
Name = "NewznabTest",
Protocol = Core.Indexers.DownloadProtocol.Usenet,
AppProfileId = 1,
Fields = SchemaBuilder.ToSchema(new FileListSettings())
});

View File

@@ -0,0 +1,21 @@
using System;
using NzbDrone.Common.TPL;
namespace NzbDrone.Test.Common
{
public class MockDebouncer : Debouncer
{
public MockDebouncer(Action action, TimeSpan debounceDuration)
: base(action, debounceDuration)
{
}
public override void Execute()
{
lock (_timer)
{
_action();
}
}
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.History;
using Prowlarr.Http;
@@ -21,30 +22,25 @@ namespace Prowlarr.Api.V1.History
[HttpGet]
[Produces("application/json")]
public PagingResource<HistoryResource> GetHistory()
public PagingResource<HistoryResource> GetHistory([FromQuery] PagingRequestResource paging, int? eventType, bool? successful, string downloadId)
{
var pagingResource = Request.ReadPagingResourceFromRequest<HistoryResource>();
var pagingResource = new PagingResource<HistoryResource>(paging);
var pagingSpec = pagingResource.MapToPagingSpec<HistoryResource, NzbDrone.Core.History.History>("date", SortDirection.Descending);
var eventTypeFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "eventType");
var successfulFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "successful");
var downloadIdFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "downloadId");
if (eventTypeFilter != null)
if (eventType.HasValue)
{
var filterValue = (HistoryEventType)Convert.ToInt32(eventTypeFilter.Value);
var filterValue = (HistoryEventType)eventType.Value;
pagingSpec.FilterExpressions.Add(v => v.EventType == filterValue);
}
if (successfulFilter != null)
if (successful.HasValue)
{
var filterValue = bool.Parse(successfulFilter.Value);
var filterValue = successful.Value;
pagingSpec.FilterExpressions.Add(v => v.Successful == filterValue);
}
if (downloadIdFilter != null)
if (downloadId.IsNotNullOrWhiteSpace())
{
var downloadId = downloadIdFilter.Value;
pagingSpec.FilterExpressions.Add(h => h.DownloadId == downloadId);
}

View File

@@ -1,4 +1,5 @@
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Validation;
using Prowlarr.Http;
namespace Prowlarr.Api.V1.Indexers
@@ -6,9 +7,17 @@ namespace Prowlarr.Api.V1.Indexers
[V1ApiController]
public class IndexerController : ProviderControllerBase<IndexerResource, IndexerBulkResource, IIndexer, IndexerDefinition>
{
public IndexerController(IndexerFactory indexerFactory, IndexerResourceMapper resourceMapper, IndexerBulkResourceMapper bulkResourceMapper)
public IndexerController(IndexerFactory indexerFactory,
IndexerResourceMapper resourceMapper,
IndexerBulkResourceMapper bulkResourceMapper,
AppProfileExistsValidator appProfileExistsValidator,
DownloadClientExistsValidator downloadClientExistsValidator)
: base(indexerFactory, "indexer", resourceMapper, bulkResourceMapper)
{
Http.Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.AppProfileId));
SharedValidator.RuleFor(c => c.AppProfileId).SetValidator(appProfileExistsValidator);
SharedValidator.RuleFor(c => c.DownloadClientId).SetValidator(downloadClientExistsValidator);
}
}
}

View File

@@ -1,5 +1,5 @@
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Instrumentation;
using Prowlarr.Http;
using Prowlarr.Http.Extensions;
@@ -18,9 +18,9 @@ namespace Prowlarr.Api.V1.Logs
[HttpGet]
[Produces("application/json")]
public PagingResource<LogResource> GetLogs()
public PagingResource<LogResource> GetLogs([FromQuery] PagingRequestResource paging, string level)
{
var pagingResource = Request.ReadPagingResourceFromRequest<LogResource>();
var pagingResource = new PagingResource<LogResource>(paging);
var pageSpec = pagingResource.MapToPagingSpec<LogResource, Log>();
if (pageSpec.SortKey == "time")
@@ -28,11 +28,9 @@ namespace Prowlarr.Api.V1.Logs
pageSpec.SortKey = "id";
}
var levelFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "level");
if (levelFilter != null)
if (level.IsNotNullOrWhiteSpace())
{
switch (levelFilter.Value)
switch (level)
{
case "fatal":
pageSpec.FilterExpressions.Add(h => h.Level == "Fatal");

View File

@@ -11,11 +11,12 @@ namespace Prowlarr.Api.V1.Profiles.App
[V1ApiController]
public class AppProfileController : RestController<AppProfileResource>
{
private readonly IProfileService _profileService;
private readonly IAppProfileService _appProfileService;
public AppProfileController(IProfileService profileService)
public AppProfileController(IAppProfileService appProfileService)
{
_profileService = profileService;
_appProfileService = appProfileService;
SharedValidator.RuleFor(c => c.Name).NotEmpty();
}
@@ -25,7 +26,7 @@ namespace Prowlarr.Api.V1.Profiles.App
public ActionResult<AppProfileResource> Create(AppProfileResource resource)
{
var model = resource.ToModel();
model = _profileService.Add(model);
model = _appProfileService.Add(model);
return Created(model.Id);
}
@@ -33,7 +34,7 @@ namespace Prowlarr.Api.V1.Profiles.App
[Produces("application/json")]
public object DeleteProfile(int id)
{
_profileService.Delete(id);
_appProfileService.Delete(id);
return new { };
}
@@ -44,7 +45,7 @@ namespace Prowlarr.Api.V1.Profiles.App
{
var model = resource.ToModel();
_profileService.Update(model);
_appProfileService.Update(model);
return Accepted(model.Id);
}
@@ -55,14 +56,23 @@ namespace Prowlarr.Api.V1.Profiles.App
[ProducesResponseType(500)]
public override AppProfileResource GetResourceById(int id)
{
return _profileService.Get(id).ToResource();
return _appProfileService.Get(id).ToResource();
}
[HttpGet]
[Produces("application/json")]
public List<AppProfileResource> GetAll()
{
return _profileService.All().ToResource();
return _appProfileService.All().ToResource();
}
[HttpGet("schema")]
[Produces("application/json")]
public AppProfileResource GetTemplates()
{
var profile = _appProfileService.GetDefaultProfile(string.Empty);
return profile.ToResource();
}
}
}

View File

@@ -1,26 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Core.Profiles;
using Prowlarr.Http;
namespace Prowlarr.Api.V1.Profiles.App
{
[V1ApiController("appprofile/schema")]
public class QualityProfileSchemaController : Controller
{
private readonly IProfileService _profileService;
public QualityProfileSchemaController(IProfileService profileService)
{
_profileService = profileService;
}
[HttpGet]
[Produces("application/json")]
public AppProfileResource GetSchema()
{
var qualityProfile = _profileService.GetDefaultProfile(string.Empty);
return qualityProfile.ToResource();
}
}
}

View File

@@ -483,6 +483,25 @@
}
}
},
"/api/v1/appprofile/schema": {
"get": {
"tags": [
"AppProfile"
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AppProfileResource"
}
}
}
}
}
}
},
"/login": {
"post": {
"tags": [
@@ -503,25 +522,25 @@
"schema": {
"type": "object",
"properties": {
"Username": {
"username": {
"type": "string"
},
"Password": {
"password": {
"type": "string"
},
"RememberMe": {
"rememberMe": {
"type": "string"
}
}
},
"encoding": {
"Username": {
"username": {
"style": "form"
},
"Password": {
"password": {
"style": "form"
},
"RememberMe": {
"rememberMe": {
"style": "form"
}
}
@@ -1412,6 +1431,62 @@
"tags": [
"History"
],
"parameters": [
{
"name": "page",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 1
}
},
{
"name": "pageSize",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 10
}
},
{
"name": "sortKey",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "sortDirection",
"in": "query",
"schema": {
"$ref": "#/components/schemas/SortDirection"
}
},
{
"name": "eventType",
"in": "query",
"schema": {
"type": "integer",
"format": "int32"
}
},
{
"name": "successful",
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "downloadId",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Success",
@@ -2263,6 +2338,47 @@
"tags": [
"Log"
],
"parameters": [
{
"name": "page",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 1
}
},
{
"name": "pageSize",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 10
}
},
{
"name": "sortKey",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "sortDirection",
"in": "query",
"schema": {
"$ref": "#/components/schemas/SortDirection"
}
},
{
"name": "level",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Success",
@@ -3183,25 +3299,6 @@
}
}
},
"/api/v1/appprofile/schema": {
"get": {
"tags": [
"QualityProfileSchema"
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AppProfileResource"
}
}
}
}
}
}
},
"/api/v1/search": {
"post": {
"tags": [
@@ -3235,21 +3332,21 @@
],
"parameters": [
{
"name": "Query",
"name": "query",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "Type",
"name": "type",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "IndexerIds",
"name": "indexerIds",
"in": "query",
"schema": {
"type": "array",
@@ -3260,7 +3357,7 @@
}
},
{
"name": "Categories",
"name": "categories",
"in": "query",
"schema": {
"type": "array",
@@ -3271,7 +3368,7 @@
}
},
{
"name": "Limit",
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
@@ -3279,7 +3376,7 @@
}
},
{
"name": "Offset",
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
@@ -4626,13 +4723,6 @@
"sortDirection": {
"$ref": "#/components/schemas/SortDirection"
},
"filters": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PagingResourceFilter"
},
"nullable": true
},
"totalRecords": {
"type": "integer",
"format": "int32"
@@ -5423,13 +5513,6 @@
"sortDirection": {
"$ref": "#/components/schemas/SortDirection"
},
"filters": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PagingResourceFilter"
},
"nullable": true
},
"totalRecords": {
"type": "integer",
"format": "int32"
@@ -5564,20 +5647,6 @@
},
"additionalProperties": false
},
"PagingResourceFilter": {
"type": "object",
"properties": {
"key": {
"type": "string",
"nullable": true
},
"value": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
},
"PingResource": {
"type": "object",
"properties": {

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Microsoft.AspNetCore.Http;
@@ -27,69 +26,6 @@ namespace Prowlarr.Http.Extensions
return defaultValue;
}
public static PagingResource<TResource> ReadPagingResourceFromRequest<TResource>(this HttpRequest request)
{
if (!int.TryParse(request.Query["PageSize"].ToString(), out var pageSize))
{
pageSize = 10;
}
if (!int.TryParse(request.Query["Page"].ToString(), out var page))
{
page = 1;
}
var pagingResource = new PagingResource<TResource>
{
PageSize = pageSize,
Page = page,
Filters = new List<PagingResourceFilter>()
};
if (request.Query["SortKey"].Any())
{
var sortKey = request.Query["SortKey"].ToString();
pagingResource.SortKey = sortKey;
if (request.Query["SortDirection"].Any())
{
pagingResource.SortDirection = request.Query["SortDirection"].ToString()
.Equals("ascending", StringComparison.InvariantCultureIgnoreCase)
? SortDirection.Ascending
: SortDirection.Descending;
}
}
// For backwards compatibility with v2
if (request.Query["FilterKey"].Any())
{
var filter = new PagingResourceFilter
{
Key = request.Query["FilterKey"].ToString()
};
if (request.Query["FilterValue"].Any())
{
filter.Value = request.Query["FilterValue"].ToString();
}
pagingResource.Filters.Add(filter);
}
// v3 uses filters in key=value format
foreach (var pair in request.Query)
{
pagingResource.Filters.Add(new PagingResourceFilter
{
Key = pair.Key,
Value = pair.Value.ToString()
});
}
return pagingResource;
}
public static PagingResource<TResource> ApplyToPage<TResource, TModel>(this PagingSpec<TModel> pagingSpec, Func<PagingSpec<TModel>, PagingSpec<TModel>> function, Converter<TModel, TResource> mapper)
{
pagingSpec = function(pagingSpec);

View File

@@ -1,17 +1,39 @@
using System.Collections.Generic;
using System.ComponentModel;
using NzbDrone.Core.Datastore;
namespace Prowlarr.Http
{
public class PagingRequestResource
{
[DefaultValue(1)]
public int? Page { get; set; }
[DefaultValue(10)]
public int? PageSize { get; set; }
public string SortKey { get; set; }
public SortDirection? SortDirection { get; set; }
}
public class PagingResource<TResource>
{
public int Page { get; set; }
public int PageSize { get; set; }
public string SortKey { get; set; }
public SortDirection SortDirection { get; set; }
public List<PagingResourceFilter> Filters { get; set; }
public int TotalRecords { get; set; }
public List<TResource> Records { get; set; }
public PagingResource()
{
}
public PagingResource(PagingRequestResource requestResource)
{
Page = requestResource.Page ?? 1;
PageSize = requestResource.PageSize ?? 10;
SortKey = requestResource.SortKey;
SortDirection = requestResource.SortDirection ?? SortDirection.Descending;
}
}
public static class PagingResourceMapper

View File

@@ -1,8 +0,0 @@
namespace Prowlarr.Http
{
public class PagingResourceFilter
{
public string Key { get; set; }
public string Value { get; set; }
}
}

View File

@@ -2557,10 +2557,10 @@ core-js-compat@^3.31.0:
dependencies:
browserslist "^4.21.10"
core-js@3.32.1:
version "3.32.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.32.1.tgz#a7d8736a3ed9dd05940c3c4ff32c591bb735be77"
integrity sha512-lqufgNn9NLnESg5mQeYsxQP5w7wrViSj0jr/kv6ECQiByzQkrn1MKvV0L3acttpDqfQrHLwr2KCMgX5b8X+lyQ==
core-js@3.33.0:
version "3.33.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.33.0.tgz#70366dbf737134761edb017990cf5ce6c6369c40"
integrity sha512-HoZr92+ZjFEKar5HS6MC776gYslNOKHt75mEBKWKnPeFDpZ6nH5OeF3S6HFT1mUAUZKrzkez05VboaX8myjSuw==
core-js@^2.4.0:
version "2.6.12"
@@ -5215,10 +5215,10 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@8.4.23:
version "8.4.23"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab"
integrity sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==
postcss@8.4.31:
version "8.4.31"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"
@@ -6865,10 +6865,10 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@5.88.1:
version "5.88.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.1.tgz#21eba01e81bd5edff1968aea726e2fbfd557d3f8"
integrity sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==
webpack@5.89.0:
version "5.89.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.89.0.tgz#56b8bf9a34356e93a6625770006490bf3a7f32dc"
integrity sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^1.0.0"