mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-04-17 21:44:48 -04:00
Compare commits
71 Commits
v1.9.1.398
...
v1.10.1.40
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71a19efd9a | ||
|
|
2c6c0fcc81 | ||
|
|
203e2dbb10 | ||
|
|
6169fc2fa3 | ||
|
|
768ce14afb | ||
|
|
31d32e8c30 | ||
|
|
7a61761b2b | ||
|
|
3963807c96 | ||
|
|
e0f6726a3d | ||
|
|
dd25bff3d6 | ||
|
|
d834c4292e | ||
|
|
7e8272ec2b | ||
|
|
62548f32fe | ||
|
|
db9f061564 | ||
|
|
b37d8799a0 | ||
|
|
4366530409 | ||
|
|
c7959f735e | ||
|
|
be3ee00e1f | ||
|
|
dace1982d6 | ||
|
|
980bd35f95 | ||
|
|
4b2f81bee8 | ||
|
|
30eb481c65 | ||
|
|
29f1c36f54 | ||
|
|
f1c01343bf | ||
|
|
bae79b22ad | ||
|
|
229d879f86 | ||
|
|
d1cee950a4 | ||
|
|
7e32b54547 | ||
|
|
b1f7d30021 | ||
|
|
c41a7e0ccc | ||
|
|
42c533386b | ||
|
|
bdae7a2cdc | ||
|
|
5e8d3542f4 | ||
|
|
d9d2aa8493 | ||
|
|
09bf1500d6 | ||
|
|
34464160cb | ||
|
|
bada5fe309 | ||
|
|
b088febbc4 | ||
|
|
1a307b8e21 | ||
|
|
32db2af0ea | ||
|
|
e602862102 | ||
|
|
bd5336e4c4 | ||
|
|
c664eaa9b5 | ||
|
|
b7e57f0c08 | ||
|
|
c06bf0e4ea | ||
|
|
c6db30c35a | ||
|
|
75c30dd318 | ||
|
|
6e7bf55dbd | ||
|
|
eb642dd2f9 | ||
|
|
19a196e2c7 | ||
|
|
93ec6cf89b | ||
|
|
52c6b56a4c | ||
|
|
82688d8a55 | ||
|
|
c81cbc801a | ||
|
|
993d189c61 | ||
|
|
1901af5a51 | ||
|
|
c1b399be39 | ||
|
|
2100e96570 | ||
|
|
3ff144421d | ||
|
|
f37ccba3f9 | ||
|
|
181cb2e0fe | ||
|
|
93c81bb7d3 | ||
|
|
7dd289b5f9 | ||
|
|
09cef8cf94 | ||
|
|
ca08c818e6 | ||
|
|
3e95bc4056 | ||
|
|
e241112915 | ||
|
|
0d98c12fa2 | ||
|
|
a0bcf5c9ae | ||
|
|
e318a47b3a | ||
|
|
b8df720c6c |
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://dev.azure.com/Prowlarr/Prowlarr/_build/latest?definitionId=1&branchName=develop)
|
||||
[](https://translate.servarr.com/engage/prowlarr/?utm_source=widget)
|
||||
[](https://wiki.servarr.com/prowlarr/installation#docker)
|
||||
[](https://wiki.servarr.com/prowlarr/installation/docker)
|
||||

|
||||
[](#backers)
|
||||
[](#sponsors)
|
||||
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '1.9.1'
|
||||
majorVersion: '1.10.1'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -24,6 +24,7 @@ function PageHeaderActionsMenu(props) {
|
||||
<MenuButton className={styles.menuButton} aria-label="Menu Button">
|
||||
<Icon
|
||||
name={icons.INTERACTIVE}
|
||||
title={translate('Menu')}
|
||||
/>
|
||||
</MenuButton>
|
||||
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
75
frontend/src/Indexer/Add/SelectIndexerRow.tsx
Normal file
75
frontend/src/Indexer/Add/SelectIndexerRow.tsx
Normal 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;
|
||||
@@ -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);
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
36
frontend/src/Search/Table/CategoryLabel.tsx
Normal file
36
frontend/src/Search/Table/CategoryLabel.tsx
Normal 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;
|
||||
@@ -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',
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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"",")]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NzbDrone.Common.Serializer;
|
||||
|
||||
public class BooleanConverter : JsonConverter<bool>
|
||||
{
|
||||
public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return reader.TokenType switch
|
||||
{
|
||||
JsonTokenType.True => true,
|
||||
JsonTokenType.False => false,
|
||||
JsonTokenType.Number => reader.GetInt64() switch
|
||||
{
|
||||
1 => true,
|
||||
0 => false,
|
||||
_ => throw new JsonException()
|
||||
},
|
||||
_ => throw new JsonException()
|
||||
};
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteBooleanValue(value);
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ namespace NzbDrone.Common.Serializer
|
||||
serializerSettings.Converters.Add(new STJTimeSpanConverter());
|
||||
serializerSettings.Converters.Add(new STJUtcConverter());
|
||||
serializerSettings.Converters.Add(new DictionaryStringObjectConverter());
|
||||
serializerSettings.Converters.Add(new BooleanConverter());
|
||||
}
|
||||
|
||||
public static T Deserialize<T>(string json)
|
||||
|
||||
17
src/NzbDrone.Common/TPL/DebounceManager.cs
Normal file
17
src/NzbDrone.Common/TPL/DebounceManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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/",
|
||||
|
||||
@@ -6,9 +6,8 @@ using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.PassThePopcorn;
|
||||
using NzbDrone.Core.Indexers.Definitions.PassThePopcorn;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
@@ -21,26 +20,22 @@ namespace NzbDrone.Core.Test.IndexerTests.PTPTests
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Subject.Definition = new IndexerDefinition()
|
||||
Subject.Definition = new IndexerDefinition
|
||||
{
|
||||
Name = "PTP",
|
||||
Settings = new PassThePopcornSettings() { APIUser = "asdf", APIKey = "sad" }
|
||||
Settings = new PassThePopcornSettings
|
||||
{
|
||||
APIUser = "asdf",
|
||||
APIKey = "sad"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[TestCase("Files/Indexers/PTP/imdbsearch.json")]
|
||||
public async Task should_parse_feed_from_PTP(string fileName)
|
||||
{
|
||||
var authResponse = new PassThePopcornAuthResponse { Result = "Ok" };
|
||||
|
||||
var authStream = new System.IO.StringWriter();
|
||||
Json.Serialize(authResponse, authStream);
|
||||
var responseJson = ReadAllText(fileName);
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Post), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader(), new CookieCollection(), authStream.ToString())));
|
||||
|
||||
Mocker.GetMock<IIndexerHttpClient>()
|
||||
.Setup(o => o.ExecuteProxiedAsync(It.Is<HttpRequest>(v => v.Method == HttpMethod.Get), Subject.Definition))
|
||||
.Returns<HttpRequest, IndexerDefinition>((r, d) => Task.FromResult(new HttpResponse(r, new HttpHeader { ContentType = HttpAccept.Json.Value }, new CookieCollection(), responseJson)));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
GetNabElement("files", r.Files, protocol),
|
||||
GetNabElement("grabs", r.Grabs, protocol),
|
||||
GetNabElement("peers", t.Peers, protocol),
|
||||
r.Year == 0 ? null : GetNabElement("year", r.Year, protocol),
|
||||
GetNabElement("author", RemoveInvalidXMLChars(r.Author), protocol),
|
||||
GetNabElement("booktitle", RemoveInvalidXMLChars(r.BookTitle), protocol),
|
||||
GetNabElement("artist", RemoveInvalidXMLChars(r.Artist), protocol),
|
||||
|
||||
@@ -200,7 +200,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var rows = dom.QuerySelectorAll("div#content table > tbody > tr");
|
||||
foreach (var row in rows)
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
else
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var document = await parser.ParseDocumentAsync(response.Content);
|
||||
using var document = await parser.ParseDocumentAsync(response.Content);
|
||||
var errorMessage = document.QuerySelector("#content .berror .berror_c")?.TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
@@ -433,7 +433,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var torrentInfos = new List<TorrentInfo>();
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
foreach (var t in dom.QuerySelectorAll("#tabs .torrent_c > div"))
|
||||
{
|
||||
@@ -465,7 +465,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var links = dom.QuerySelectorAll(".searchitem > h3 > a[href], #dle-content > .story > .story_h > .lcol > h2 > a[href]");
|
||||
foreach (var link in links)
|
||||
|
||||
@@ -555,6 +555,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = minimumSeedTime,
|
||||
Title = fileName,
|
||||
Year = year.GetValueOrDefault(),
|
||||
InfoUrl = details.AbsoluteUri,
|
||||
Guid = guid.AbsoluteUri,
|
||||
DownloadUrl = link.AbsoluteUri,
|
||||
@@ -571,8 +572,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
};
|
||||
|
||||
releaseInfos.Add(release);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,6 +588,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = minimumSeedTime,
|
||||
Title = releaseTitle.Trim(),
|
||||
Year = year.GetValueOrDefault(),
|
||||
InfoUrl = details.AbsoluteUri,
|
||||
Guid = guid.AbsoluteUri,
|
||||
DownloadUrl = link.AbsoluteUri,
|
||||
@@ -699,7 +699,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
[FieldDefinition(8, Label = "Enable Sonarr Compatibility", Type = FieldType.Checkbox, HelpText = "Makes Prowlarr try to add Season information into Release names, without this Sonarr can't match any Seasons, but it has a lot of false positives as well")]
|
||||
public bool EnableSonarrCompatibility { get; set; }
|
||||
|
||||
[FieldDefinition(9, Label = "Use Filenames for Single Episodes", Type = FieldType.Checkbox, HelpText = "Makes Prowlarr replace AnimeBytes release names with the actual filename, this currently only works for single episode releases")]
|
||||
[FieldDefinition(9, Label = "Use Filenames for Single Episodes", Type = FieldType.Checkbox, HelpText = "Add a release using the actual filename, this currently only works for single episode releases")]
|
||||
public bool UseFilenameForSingleEpisodes { get; set; }
|
||||
|
||||
[FieldDefinition(10, Label = "Add Japanese title as a synonym", Type = FieldType.Checkbox, HelpText = "Makes Prowlarr add Japanese titles as synonyms, i.e kanji/hiragana/katakana.")]
|
||||
|
||||
@@ -275,7 +275,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var rows = dom.QuerySelectorAll("table tr");
|
||||
foreach (var (row, index) in rows.Skip(1).Select((v, i) => (v, i)))
|
||||
|
||||
@@ -253,7 +253,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var torrentInfos = new List<TorrentInfo>();
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
foreach (var t in dom.QuerySelectorAll("ul.media__tabs__nav > li > a"))
|
||||
{
|
||||
@@ -291,7 +291,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var links = dom.QuerySelectorAll("a.ads-list__item__title");
|
||||
foreach (var link in links)
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = parser.ParseDocument(response.Content);
|
||||
var errorMessage = dom.QuerySelector("form#loginform")?.TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
@@ -206,7 +206,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
using var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
var rows = doc.QuerySelectorAll("table.torrent_table > tbody > tr.torrent");
|
||||
foreach (var row in rows)
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ public class AroLol : GazelleBase<AroLolSettings>
|
||||
if (response.Content.Contains("loginform"))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = parser.ParseDocument(response.Content);
|
||||
var errorMessage = dom.QuerySelector("#loginform > .warning")?.TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
|
||||
@@ -88,7 +88,7 @@ public class AudioBookBay : TorrentIndexerBase<NoAuthTorrentBaseSettings>
|
||||
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = parser.ParseDocument(response.Content);
|
||||
|
||||
var hash = dom.QuerySelector("td:contains(\"Info Hash:\") ~ td")?.TextContent.Trim();
|
||||
if (hash == null)
|
||||
@@ -269,7 +269,7 @@ public class AudioBookBayParser : IParseIndexerResponse
|
||||
{
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var doc = ParseHtmlDocument(indexerResponse.Content);
|
||||
using var doc = ParseHtmlDocument(indexerResponse.Content);
|
||||
|
||||
var rows = doc.QuerySelectorAll("div.post:has(div[class=\"postTitle\"])");
|
||||
foreach (var row in rows)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
{
|
||||
@@ -9,34 +9,34 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
public string Download { get; set; }
|
||||
public Dictionary<string, string> Category { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "movie_tv")]
|
||||
[JsonPropertyName("movie_tv")]
|
||||
public AvistazIdInfo MovieTvinfo { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "created_at")]
|
||||
[JsonPropertyName("created_at")]
|
||||
public string CreatedAt { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "file_name")]
|
||||
[JsonPropertyName("file_name")]
|
||||
public string FileName { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "info_hash")]
|
||||
[JsonPropertyName("info_hash")]
|
||||
public string InfoHash { get; set; }
|
||||
public int? Leech { get; set; }
|
||||
public int? Completed { get; set; }
|
||||
public int? Seed { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "file_size")]
|
||||
[JsonPropertyName("file_size")]
|
||||
public long? FileSize { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "file_count")]
|
||||
[JsonPropertyName("file_count")]
|
||||
public int? FileCount { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "download_multiply")]
|
||||
[JsonPropertyName("download_multiply")]
|
||||
public double? DownloadMultiply { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "upload_multiply")]
|
||||
[JsonPropertyName("upload_multiply")]
|
||||
public double? UploadMultiply { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "video_quality")]
|
||||
[JsonPropertyName("video_quality")]
|
||||
public string VideoQuality { get; set; }
|
||||
public string Type { get; set; }
|
||||
public List<AvistazLanguage> Audio { get; set; }
|
||||
@@ -64,21 +64,10 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
public string Tmdb { get; set; }
|
||||
public string Tvdb { get; set; }
|
||||
public string Imdb { get; set; }
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "tv_episode")]
|
||||
public string TvEpisode { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "tv_season")]
|
||||
public string TVSeason { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "tv_full_season")]
|
||||
public bool TVFullSeason { get; set; }
|
||||
}
|
||||
|
||||
public class AvistazAuthResponse
|
||||
{
|
||||
public string Token { get; set; }
|
||||
public string Expiry { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
@@ -122,10 +123,11 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
.Accept(HttpAccept.Json)
|
||||
.Build();
|
||||
|
||||
var response = await _httpClient.PostAsync<AvistazAuthResponse>(authLoginRequest);
|
||||
var token = response.Resource.Token;
|
||||
var response = await ExecuteAuth(authLoginRequest);
|
||||
|
||||
return token;
|
||||
var authResponse = STJson.Deserialize<AvistazAuthResponse>(response.Content);
|
||||
|
||||
return authResponse.Token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -42,9 +43,9 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from indexer request, expected {HttpAccept.Json.Value}");
|
||||
}
|
||||
|
||||
var jsonResponse = new HttpResponse<AvistazResponse>(indexerResponse.HttpResponse);
|
||||
var jsonResponse = STJson.Deserialize<AvistazResponse>(indexerResponse.HttpResponse.Content);
|
||||
|
||||
foreach (var row in jsonResponse.Resource.Data)
|
||||
foreach (var row in jsonResponse.Data)
|
||||
{
|
||||
var details = row.Url;
|
||||
var link = row.Download;
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = parser.ParseDocument(response.Content);
|
||||
var messageEl = dom.QuerySelectorAll("#loginform");
|
||||
var messages = new List<string>();
|
||||
for (var i = 0; i < 13; i++)
|
||||
@@ -242,7 +242,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
var rows = dom.QuerySelectorAll("#torrent_table > tbody > tr.torrent");
|
||||
|
||||
foreach (var row in rows)
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = parser.ParseDocument(response.Content);
|
||||
var downloadLink = dom.QuerySelector(".download_link")?.GetAttribute("href");
|
||||
|
||||
if (downloadLink.IsNullOrWhiteSpace())
|
||||
@@ -82,7 +82,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var loginPage = await ExecuteAuth(new HttpRequest(loginUrl));
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = await parser.ParseDocumentAsync(loginPage.Content);
|
||||
using var dom = await parser.ParseDocumentAsync(loginPage.Content);
|
||||
var loginKey = dom.QuerySelector("input[name=\"loginKey\"]");
|
||||
if (loginKey != null)
|
||||
{
|
||||
@@ -102,7 +102,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var htmlParser = new HtmlParser();
|
||||
var document = await htmlParser.ParseDocumentAsync(response.Content);
|
||||
using var document = await htmlParser.ParseDocumentAsync(response.Content);
|
||||
var errorMessage = document.QuerySelector("#loginError, .error")?.TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
@@ -247,7 +247,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
var rows = dom.QuerySelectorAll(".torrents tr.torrent, .torrents tr.torrent_alt");
|
||||
var currentCategories = new List<IndexerCategory> { NewznabStandardCategory.TVAnime };
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json.Serialization;
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
@@ -19,6 +19,7 @@ using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
using static Newtonsoft.Json.Formatting;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
@@ -163,7 +164,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
Method = HttpMethod.Post
|
||||
};
|
||||
request.SetContent(body.ToJson());
|
||||
request.ContentSummary = body.ToJson(Formatting.None);
|
||||
request.ContentSummary = body.ToJson(None);
|
||||
|
||||
yield return new IndexerRequest(request);
|
||||
}
|
||||
@@ -245,16 +246,16 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
throw new IndexerAuthException("API Key invalid or not authorized");
|
||||
}
|
||||
|
||||
var jsonResponse = new HttpResponse<BeyondHDResponse>(indexerHttpResponse);
|
||||
var jsonResponse = STJson.Deserialize<BeyondHDResponse>(indexerResponse.Content);
|
||||
|
||||
if (jsonResponse.Resource.StatusCode == 0)
|
||||
if (jsonResponse.StatusCode == 0)
|
||||
{
|
||||
throw new IndexerException(indexerResponse, $"Indexer Error: {jsonResponse.Resource.StatusMessage}");
|
||||
throw new IndexerException(indexerResponse, $"Indexer Error: {jsonResponse.StatusMessage}");
|
||||
}
|
||||
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
foreach (var row in jsonResponse.Resource.Results)
|
||||
foreach (var row in jsonResponse.Results)
|
||||
{
|
||||
var details = row.InfoUrl;
|
||||
var link = row.DownloadLink;
|
||||
@@ -412,10 +413,10 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public class BeyondHDResponse
|
||||
{
|
||||
[JsonProperty(PropertyName = "status_code")]
|
||||
[JsonPropertyName("status_code")]
|
||||
public int StatusCode { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "status_message")]
|
||||
[JsonPropertyName("status_message")]
|
||||
public string StatusMessage { get; set; }
|
||||
public List<BeyondHDTorrent> Results { get; set; }
|
||||
}
|
||||
@@ -424,36 +425,42 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "info_hash")]
|
||||
[JsonPropertyName("info_hash")]
|
||||
public string InfoHash { get; set; }
|
||||
public string Category { get; set; }
|
||||
public string Type { get; set; }
|
||||
public long Size { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "times_completed")]
|
||||
[JsonPropertyName("times_completed")]
|
||||
public int Grabs { get; set; }
|
||||
public int Seeders { get; set; }
|
||||
public int Leechers { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "created_at")]
|
||||
[JsonPropertyName("created_at")]
|
||||
public string CreatedAt { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "download_url")]
|
||||
[JsonPropertyName("download_url")]
|
||||
public string DownloadLink { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "url")]
|
||||
[JsonPropertyName("url")]
|
||||
public string InfoUrl { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "imdb_id")]
|
||||
[JsonPropertyName("imdb_id")]
|
||||
public string ImdbId { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "tmdb_id")]
|
||||
[JsonPropertyName("tmdb_id")]
|
||||
public string TmdbId { get; set; }
|
||||
|
||||
public bool Freeleech { get; set; }
|
||||
|
||||
public bool Promo25 { get; set; }
|
||||
|
||||
public bool Promo50 { get; set; }
|
||||
|
||||
public bool Promo75 { get; set; }
|
||||
|
||||
public bool Limited { get; set; }
|
||||
|
||||
public bool Internal { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
using var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
var rows = doc.QuerySelectorAll("table.xMenuT > tbody > tr").Skip(1);
|
||||
foreach (var row in rows)
|
||||
{
|
||||
|
||||
@@ -185,7 +185,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
foreach (var child in dom.QuerySelectorAll("#needseed"))
|
||||
{
|
||||
child.Remove();
|
||||
|
||||
@@ -598,7 +598,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Cardigann
|
||||
}
|
||||
|
||||
var resultParser = new HtmlParser();
|
||||
var resultDocument = resultParser.ParseDocument(loginResult.Content);
|
||||
using var resultDocument = resultParser.ParseDocument(loginResult.Content);
|
||||
foreach (var error in errorBlocks)
|
||||
{
|
||||
var selection = resultDocument.QuerySelector(error.Selector);
|
||||
@@ -984,7 +984,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Cardigann
|
||||
var selectorText = ApplyGoTemplateText(selector.Selector, variables);
|
||||
var parser = new HtmlParser();
|
||||
|
||||
var resultDocument = parser.ParseDocument(response.Content);
|
||||
using var resultDocument = parser.ParseDocument(response.Content);
|
||||
|
||||
var element = resultDocument.QuerySelector(selectorText);
|
||||
if (element == null)
|
||||
@@ -1045,7 +1045,7 @@ namespace NzbDrone.Core.Indexers.Definitions.Cardigann
|
||||
if (_definition.Login.Test?.Selector != null && (response.Headers.ContentType?.Contains("text/html") ?? true))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var document = parser.ParseDocument(response.Content);
|
||||
using var document = parser.ParseDocument(response.Content);
|
||||
|
||||
var selection = document.QuerySelectorAll(_definition.Login.Test.Selector);
|
||||
if (selection.Length == 0)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,28 +1,40 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions.FileList;
|
||||
|
||||
public class FileListTorrent
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public uint Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public long Size { get; set; }
|
||||
|
||||
public int Leechers { get; set; }
|
||||
|
||||
public int Seeders { get; set; }
|
||||
[JsonProperty(PropertyName = "times_completed")]
|
||||
|
||||
[JsonPropertyName("times_completed")]
|
||||
public uint TimesCompleted { get; set; }
|
||||
public uint Comments { get; set; }
|
||||
|
||||
public uint Files { get; set; }
|
||||
[JsonProperty(PropertyName = "imdb")]
|
||||
|
||||
[JsonPropertyName("imdb")]
|
||||
public string ImdbId { get; set; }
|
||||
|
||||
public bool Internal { get; set; }
|
||||
[JsonProperty(PropertyName = "freeleech")]
|
||||
|
||||
[JsonPropertyName("freeleech")]
|
||||
public bool FreeLeech { get; set; }
|
||||
[JsonProperty(PropertyName = "doubleup")]
|
||||
|
||||
[JsonPropertyName("doubleup")]
|
||||
public bool DoubleUp { get; set; }
|
||||
[JsonProperty(PropertyName = "upload_date")]
|
||||
|
||||
[JsonPropertyName("upload_date")]
|
||||
public string UploadDate { get; set; }
|
||||
|
||||
public string Category { get; set; }
|
||||
[JsonProperty(PropertyName = "small_description")]
|
||||
|
||||
[JsonPropertyName("small_description")]
|
||||
public string SmallDescription { get; set; }
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
@@ -35,7 +35,7 @@ public class FileListParser : IParseIndexerResponse
|
||||
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var results = JsonConvert.DeserializeObject<List<FileListTorrent>>(indexerResponse.Content);
|
||||
var results = STJson.Deserialize<List<FileListTorrent>>(indexerResponse.Content);
|
||||
|
||||
foreach (var row in results)
|
||||
{
|
||||
@@ -54,7 +54,7 @@ public class FileListParser : IParseIndexerResponse
|
||||
}
|
||||
|
||||
var imdbId = 0;
|
||||
if (row.ImdbId != null && row.ImdbId.Length > 2)
|
||||
if (row.ImdbId is { Length: > 2 })
|
||||
{
|
||||
imdbId = int.Parse(row.ImdbId.Substring(2));
|
||||
}
|
||||
@@ -64,7 +64,7 @@ public class FileListParser : IParseIndexerResponse
|
||||
|
||||
releaseInfos.Add(new TorrentInfo
|
||||
{
|
||||
Guid = string.Format("FileList-{0}", id),
|
||||
Guid = $"FileList-{id}",
|
||||
Title = row.Name,
|
||||
Size = row.Size,
|
||||
Categories = _categories.MapTrackerCatDescToNewznab(row.Category),
|
||||
@@ -91,21 +91,21 @@ public class FileListParser : IParseIndexerResponse
|
||||
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
private string GetDownloadUrl(string torrentId)
|
||||
private string GetDownloadUrl(uint torrentId)
|
||||
{
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/download.php")
|
||||
.AddQueryParam("id", torrentId)
|
||||
.AddQueryParam("id", torrentId.ToString())
|
||||
.AddQueryParam("passkey", _settings.Passkey);
|
||||
|
||||
return url.FullUri;
|
||||
}
|
||||
|
||||
private string GetInfoUrl(string torrentId)
|
||||
private string GetInfoUrl(uint torrentId)
|
||||
{
|
||||
var url = new HttpUri(_settings.BaseUrl)
|
||||
.CombinePath("/details.php")
|
||||
.AddQueryParam("id", torrentId);
|
||||
.AddQueryParam("id", torrentId.ToString());
|
||||
|
||||
return url.FullUri;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ public class FunFile : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = await parser.ParseDocumentAsync(response.Content);
|
||||
var errorMessage = dom.QuerySelector("td.mf_content")?.TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
@@ -276,7 +276,7 @@ public class FunFileParser : IParseIndexerResponse
|
||||
var releaseInfos = new List<TorrentInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var rows = dom.QuerySelectorAll("table.mainframe table[cellpadding=\"2\"] > tbody > tr:has(td.row3)");
|
||||
foreach (var row in rows)
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = parser.ParseDocument(response.Content);
|
||||
var errorMessages = dom
|
||||
.QuerySelectorAll("table.lista td.lista span[style*=\"#FF0000\"], table.lista td.header:contains(\"login attempts\")")
|
||||
.Select(r => r.TextContent.Trim())
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
if (response.Content != null && !response.Content.ContainsIgnoreCase("If your browser doesn't have javascript enabled"))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = await parser.ParseDocumentAsync(response.Content);
|
||||
|
||||
var errorMessage = dom.QuerySelector("div > font[color=\"#FF0000\"]")?.TextContent.Trim();
|
||||
|
||||
@@ -256,7 +256,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var userInfo = dom.QuerySelector("table.navus tr");
|
||||
var userRank = userInfo?.Children[1].TextContent.Replace("Rank:", string.Empty).Trim();
|
||||
|
||||
@@ -291,7 +291,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
using var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var rows = doc.QuerySelectorAll("table[id=\"torrents\"] > tbody > tr");
|
||||
foreach (var row in rows)
|
||||
|
||||
@@ -266,7 +266,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var rows = dom.QuerySelectorAll("table#sortabletable > tbody > tr:has(a[href*=\"details.php?id=\"])");
|
||||
foreach (var row in rows)
|
||||
|
||||
@@ -77,7 +77,7 @@ public class Libble : TorrentIndexerBase<LibbleSettings>
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = parser.ParseDocument(response.Content);
|
||||
var errorMessage = dom.QuerySelector("#loginform > .warning")?.TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
@@ -238,7 +238,7 @@ public class LibbleParser : IParseIndexerResponse
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
using var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var groups = doc.QuerySelectorAll("table#torrent_table > tbody > tr.group:has(strong > a[href*=\"torrents.php?id=\"])");
|
||||
foreach (var group in groups)
|
||||
|
||||
@@ -177,7 +177,7 @@ public class MoreThanTVParser : IParseIndexerResponse
|
||||
try
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var document = parser.ParseDocument(indexerResponse.Content);
|
||||
using var document = parser.ParseDocument(indexerResponse.Content);
|
||||
var torrents = document.QuerySelectorAll("#torrent_table > tbody > tr.torrent");
|
||||
var movies = new[] { "movie" };
|
||||
var tv = new[] { "season", "episode" };
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
@@ -65,6 +68,26 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
return Task.FromResult(request);
|
||||
}
|
||||
|
||||
protected override IList<ReleaseInfo> CleanupReleases(IEnumerable<ReleaseInfo> releases, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var cleanReleases = base.CleanupReleases(releases, searchCriteria);
|
||||
|
||||
return FilterReleasesByQuery(cleanReleases, searchCriteria).ToList();
|
||||
}
|
||||
|
||||
protected override IEnumerable<ReleaseInfo> FilterReleasesByQuery(IEnumerable<ReleaseInfo> releases, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (!searchCriteria.IsRssSearch &&
|
||||
searchCriteria.IsIdSearch &&
|
||||
searchCriteria is TvSearchCriteria tvSearchCriteria &&
|
||||
tvSearchCriteria.EpisodeSearchString.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
releases = releases.Where(r => r.Title.IsNotNullOrWhiteSpace() && r.Title.ContainsIgnoreCase(tvSearchCriteria.EpisodeSearchString)).ToList();
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
@@ -208,7 +231,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request");
|
||||
}
|
||||
|
||||
var jsonResponse = new HttpResponse<JsonRpcResponse<NebulanceTorrents>>(indexerResponse.HttpResponse).Resource;
|
||||
var jsonResponse = STJson.Deserialize<JsonRpcResponse<NebulanceTorrents>>(indexerResponse.HttpResponse.Content);
|
||||
|
||||
if (jsonResponse.Error != null || jsonResponse.Result == null)
|
||||
{
|
||||
@@ -242,12 +265,18 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
Grabs = ParseUtil.CoerceInt(row.Snatch),
|
||||
Seeders = ParseUtil.CoerceInt(row.Seed),
|
||||
Peers = ParseUtil.CoerceInt(row.Seed) + ParseUtil.CoerceInt(row.Leech),
|
||||
Scene = row.Tags?.ContainsIgnoreCase("scene"),
|
||||
MinimumRatio = 0, // ratioless
|
||||
MinimumSeedTime = row.Category.ToLower() == "season" ? 432000 : 86400, // 120 hours for seasons and 24 hours for episodes
|
||||
DownloadVolumeFactor = 0, // ratioless tracker
|
||||
UploadVolumeFactor = 1
|
||||
};
|
||||
|
||||
if (row.TvMazeId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
release.TvMazeId = ParseUtil.CoerceInt(row.TvMazeId);
|
||||
}
|
||||
|
||||
torrentInfos.Add(release);
|
||||
}
|
||||
|
||||
@@ -299,25 +328,28 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public class NebulanceTorrent
|
||||
{
|
||||
[JsonProperty(PropertyName = "rls_name")]
|
||||
[JsonPropertyName("rls_name")]
|
||||
public string ReleaseTitle { get; set; }
|
||||
[JsonProperty(PropertyName = "cat")]
|
||||
[JsonPropertyName("cat")]
|
||||
public string Category { get; set; }
|
||||
public string Size { get; set; }
|
||||
public string Seed { get; set; }
|
||||
public string Leech { get; set; }
|
||||
public string Snatch { get; set; }
|
||||
public string Download { get; set; }
|
||||
[JsonProperty(PropertyName = "file_list")]
|
||||
[JsonPropertyName("file_list")]
|
||||
public string[] FileList { get; set; }
|
||||
[JsonProperty(PropertyName = "group_name")]
|
||||
[JsonPropertyName("group_name")]
|
||||
public string GroupName { get; set; }
|
||||
[JsonProperty(PropertyName = "series_banner")]
|
||||
[JsonPropertyName("series_banner")]
|
||||
public string Banner { get; set; }
|
||||
[JsonProperty(PropertyName = "group_id")]
|
||||
[JsonPropertyName("group_id")]
|
||||
public string TorrentId { get; set; }
|
||||
[JsonProperty(PropertyName = "rls_utc")]
|
||||
[JsonPropertyName("series_id")]
|
||||
public string TvMazeId { get; set; }
|
||||
[JsonPropertyName("rls_utc")]
|
||||
public string PublishDateUtc { get; set; }
|
||||
public IEnumerable<string> Tags { get; set; }
|
||||
}
|
||||
|
||||
public class NebulanceTorrents
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
return new NewznabRequestGenerator(_capabilitiesProvider)
|
||||
{
|
||||
Definition = Definition,
|
||||
PageSize = PageSize,
|
||||
Settings = Settings
|
||||
};
|
||||
|
||||
@@ -129,6 +129,8 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
capabilities.SearchParams.AddIfNotNull(searchParam);
|
||||
}
|
||||
}
|
||||
|
||||
capabilities.SupportsRawSearch = xmlBasicSearch.Attribute("searchEngine")?.Value == "raw";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -263,7 +263,7 @@ public class NorBitsParser : IParseIndexerResponse
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var rows = dom.QuerySelectorAll("#torrentTable > tbody > tr").Skip(1).ToCollection();
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ using NLog;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
|
||||
{
|
||||
public class PassThePopcorn : TorrentIndexerBase<PassThePopcornSettings>
|
||||
{
|
||||
@@ -29,22 +29,20 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new PassThePopcornRequestGenerator
|
||||
{
|
||||
Settings = Settings,
|
||||
HttpClient = _httpClient,
|
||||
Logger = _logger
|
||||
};
|
||||
return new PassThePopcornRequestGenerator(Settings);
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new PassThePopcornParser(Settings);
|
||||
}
|
||||
|
||||
private IndexerCapabilities SetCapabilities()
|
||||
{
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
SearchParams = new List<SearchParam>
|
||||
{
|
||||
SearchParam.Q
|
||||
},
|
||||
LimitsDefault = PageSize,
|
||||
LimitsMax = PageSize,
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
@@ -73,16 +71,11 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
{
|
||||
return new PassThePopcornParser(Settings, Capabilities, _logger);
|
||||
}
|
||||
}
|
||||
|
||||
public class PassThePopcornFlag : IndexerFlag
|
||||
{
|
||||
public static IndexerFlag Golden => new IndexerFlag("golden", "Release follows Golden Popcorn quality rules");
|
||||
public static IndexerFlag Approved => new IndexerFlag("approved", "Release approved by PTP");
|
||||
public static IndexerFlag Golden => new ("golden", "Release follows Golden Popcorn quality rules");
|
||||
public static IndexerFlag Approved => new ("approved", "Release approved by PTP");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
|
||||
{
|
||||
public class Director
|
||||
public class PassThePopcornResponse
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string TotalResults { get; set; }
|
||||
public List<PassThePopcornMovie> Movies { get; set; }
|
||||
public string Page { get; set; }
|
||||
public string AuthKey { get; set; }
|
||||
public string PassKey { get; set; }
|
||||
}
|
||||
|
||||
public class Torrent
|
||||
public class PassThePopcornMovie
|
||||
{
|
||||
public string GroupId { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Year { get; set; }
|
||||
public string Cover { get; set; }
|
||||
public List<string> Tags { get; set; }
|
||||
public string ImdbId { get; set; }
|
||||
public List<PassThePopcornTorrent> Torrents { get; set; }
|
||||
}
|
||||
|
||||
public class PassThePopcornTorrent
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Quality { get; set; }
|
||||
@@ -19,7 +32,7 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
public string Resolution { get; set; }
|
||||
public bool Scene { get; set; }
|
||||
public string Size { get; set; }
|
||||
public DateTime UploadTime { get; set; }
|
||||
public string UploadTime { get; set; }
|
||||
public string RemasterTitle { get; set; }
|
||||
public string Snatched { get; set; }
|
||||
public string Seeders { get; set; }
|
||||
@@ -29,32 +42,4 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
public bool GoldenPopcorn { get; set; }
|
||||
public string FreeleechType { get; set; }
|
||||
}
|
||||
|
||||
public class Movie
|
||||
{
|
||||
public string GroupId { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Year { get; set; }
|
||||
public string Cover { get; set; }
|
||||
public List<string> Tags { get; set; }
|
||||
public List<Director> Directors { get; set; }
|
||||
public string ImdbId { get; set; }
|
||||
public List<Torrent> Torrents { get; set; }
|
||||
}
|
||||
|
||||
public class PassThePopcornResponse
|
||||
{
|
||||
public string TotalResults { get; set; }
|
||||
public List<Movie> Movies { get; set; }
|
||||
public string Page { get; set; }
|
||||
public string AuthKey { get; set; }
|
||||
public string PassKey { get; set; }
|
||||
}
|
||||
|
||||
public class PassThePopcornAuthResponse
|
||||
{
|
||||
public string Result { get; set; }
|
||||
public string Popcron { get; set; }
|
||||
public string AntiCsrfToken { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using System.Text.RegularExpressions;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Indexers.Exceptions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
|
||||
{
|
||||
public class PassThePopcornParser : IParseIndexerResponse
|
||||
{
|
||||
private readonly IndexerCapabilities _capabilities;
|
||||
private readonly PassThePopcornSettings _settings;
|
||||
private readonly Logger _logger;
|
||||
public PassThePopcornParser(PassThePopcornSettings settings, IndexerCapabilities capabilities, Logger logger)
|
||||
|
||||
private static Regex SeasonRegex => new (@"\bS\d{2,3}(E\d{2,3})?\b", RegexOptions.Compiled);
|
||||
|
||||
public PassThePopcornParser(PassThePopcornSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
_capabilities = capabilities;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
||||
{
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
var indexerHttpResponse = indexerResponse.HttpResponse;
|
||||
|
||||
if (indexerHttpResponse.StatusCode != HttpStatusCode.OK)
|
||||
var httpResponse = indexerResponse.HttpResponse;
|
||||
|
||||
if (httpResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
// Remove cookie cache
|
||||
if (indexerHttpResponse.HasHttpRedirect && indexerHttpResponse.RedirectUrl
|
||||
.ContainsIgnoreCase("login.php"))
|
||||
{
|
||||
CookiesUpdater(null, null);
|
||||
throw new IndexerAuthException("We are being redirected to the PTP login page. Most likely your session expired or was killed. Recheck your cookie or credentials and try testing the indexer.");
|
||||
}
|
||||
|
||||
if (indexerHttpResponse.StatusCode == HttpStatusCode.Forbidden)
|
||||
if (httpResponse.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
throw new RequestLimitReachedException(indexerResponse, "PTP Query Limit Reached. Please try again later.");
|
||||
}
|
||||
@@ -45,19 +38,13 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response status {indexerResponse.HttpResponse.StatusCode} code from indexer request");
|
||||
}
|
||||
|
||||
if (indexerHttpResponse.Headers.ContentType != HttpAccept.Json.Value)
|
||||
if (httpResponse.Headers.ContentType != HttpAccept.Json.Value)
|
||||
{
|
||||
if (indexerHttpResponse.Request.Url.Path.ContainsIgnoreCase("login.php"))
|
||||
{
|
||||
CookiesUpdater(null, null);
|
||||
throw new IndexerAuthException("We are currently on the login page. Most likely your session expired or was killed. Try testing the indexer in the settings.");
|
||||
}
|
||||
|
||||
// Remove cookie cache
|
||||
throw new IndexerException(indexerResponse, $"Unexpected response header {indexerResponse.HttpResponse.Headers.ContentType} from indexer request, expected {HttpAccept.Json.Value}");
|
||||
}
|
||||
|
||||
var jsonResponse = STJson.Deserialize<PassThePopcornResponse>(indexerResponse.Content);
|
||||
|
||||
if (jsonResponse.TotalResults == "0" ||
|
||||
jsonResponse.TotalResults.IsNullOrWhiteSpace() ||
|
||||
jsonResponse.Movies == null)
|
||||
@@ -81,51 +68,39 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
|
||||
if (torrent.Checked)
|
||||
{
|
||||
flags.Add(PassThePopcornFlag.Approved); //title = $"{title} ✔";
|
||||
flags.Add(PassThePopcornFlag.Approved);
|
||||
}
|
||||
|
||||
if (torrent.Scene)
|
||||
var categories = new List<IndexerCategory> { NewznabStandardCategory.Movies };
|
||||
|
||||
if (title != null && SeasonRegex.Match(title).Success)
|
||||
{
|
||||
flags.Add(IndexerFlag.Scene);
|
||||
categories.Add(NewznabStandardCategory.TV);
|
||||
}
|
||||
|
||||
var free = !(torrent.FreeleechType is null);
|
||||
|
||||
// Only add approved torrents
|
||||
try
|
||||
torrentInfos.Add(new TorrentInfo
|
||||
{
|
||||
torrentInfos.Add(new TorrentInfo
|
||||
{
|
||||
Guid = string.Format("PassThePopcorn-{0}", id),
|
||||
Title = title,
|
||||
Size = long.Parse(torrent.Size),
|
||||
DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey),
|
||||
InfoUrl = GetInfoUrl(result.GroupId, id),
|
||||
Grabs = int.Parse(torrent.Snatched),
|
||||
Seeders = int.Parse(torrent.Seeders),
|
||||
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
|
||||
PublishDate = torrent.UploadTime.ToUniversalTime(),
|
||||
ImdbId = result.ImdbId.IsNotNullOrWhiteSpace() ? int.Parse(result.ImdbId) : 0,
|
||||
IndexerFlags = flags,
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 345600,
|
||||
DownloadVolumeFactor = free ? 0 : 1,
|
||||
UploadVolumeFactor = 1,
|
||||
Categories = new List<IndexerCategory> { NewznabStandardCategory.Movies }
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Encountered exception parsing PTP torrent: {" +
|
||||
$"Size: {torrent.Size}" +
|
||||
$"UploadTime: {torrent.UploadTime}" +
|
||||
$"Seeders: {torrent.Seeders}" +
|
||||
$"Leechers: {torrent.Leechers}" +
|
||||
$"ReleaseName: {torrent.ReleaseName}" +
|
||||
$"ID: {torrent.Id}" +
|
||||
"}. Please immediately report this info on https://github.com/Prowlarr/Prowlarr/issues/1584.");
|
||||
throw;
|
||||
}
|
||||
Guid = $"PassThePopcorn-{id}",
|
||||
Title = title,
|
||||
Year = int.Parse(result.Year),
|
||||
InfoUrl = GetInfoUrl(result.GroupId, id),
|
||||
DownloadUrl = GetDownloadUrl(id, jsonResponse.AuthKey, jsonResponse.PassKey),
|
||||
Categories = categories,
|
||||
Size = long.Parse(torrent.Size),
|
||||
Grabs = int.Parse(torrent.Snatched),
|
||||
Seeders = int.Parse(torrent.Seeders),
|
||||
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
|
||||
PublishDate = DateTime.Parse(torrent.UploadTime + " +0000", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal),
|
||||
ImdbId = result.ImdbId.IsNotNullOrWhiteSpace() ? int.Parse(result.ImdbId) : 0,
|
||||
Scene = torrent.Scene,
|
||||
IndexerFlags = flags,
|
||||
DownloadVolumeFactor = torrent.FreeleechType is "Freeleech" ? 0 : 1,
|
||||
UploadVolumeFactor = 1,
|
||||
MinimumRatio = 1,
|
||||
MinimumSeedTime = 345600,
|
||||
Genres = result.Tags ?? new List<string>(),
|
||||
PosterUrl = GetPosterUrl(result.Cover)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,5 +130,17 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
|
||||
return url.FullUri;
|
||||
}
|
||||
|
||||
private static string GetPosterUrl(string cover)
|
||||
{
|
||||
if (cover.IsNotNullOrWhiteSpace() &&
|
||||
Uri.TryCreate(cover, UriKind.Absolute, out var posterUri) &&
|
||||
(posterUri.Scheme == Uri.UriSchemeHttp || posterUri.Scheme == Uri.UriSchemeHttps))
|
||||
{
|
||||
return posterUri.AbsoluteUri;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
|
||||
{
|
||||
public class PassThePopcornRequestGenerator : IIndexerRequestGenerator
|
||||
{
|
||||
public PassThePopcornSettings Settings { get; set; }
|
||||
private readonly PassThePopcornSettings _settings;
|
||||
|
||||
public IDictionary<string, string> Cookies { get; set; }
|
||||
|
||||
public IIndexerHttpClient HttpClient { get; set; }
|
||||
public Logger Logger { get; set; }
|
||||
public PassThePopcornRequestGenerator(PassThePopcornSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
@@ -34,51 +33,6 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
|
||||
private IEnumerable<IndexerRequest> GetRequest(string searchParameters, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var queryParams = new NameValueCollection
|
||||
{
|
||||
{ "action", "advanced" },
|
||||
{ "json", "noredirect" },
|
||||
{ "grouping", "0" },
|
||||
{ "searchstr", searchParameters }
|
||||
};
|
||||
|
||||
if (searchCriteria.Limit is > 0 && searchCriteria.Offset is > 0)
|
||||
{
|
||||
var page = (int)(searchCriteria.Offset / searchCriteria.Limit) + 1;
|
||||
queryParams.Set("page", page.ToString());
|
||||
}
|
||||
|
||||
if (Settings.FreeleechOnly)
|
||||
{
|
||||
queryParams.Set("freetorrent", "1");
|
||||
}
|
||||
|
||||
var request =
|
||||
new IndexerRequest(
|
||||
$"{Settings.BaseUrl.Trim().TrimEnd('/')}/torrents.php?{queryParams.GetQueryString()}",
|
||||
HttpAccept.Json);
|
||||
|
||||
request.HttpRequest.Headers["ApiUser"] = Settings.APIUser;
|
||||
request.HttpRequest.Headers["ApiKey"] = Settings.APIKey;
|
||||
|
||||
if (Settings.APIKey.IsNullOrWhiteSpace())
|
||||
{
|
||||
foreach (var cookie in Cookies)
|
||||
{
|
||||
request.HttpRequest.Cookies[cookie.Key] = cookie.Value;
|
||||
}
|
||||
|
||||
CookiesUpdater(Cookies, DateTime.Now.AddDays(30));
|
||||
}
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
|
||||
{
|
||||
return new IndexerPageableRequestChain();
|
||||
@@ -102,5 +56,38 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetRequest(string searchParameters, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var parameters = new NameValueCollection
|
||||
{
|
||||
{ "action", "advanced" },
|
||||
{ "json", "noredirect" },
|
||||
{ "grouping", "0" },
|
||||
{ "searchstr", searchParameters }
|
||||
};
|
||||
|
||||
if (searchCriteria.Limit is > 0 && searchCriteria.Offset is > 0)
|
||||
{
|
||||
var page = (int)(searchCriteria.Offset / searchCriteria.Limit) + 1;
|
||||
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);
|
||||
request.HttpRequest.Headers.Add("ApiUser", _settings.APIUser);
|
||||
request.HttpRequest.Headers.Add("ApiKey", _settings.APIKey);
|
||||
|
||||
yield return request;
|
||||
}
|
||||
|
||||
public Func<IDictionary<string, string>> GetCookies { get; set; }
|
||||
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Indexers.Settings;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.PassThePopcorn
|
||||
namespace NzbDrone.Core.Indexers.Definitions.PassThePopcorn
|
||||
{
|
||||
public class PassThePopcornSettingsValidator : NoAuthSettingsValidator<PassThePopcornSettings>
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions;
|
||||
|
||||
[Obsolete("PirateTheNet has shutdown 2023-10-14")]
|
||||
public class PirateTheNet : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
||||
{
|
||||
public override string Name => "PirateTheNet";
|
||||
@@ -234,7 +234,7 @@ public class PirateTheNetParser : IParseIndexerResponse
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var rows = dom.QuerySelectorAll("table.main > tbody > tr");
|
||||
foreach (var row in rows.Skip(1))
|
||||
|
||||
@@ -171,7 +171,7 @@ public class PixelHDParser : IParseIndexerResponse
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var groups = dom.QuerySelectorAll("div.browsePoster");
|
||||
foreach (var group in groups)
|
||||
|
||||
@@ -85,7 +85,7 @@ public class PreToMe : TorrentIndexerBase<PreToMeSettings>
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = parser.ParseDocument(response.Content);
|
||||
var errorMessage = dom.QuerySelector("table.body_table font[color~=\"red\"]")?.TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
@@ -340,7 +340,7 @@ public class PreToMeParser : IParseIndexerResponse
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var rows = dom.QuerySelectorAll("table > tbody > tr.browse");
|
||||
foreach (var row in rows)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -249,7 +249,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var torrentInfos = new List<TorrentInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
var rows = dom.QuerySelectorAll("#torrents-table > tbody > tr");
|
||||
|
||||
foreach (var row in rows.Skip(1))
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = parser.ParseDocument(response.Content);
|
||||
var magnetLink = dom.QuerySelector("table.attach a.magnet-link[href^=\"magnet:?\"]")?.GetAttribute("href");
|
||||
|
||||
if (magnetLink == null)
|
||||
@@ -104,7 +104,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
if (!response.Content.Contains("id=\"logged-in-username\""))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var document = await parser.ParseDocumentAsync(response.Content);
|
||||
using var document = await parser.ParseDocumentAsync(response.Content);
|
||||
var errorMessage = document.QuerySelector("h4.warnColor1.tCenter.mrg_16, div.msg-main")?.TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "RuTracker Auth Failed");
|
||||
@@ -1580,7 +1580,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
using var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
var rows = doc.QuerySelectorAll("table#tor-tbl > tbody > tr");
|
||||
|
||||
foreach (var row in rows)
|
||||
|
||||
@@ -225,7 +225,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var table = dom.QuerySelector("table.movehere");
|
||||
if (table == null)
|
||||
|
||||
@@ -78,7 +78,7 @@ public class Shazbat : TorrentIndexerBase<ShazbatSettings>
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = await parser.ParseDocumentAsync(response.Content);
|
||||
var errorMessage = dom.QuerySelector("div#fail .modal-body")?.TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
@@ -223,7 +223,7 @@ public class ShazbatParser : IParseIndexerResponse
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var hasGlobalFreeleech = dom.QuerySelector("span:contains(\"Freeleech until:\"):has(span.datetime)") != null;
|
||||
|
||||
@@ -303,7 +303,7 @@ public class ShazbatParser : IParseIndexerResponse
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
if (!hasGlobalFreeleech)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
@@ -16,12 +17,11 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
[Obsolete("Site unavailable")]
|
||||
public class Shizaproject : TorrentIndexerBase<NoAuthTorrentBaseSettings>
|
||||
{
|
||||
public override string Name => "ShizaProject";
|
||||
public override string[] IndexerUrls => new string[] { "https://shiza-project.com/" };
|
||||
public override string Description => "Shizaproject is russian anime voiceover group and eponymous anime tracker.";
|
||||
public override string[] IndexerUrls => new[] { "https://shiza-project.com/" };
|
||||
public override string Description => "ShizaProject Tracker is a Public RUSSIAN tracker and release group for ANIME";
|
||||
public override string Language => "ru-RU";
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public override IIndexerRequestGenerator GetRequestGenerator()
|
||||
{
|
||||
return new ShizaprojectRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
|
||||
return new ShizaprojectRequestGenerator { Settings = Settings, Capabilities = Capabilities };
|
||||
}
|
||||
|
||||
public override IParseIndexerResponse GetParser()
|
||||
@@ -47,20 +47,22 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var caps = new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
}
|
||||
{
|
||||
MovieSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TVAnime, "TV");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVAnime, "TV_SPECIAL");
|
||||
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVAnime, "ONA");
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVAnime, "OVA");
|
||||
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Movies, "MOVIE");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.Movies, "SHORT_MOVIE");
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
@@ -93,6 +95,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
publishedAt
|
||||
slug
|
||||
torrents {
|
||||
synopsis
|
||||
downloaded
|
||||
seeders
|
||||
leechers
|
||||
@@ -112,7 +115,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var queryCollection = new NameValueCollection
|
||||
{
|
||||
{ "query", query.Replace('\n', ' ').Trim() },
|
||||
{ "variables", Newtonsoft.Json.JsonConvert.SerializeObject(variables) }
|
||||
{ "variables", JsonConvert.SerializeObject(variables) }
|
||||
};
|
||||
|
||||
var requestUrl = string.Format("{0}/graphql?", Settings.BaseUrl.TrimEnd('/')) + queryCollection.GetQueryString();
|
||||
@@ -175,25 +178,26 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
_categories = categories;
|
||||
}
|
||||
|
||||
private string composeTitle(ShizaprojectNode n, ShizaprojectTorrent tr)
|
||||
private string ComposeTitle(ShizaprojectNode n, ShizaprojectTorrent tr)
|
||||
{
|
||||
var title = string.Format("{0} / {1}", n.Name, n.OriginalName);
|
||||
foreach (var tl in n.AlternativeNames)
|
||||
var allNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
title += " / " + tl;
|
||||
n.Name,
|
||||
n.OriginalName
|
||||
};
|
||||
allNames.UnionWith(n.AlternativeNames.ToHashSet());
|
||||
|
||||
var title = $"{string.Join(" / ", allNames)} {tr.Synopsis}";
|
||||
|
||||
if (tr.VideoQualities.Length > 0)
|
||||
{
|
||||
title += $" [{string.Join(" ", tr.VideoQualities)}]";
|
||||
}
|
||||
|
||||
title += " [";
|
||||
foreach (var q in tr.VideoQualities)
|
||||
{
|
||||
title += " " + q;
|
||||
}
|
||||
|
||||
title += " ]";
|
||||
return title;
|
||||
}
|
||||
|
||||
private DateTime getActualPublishDate(ShizaprojectNode n, ShizaprojectTorrent t)
|
||||
private DateTime GetActualPublishDate(ShizaprojectNode n, ShizaprojectTorrent t)
|
||||
{
|
||||
if (n.PublishedAt == null)
|
||||
{
|
||||
@@ -205,7 +209,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
}
|
||||
|
||||
private string getResolution(string[] qualities)
|
||||
private string GetResolution(string[] qualities)
|
||||
{
|
||||
var resPrefix = "RESOLUTION_";
|
||||
var res = Array.Find(qualities, s => s.StartsWith(resPrefix));
|
||||
@@ -234,7 +238,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
{
|
||||
var torrentInfo = new TorrentInfo
|
||||
{
|
||||
Title = composeTitle(e.Node, tr),
|
||||
Title = ComposeTitle(e.Node, tr),
|
||||
InfoUrl = string.Format("{0}/releases/{1}/", _settings.BaseUrl.TrimEnd('/'), e.Node.Slug),
|
||||
DownloadVolumeFactor = 0,
|
||||
UploadVolumeFactor = 1,
|
||||
@@ -242,12 +246,12 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
Peers = tr.Leechers + tr.Seeders,
|
||||
Grabs = tr.Downloaded,
|
||||
Categories = _categories.MapTrackerCatDescToNewznab(e.Node.Type),
|
||||
PublishDate = getActualPublishDate(e.Node, tr),
|
||||
PublishDate = GetActualPublishDate(e.Node, tr),
|
||||
Guid = tr.File.Url,
|
||||
DownloadUrl = tr.File.Url,
|
||||
MagnetUrl = tr.MagnetUri,
|
||||
Size = tr.Size,
|
||||
Resolution = getResolution(tr.VideoQualities)
|
||||
Resolution = GetResolution(tr.VideoQualities)
|
||||
};
|
||||
|
||||
torrentInfos.Add(torrentInfo);
|
||||
@@ -310,6 +314,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
|
||||
public class ShizaprojectTorrent
|
||||
{
|
||||
public string Synopsis { get; set; }
|
||||
public int Downloaded { get; set; }
|
||||
public int Seeders { get; set; }
|
||||
public int Leechers { get; set; }
|
||||
|
||||
@@ -96,7 +96,7 @@ public class SpeedCD : TorrentIndexerBase<SpeedCDSettings>
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = parser.ParseDocument(response.Content);
|
||||
var errorMessage = dom.QuerySelector("h5")?.TextContent.Trim();
|
||||
|
||||
if (response.Content.Contains("Wrong Captcha!"))
|
||||
@@ -323,7 +323,7 @@ public class SpeedCDParser : IParseIndexerResponse
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var rows = dom.QuerySelectorAll("div.boxContent > table > tbody > tr");
|
||||
foreach (var row in rows)
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = parser.ParseDocument(response.Content);
|
||||
var errorMessage = dom.QuerySelector("form#loginform")?.TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
@@ -224,7 +224,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
using var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
// get params to build download link (user could be banned without those params)
|
||||
var rssFeedUri = new Uri(_settings.BaseUrl + doc.QuerySelector("link[href^=\"/feeds.php?feed=\"]")
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = await parser.ParseDocumentAsync(response.Content);
|
||||
var errorMessage = dom.QuerySelector("table.forumline table span.gen")?.FirstChild?.TextContent;
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
@@ -367,7 +367,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var rows = dom.QuerySelectorAll("table.forumline > tbody > tr[class*=prow]");
|
||||
foreach (var row in rows)
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = parser.ParseDocument(response.Content);
|
||||
var errorMessage = dom.QuerySelector("td.embedded")?.TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
@@ -245,7 +245,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
using var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
var rows = doc.QuerySelectorAll("table > tbody:has(tr > td.colhead) > tr:not(:has(td.colhead))");
|
||||
foreach (var row in rows)
|
||||
{
|
||||
|
||||
@@ -77,14 +77,6 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
}
|
||||
};
|
||||
|
||||
caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.TVAnime, "Anime");
|
||||
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.PC, "Appz/Packs");
|
||||
caps.Categories.AddCategoryMapping(42, NewznabStandardCategory.AudioAudiobook, "Audio Books");
|
||||
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.Books, "Books");
|
||||
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.TVDocumentary, "Documentary");
|
||||
caps.Categories.AddCategoryMapping(47, NewznabStandardCategory.Other, "Fonts");
|
||||
caps.Categories.AddCategoryMapping(43, NewznabStandardCategory.PCMac, "Mac");
|
||||
|
||||
caps.Categories.AddCategoryMapping(96, NewznabStandardCategory.MoviesUHD, "Movie/4K");
|
||||
caps.Categories.AddCategoryMapping(25, NewznabStandardCategory.MoviesSD, "Movies/480p");
|
||||
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.MoviesBluRay, "Movies/Bluray");
|
||||
@@ -97,31 +89,43 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
caps.Categories.AddCategoryMapping(48, NewznabStandardCategory.Movies, "Movies/x265");
|
||||
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.MoviesSD, "Movies/XviD");
|
||||
|
||||
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.AudioMP3, "Music/Audio");
|
||||
caps.Categories.AddCategoryMapping(23, NewznabStandardCategory.AudioForeign, "Music/Non-English");
|
||||
caps.Categories.AddCategoryMapping(41, NewznabStandardCategory.Audio, "Music/Packs");
|
||||
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.AudioVideo, "Music/Video");
|
||||
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.Audio, "Music/Flac");
|
||||
|
||||
caps.Categories.AddCategoryMapping(45, NewznabStandardCategory.AudioOther, "Podcast");
|
||||
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.PCGames, "PC/Games");
|
||||
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.ConsolePS3, "PS3");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.ConsolePSP, "PSP");
|
||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.ConsoleWii, "Wii");
|
||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.ConsoleXBox360, "Xbox-360");
|
||||
|
||||
caps.Categories.AddCategoryMapping(24, NewznabStandardCategory.TVSD, "TV/480p");
|
||||
caps.Categories.AddCategoryMapping(32, NewznabStandardCategory.TVHD, "TV/Bluray");
|
||||
caps.Categories.AddCategoryMapping(31, NewznabStandardCategory.TVSD, "TV/DVD-R");
|
||||
caps.Categories.AddCategoryMapping(33, NewznabStandardCategory.TVSD, "TV/DVD-Rip");
|
||||
caps.Categories.AddCategoryMapping(46, NewznabStandardCategory.TVSD, "TV/Mobile");
|
||||
caps.Categories.AddCategoryMapping(82, NewznabStandardCategory.TVForeign, "TV/Non-English");
|
||||
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.TV, "TV/Packs");
|
||||
caps.Categories.AddCategoryMapping(26, NewznabStandardCategory.TVSD, "TV/SD/x264");
|
||||
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.TVHD, "TV/x264");
|
||||
caps.Categories.AddCategoryMapping(34, NewznabStandardCategory.TVUHD, "TV/x265");
|
||||
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TVSD, "TV/XviD");
|
||||
|
||||
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.PCGames, "PC/Games");
|
||||
caps.Categories.AddCategoryMapping(18, NewznabStandardCategory.ConsolePS3, "PS");
|
||||
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.ConsolePSP, "PSP");
|
||||
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.ConsoleNDS, "Nintendo");
|
||||
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.ConsoleXBox, "Xbox");
|
||||
|
||||
caps.Categories.AddCategoryMapping(17, NewznabStandardCategory.AudioMP3, "Music/Audio");
|
||||
caps.Categories.AddCategoryMapping(27, NewznabStandardCategory.Audio, "Music/Flac");
|
||||
caps.Categories.AddCategoryMapping(23, NewznabStandardCategory.AudioForeign, "Music/Non-English");
|
||||
caps.Categories.AddCategoryMapping(41, NewznabStandardCategory.Audio, "Music/Packs");
|
||||
caps.Categories.AddCategoryMapping(16, NewznabStandardCategory.AudioVideo, "Music/Video");
|
||||
|
||||
caps.Categories.AddCategoryMapping(29, NewznabStandardCategory.TVAnime, "Anime");
|
||||
caps.Categories.AddCategoryMapping(42, NewznabStandardCategory.AudioAudiobook, "Audio Books");
|
||||
caps.Categories.AddCategoryMapping(20, NewznabStandardCategory.Books, "Books");
|
||||
caps.Categories.AddCategoryMapping(102, NewznabStandardCategory.BooksForeign, "Books/Non-English");
|
||||
caps.Categories.AddCategoryMapping(30, NewznabStandardCategory.TVDocumentary, "Documentary");
|
||||
caps.Categories.AddCategoryMapping(95, NewznabStandardCategory.TVDocumentary, "Educational");
|
||||
caps.Categories.AddCategoryMapping(47, NewznabStandardCategory.Other, "Fonts");
|
||||
caps.Categories.AddCategoryMapping(43, NewznabStandardCategory.PCMac, "Mac");
|
||||
caps.Categories.AddCategoryMapping(45, NewznabStandardCategory.AudioOther, "Podcast");
|
||||
caps.Categories.AddCategoryMapping(28, NewznabStandardCategory.PC, "Softwa/Packs");
|
||||
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.PC, "Software");
|
||||
|
||||
caps.Categories.AddCategoryMapping(19, NewznabStandardCategory.XXX, "XXX/0Day");
|
||||
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.XXX, "XXX/Movies");
|
||||
caps.Categories.AddCategoryMapping(15, NewznabStandardCategory.XXXPack, "XXX/Packs");
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
|
||||
var torrentInfo = new TorrentInfo
|
||||
{
|
||||
Guid = GetGuid(torrent),
|
||||
Title = torrent.release_name,
|
||||
Title = WebUtility.HtmlDecode(torrent.release_name),
|
||||
Categories = new List<IndexerCategory> { NewznabStandardCategory.Movies },
|
||||
Size = torrent.size * 1000 * 1000,
|
||||
DownloadUrl = torrent.download_url,
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
pageableRequests.Add(BuildRequest(searchCriteria.SearchTerm, searchCriteria.ImdbId));
|
||||
pageableRequests.Add(BuildRequest(searchCriteria.SearchTerm, searchCriteria.FullImdbId));
|
||||
|
||||
return pageableRequests;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Indexers.Definitions.TorrentPotato
|
||||
}
|
||||
}
|
||||
|
||||
public class TorrentPotatoSettings : IIndexerSettings
|
||||
public class TorrentPotatoSettings : ITorrentIndexerSettings
|
||||
{
|
||||
private static readonly TorrentPotatoSettingsValidator Validator = new ();
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -65,7 +65,7 @@ public class Uniotaku : TorrentIndexerBase<UniotakuSettings>
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = await parser.ParseDocumentAsync(response.Content);
|
||||
var errorMessage = dom.QuerySelector(".login-content span.text-red")?.TextContent.Trim();
|
||||
|
||||
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
|
||||
@@ -92,7 +92,7 @@ public class Uniotaku : TorrentIndexerBase<UniotakuSettings>
|
||||
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = parser.ParseDocument(response.Content);
|
||||
var downloadLink = dom.QuerySelector("a[href^=\"download.php?id=\"]")?.GetAttribute("href")?.Trim();
|
||||
|
||||
if (downloadLink == null)
|
||||
@@ -250,9 +250,9 @@ public class UniotakuParser : IParseIndexerResponse
|
||||
var publishDate = DateTime.Now;
|
||||
foreach (var item in jsonContent.Value<JArray>("data"))
|
||||
{
|
||||
var detailsDom = parser.ParseDocument(item.SelectToken("[0]").Value<string>());
|
||||
var categoryDom = parser.ParseDocument(item.SelectToken("[1]").Value<string>());
|
||||
var groupDom = parser.ParseDocument(item.SelectToken("[7]").Value<string>());
|
||||
using var detailsDom = parser.ParseDocument(item.SelectToken("[0]").Value<string>());
|
||||
using var categoryDom = parser.ParseDocument(item.SelectToken("[1]").Value<string>());
|
||||
using var groupDom = parser.ParseDocument(item.SelectToken("[7]").Value<string>());
|
||||
|
||||
var qTitleLink = detailsDom.QuerySelector("a[href^=\"torrents-details.php?id=\"]");
|
||||
var title = qTitleLink?.TextContent.Trim();
|
||||
|
||||
@@ -72,7 +72,7 @@ public class XSpeeds : TorrentIndexerBase<UserPassTorrentBaseSettings>
|
||||
if (CheckIfLoginNeeded(response))
|
||||
{
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(response.Content);
|
||||
using var dom = await parser.ParseDocumentAsync(response.Content);
|
||||
var errorMessage = dom.QuerySelector(".left_side table:nth-of-type(1) tr:nth-of-type(2)")?.TextContent.Trim().Replace("\n\t", " ");
|
||||
if (errorMessage.IsNullOrWhiteSpace())
|
||||
{
|
||||
@@ -289,7 +289,7 @@ public class XSpeedsParser : IParseIndexerResponse
|
||||
var releaseInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
using var dom = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var rows = dom.QuerySelectorAll("table#sortabletable > tbody > tr:has(a[href*=\"details.php?id=\"])");
|
||||
foreach (var row in rows)
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
// The first page set the cookies and the session_id
|
||||
var loginPage = await ExecuteAuth(new HttpRequest(Login1Url));
|
||||
var parser = new HtmlParser();
|
||||
var dom = parser.ParseDocument(loginPage.Content);
|
||||
using var dom = parser.ParseDocument(loginPage.Content);
|
||||
var sessionId = dom.QuerySelector("input#session_id")?.GetAttribute("value");
|
||||
if (string.IsNullOrWhiteSpace(sessionId))
|
||||
{
|
||||
@@ -315,7 +315,7 @@ namespace NzbDrone.Core.Indexers.Definitions
|
||||
var torrentInfos = new List<ReleaseInfo>();
|
||||
|
||||
var parser = new HtmlParser();
|
||||
var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
using var doc = parser.ParseDocument(indexerResponse.Content);
|
||||
|
||||
var rows = doc.QuerySelectorAll("table.torrent_list > tbody > tr");
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ namespace NzbDrone.Core.Indexers
|
||||
return result.DistinctBy(v => v.Guid).ToList();
|
||||
}
|
||||
|
||||
protected IEnumerable<ReleaseInfo> FilterReleasesByQuery(IEnumerable<ReleaseInfo> releases, SearchCriteriaBase searchCriteria)
|
||||
protected virtual IEnumerable<ReleaseInfo> FilterReleasesByQuery(IEnumerable<ReleaseInfo> releases, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var commonWords = new[] { "and", "the", "an", "of" };
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +161,7 @@ namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
releaseInfo.Guid = GetGuid(item);
|
||||
releaseInfo.Title = GetTitle(item);
|
||||
releaseInfo.Description = GetDescription(item);
|
||||
releaseInfo.PublishDate = GetPublishDate(item);
|
||||
releaseInfo.DownloadUrl = GetDownloadUrl(item);
|
||||
releaseInfo.InfoUrl = GetInfoUrl(item);
|
||||
@@ -195,6 +196,11 @@ namespace NzbDrone.Core.Indexers
|
||||
return item.TryGetValue("title", "Unknown");
|
||||
}
|
||||
|
||||
protected virtual string GetDescription(XElement item)
|
||||
{
|
||||
return item.TryGetValue("description", null);
|
||||
}
|
||||
|
||||
protected virtual ICollection<IndexerCategory> GetCategory(XElement item)
|
||||
{
|
||||
return new List<IndexerCategory> { NewznabStandardCategory.Other };
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -391,7 +391,7 @@
|
||||
"UpdateAvailable": "La nueva actualización está disponible",
|
||||
"Genre": "Géneros",
|
||||
"Publisher": "Editor",
|
||||
"AuthenticationRequired": "Autenticación Requerida",
|
||||
"AuthenticationRequired": "Autenticación requerida",
|
||||
"ApplyChanges": "Aplicar Cambios",
|
||||
"CountIndexersSelected": "{0} indexador(es) seleccionado(s)",
|
||||
"CountDownloadClientsSelected": "{0} cliente(s) de descarga seleccionado(s)",
|
||||
@@ -424,12 +424,19 @@
|
||||
"None": "Ninguna",
|
||||
"ResetAPIKeyMessageText": "¿Está seguro de que desea restablecer su clave API?",
|
||||
"EditIndexerProxyImplementation": "Agregar Condición - { implementationName}",
|
||||
"AppUpdated": "{appName} Actualizado",
|
||||
"AppUpdated": "{appName} Actualizada",
|
||||
"AppUpdatedVersion": "{appName} ha sido actualizado a la versión `{version}`, para obtener los cambios más recientes, necesitaras recargar {appName}",
|
||||
"AddApplicationImplementation": "Agregar Condición - { implementationName}",
|
||||
"AddConnectionImplementation": "Agregar Condición - { implementationName}",
|
||||
"AddConnectionImplementation": "Añadir Conexión - {implementationName}",
|
||||
"AddIndexerImplementation": "Agregar Condición - { implementationName}",
|
||||
"AddIndexerProxyImplementation": "Agregar Condición - { implementationName}",
|
||||
"EditApplicationImplementation": "Agregar Condición - { implementationName}",
|
||||
"EditConnectionImplementation": "Agregar Condición - { implementationName}"
|
||||
"EditConnectionImplementation": "Agregar Condición - { implementationName}",
|
||||
"AddDownloadClientImplementation": "Añadir Cliente de Descarga - {implementationName}",
|
||||
"AuthenticationMethod": "Método de autenticación",
|
||||
"AuthenticationMethodHelpTextWarning": "Por favor selecciona un método válido de autenticación",
|
||||
"AuthenticationRequiredHelpText": "Cambiar para que las solicitudes requieran autenticación. No lo cambie a menos que entienda los riesgos.",
|
||||
"AuthenticationRequiredPasswordHelpTextWarning": "Introduzca una nueva contraseña",
|
||||
"AuthenticationRequiredUsernameHelpTextWarning": "Introduzca un nuevo nombre de usuario",
|
||||
"AuthenticationRequiredWarning": "Para evitar el acceso remoto sin autenticación, {appName} ahora requiere que la autenticación esté habilitada. Opcionalmente puede desactivar la autenticación desde una dirección local."
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"Indexers": "Tietolähteet",
|
||||
"MovieIndexScrollBottom": "Elokuvakirjasto: vieritä alas",
|
||||
"SSLCertPassword": "SSL-varmenteen salasana",
|
||||
"Style": "Tyyli",
|
||||
"Style": "Ulkoasu",
|
||||
"Tags": "Tunnisteet",
|
||||
"Today": "Tänään",
|
||||
"About": "Tietoja",
|
||||
@@ -22,7 +22,7 @@
|
||||
"ApplicationStatusCheckSingleClientMessage": "Sovellukset eivät ole käytettävissä virheiden vuoksi: {0}",
|
||||
"Date": "Päiväys",
|
||||
"Dates": "Päiväykset",
|
||||
"SettingsTimeFormat": "Ajan esitystapa",
|
||||
"SettingsTimeFormat": "Kellonajan esitys",
|
||||
"Message": "Viesti",
|
||||
"Seeders": "Jakajat",
|
||||
"TestAll": "Testaa kaikki",
|
||||
@@ -48,15 +48,15 @@
|
||||
"RestartRequiredHelpTextWarning": "Käyttöönotto vaatii uudelleenkäynnistyksen.",
|
||||
"Result": "Tulos",
|
||||
"Settings": "Asetukset",
|
||||
"SettingsLongDateFormat": "Päiväyksen pitkä esitystapa",
|
||||
"SettingsShortDateFormat": "Päiväyksen lyhyt esitystapa",
|
||||
"SettingsLongDateFormat": "Pitkän päiväyksen esitys",
|
||||
"SettingsShortDateFormat": "Lyhyen päiväyksen esitys",
|
||||
"UnselectAll": "Poista kaikkien valinta",
|
||||
"UpdateCheckStartupTranslocationMessage": "Päivitystä ei voi asentaa, koska käynnistyskansio '{0}' sijaitsee 'App Translocation' -kansiossa.",
|
||||
"UpdateCheckUINotWritableMessage": "Päivitystä ei voi asentaa, koska käyttäjällä '{1}' ei ole kirjoitusoikeutta käyttöliittymäkansioon '{0}'.",
|
||||
"UpdateMechanismHelpText": "Käytä Prowlarrin sisäänrakennettua päivitystoimintoa tai omaa komentosarjaasi.",
|
||||
"Enable": "Käytä",
|
||||
"UI": "Käyttöliittymä",
|
||||
"UrlBaseHelpText": "Käänteisen välityspalvelimen tuki (esim. 'http://[host]:[port]/[urlBase]'). Käytä oletusta jättämällä tyhjäksi.",
|
||||
"UrlBaseHelpText": "Käänteisen välityspalvelimen tuki (esim. \"http://[host]:[port]/[urlBase]\"). Käytä oletusta jättämällä tyhjäksi.",
|
||||
"Usenet": "Usenet",
|
||||
"BackupNow": "Varmuuskopioi nyt",
|
||||
"NoBackupsAreAvailable": "Varmuuskopioita ei ole saatavilla",
|
||||
@@ -71,8 +71,8 @@
|
||||
"NoTagsHaveBeenAddedYet": "Tunnisteita ei ole vielä lisätty.",
|
||||
"ApplyTags": "Tunnistetoimenpide",
|
||||
"Authentication": "Todennus",
|
||||
"AuthenticationMethodHelpText": "Vaadi käyttäjätunnus ja salasana.",
|
||||
"BindAddressHelpText": "Toimiva IP-osoite, localhost tai '*' (tähti) kaikille yhteyksille.",
|
||||
"AuthenticationMethodHelpText": "Vaadi käyttäjätunnus ja salasana {appName}in käyttöön.",
|
||||
"BindAddressHelpText": "Toimiva IP-osoite, \"localhost\" tai \"*\" (tähti) kaikille verkkoliitännöille.",
|
||||
"Close": "Sulje",
|
||||
"DeleteNotification": "Poista kytkentä",
|
||||
"Docker": "Docker",
|
||||
@@ -96,7 +96,7 @@
|
||||
"Protocol": "Protokolla",
|
||||
"ProxyCheckBadRequestMessage": "Välityspalvelintesti epäonnistui. Tilakoodi: {0}",
|
||||
"ProxyCheckFailedToTestMessage": "Välityspalvelintesti epäonnistui: {0}",
|
||||
"ProxyCheckResolveIpMessage": "Määritetyn välityspalvelimen '{0}' IP-osoitteen selvitys epäonnistui.",
|
||||
"ProxyCheckResolveIpMessage": "Määritetyn välityspalvelimen \"{0}\" IP-osoitteen selvitys epäonnistui.",
|
||||
"ProxyPasswordHelpText": "Käyttäjätunnus ja salasana tulee syöttää vain tarvittaessa. Muussa tapauksessa jätä kentät tyhjiksi.",
|
||||
"ProxyType": "Välityspalvelimen tyyppi",
|
||||
"ProxyUsernameHelpText": "Käyttäjätunnus ja salasana tulee syöttää vain tarvittaessa. Muussa tapauksessa jätä kentät tyhjiksi.",
|
||||
@@ -145,7 +145,7 @@
|
||||
"All": "Kaikki",
|
||||
"AllIndexersHiddenDueToFilter": "Aktiivinen suodatin on piilottanut kaikki tietolähteet.",
|
||||
"Analytics": "Analytiikka",
|
||||
"AnalyticsEnabledHelpText": "Lähetä nimettömiä käyttö- ja virhetietoja sovelluksen palvelimille. Tämä sisältää tietoja selaimestasi, verkkokäyttöliittymän sivujen käytöstä, virheraportoinnista sekä käyttöjärjestelmästäsi ja versiosta. Käytämme näitä tietoja ominaisuuksien ja virhekorjauksien painotukseen.",
|
||||
"AnalyticsEnabledHelpText": "Lähetä nimettömiä käyttö- ja virhetietoja palvelimillemme. Tämä sisältää tietoja selaimestasi, käyttöliittymän sivujen käytöstä, virheraportoinnista, käyttöjärjestelmästä ja suoritusalustasta. Käytämme näitä tietoja ominaisuuksien ja vikakorjausten painotukseen.",
|
||||
"ApiKey": "API-avain",
|
||||
"AppDataDirectory": "AppData-kansio",
|
||||
"DBMigration": "Tietokannan siirto",
|
||||
@@ -181,7 +181,7 @@
|
||||
"Discord": "Discord",
|
||||
"Donations": "Lahjoitukset",
|
||||
"Edit": "Muokkaa",
|
||||
"EnableAutomaticSearchHelpText": "Profiilia käytetään automaattihaun yhteydessä, kun haku suoritetaan käyttöliittymästä tai Prowlarrin toimesta.",
|
||||
"EnableAutomaticSearchHelpText": "Profiilia käytetään automaattihauille, jotka suoritetaan käyttöliittymästä tai Prowlarrin toimesta.",
|
||||
"Enabled": "Käytössä",
|
||||
"EventType": "Tapahtumatyyppi",
|
||||
"Exception": "Poikkeus",
|
||||
@@ -199,9 +199,9 @@
|
||||
"SaveChanges": "Tallenna muutokset",
|
||||
"SaveSettings": "Tallenna asetukset",
|
||||
"Scheduled": "Ajoitettu",
|
||||
"SettingsEnableColorImpairedModeHelpText": "Muokattu tyyli käyttäjille, joiden värinäkö on heikentynyt. Auttaa erottamaan värikoodatun tiedon.",
|
||||
"SettingsShowRelativeDates": "Näytä suhteutetut päiväykset",
|
||||
"SettingsShowRelativeDatesHelpText": "Näytä suhteutetut (tänään/eilen/yms.) tai absoluuttiset päiväykset.",
|
||||
"SettingsEnableColorImpairedModeHelpText": "Vaihtoehtoinen tyyli, joka auttaa erottamaan värikoodatut tiedot paremmin",
|
||||
"SettingsShowRelativeDates": "Suhteellisten päiväysten esitys",
|
||||
"SettingsShowRelativeDatesHelpText": "Näytä suhteutetut (tänään/eilen/yms.) absoluuttisten sijaan",
|
||||
"ShowSearch": "Näytä haku",
|
||||
"Source": "Lähde",
|
||||
"SSLPort": "SSL-portti",
|
||||
@@ -435,7 +435,7 @@
|
||||
"EditSyncProfile": "Muokkaa synkronointiprofiilia",
|
||||
"InstanceName": "Instanssin nimi",
|
||||
"InstanceNameHelpText": "Instanssin nimi välilehdellä ja järjestelmälokissa",
|
||||
"ThemeHelpText": "Vaihda sovelluksen käyttöliittymän ulkoasua. \"Automaattinen\" vaihtaa vaalean ja tumman tilan käyttöjärjestelmäsi teemaa vastaavaksi. Innoittanut {0}.",
|
||||
"ThemeHelpText": "Vaihda sovelluksen käyttöliittymän ulkoasu. \"Automaattinen\" vaihtaa vaalean ja tumman tilan välillä järjestelmän teeman mukaan. Innoittanut Theme.Park.",
|
||||
"Duration": "Kesto",
|
||||
"ElapsedTime": "Kulunut aika",
|
||||
"EnabledRedirected": "Kulunut, uudelleenohjattu",
|
||||
@@ -485,11 +485,11 @@
|
||||
"Episode": "Jakso",
|
||||
"Label": "Tunniste",
|
||||
"Theme": "Teema",
|
||||
"ConnectionLostReconnect": "Radarr pyrkii muodostamaan yhteyden automaattisesti tai voit painaa alta \"Lataa uudelleen\".",
|
||||
"ConnectionLostReconnect": "{appName} pyrkii ajoittain muodostamaan yhteyden automaattisesti tai sitä voidaan yrittää manuaalisesti painamalla alta \"Lataa uudelleen\".",
|
||||
"DeleteAppProfileMessageText": "Haluatko varmasti poistaa laatuprofiilin '{0}'?",
|
||||
"RecentChanges": "Viimeaikaiset muutokset",
|
||||
"WhatsNew": "Mikä on uutta?",
|
||||
"ConnectionLostToBackend": "Radarr on menettänyt yhteyden taustajärjestelmään ja sivu on päivitettävä toiminnallisuuden palauttamiseksi.",
|
||||
"ConnectionLostToBackend": "{appName} kadotti yhteyden taustajärjestelmään ja käytettävyyden palauttamiseksi se on ladattava uudelleen.",
|
||||
"minutes": "Minuuttia",
|
||||
"AddConnection": "Lisää yhteys",
|
||||
"NotificationStatusAllClientHealthCheckMessage": "Sovellukset eivät ole käytettävissä virheiden vuoksi",
|
||||
@@ -498,5 +498,11 @@
|
||||
"AuthForm": "Lomake (kirjautumissivu)",
|
||||
"DisabledForLocalAddresses": "Ei käytetä paikallisille osoittelle",
|
||||
"None": "Ei mitään",
|
||||
"ResetAPIKeyMessageText": "Haluatko varmasti uudistaa API-avaimesi?"
|
||||
"ResetAPIKeyMessageText": "Haluatko varmasti uudistaa API-avaimesi?",
|
||||
"TotalIndexerSuccessfulGrabs": "Onnistuneiden tietolähdesieppausten kokonaismäärä",
|
||||
"AppUpdated": "{appName} on päivitetty",
|
||||
"AppUpdatedVersion": "{appName} on päivitetty versioon {version} ja muutosten käyttöönottamiseksi se on ladattava uudelleen.",
|
||||
"IndexerDownloadClientHelpText": "Määritä tämän tietolähteen kanssa käytettävä lataustyökalu",
|
||||
"AuthenticationRequiredWarning": "Etäkäytön estämiseksi ilman tunnistautumista {appName} vaatii nyt todennuksen käyttöönoton. Todennus voidaan poistaa käytöstä paikallisille osoitteille.",
|
||||
"TotalGrabs": "Sieppausten kokonaismäärä"
|
||||
}
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
"Indexers": "Indexeurs",
|
||||
"Host": "Hôte",
|
||||
"History": "Historique",
|
||||
"HideAdvanced": "Masquer avancé",
|
||||
"HideAdvanced": "Masquer param. av.",
|
||||
"Health": "Santé",
|
||||
"General": "Général",
|
||||
"Folder": "Dossier",
|
||||
"Filter": "Filtre",
|
||||
"Filter": "Filtrer",
|
||||
"Files": "Fichiers",
|
||||
"Events": "Événements",
|
||||
"Edit": "Éditer",
|
||||
"Edit": "Modifier",
|
||||
"DownloadClientStatusCheckAllClientMessage": "Aucun client de téléchargement n'est disponible en raison d'échecs",
|
||||
"DownloadClients": "Clients 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 Tags",
|
||||
"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éselectionner",
|
||||
"UnselectAll": "Tout désélectionner",
|
||||
"UISettingsSummary": "Date, langue, et perceptions des couleurs",
|
||||
"TagsSettingsSummary": "Voir tous les tags et leur utilisation. Les tags inutilisés peuvent être supprimés",
|
||||
"TagsSettingsSummary": "Voir toutes les balises et comment elles sont utilisées. Les balises inutilisées peuvent être supprimées",
|
||||
"Style": "Style",
|
||||
"Status": "Statut",
|
||||
"Status": "État",
|
||||
"Sort": "Trier",
|
||||
"Size": "Taille",
|
||||
"ShowAdvanced": "Afficher avancés",
|
||||
"ShowAdvanced": "Afficher les paramètres avancés",
|
||||
"Settings": "Paramètres",
|
||||
"SelectAll": "Tout sélectionner",
|
||||
"Security": "Sécurité",
|
||||
@@ -49,23 +49,23 @@
|
||||
"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}",
|
||||
"ProxyCheckBadRequestMessage": "Échec du test du proxy. StatusCode : {0}",
|
||||
"ProxyCheckBadRequestMessage": "Échec du test du proxy. Code d'état : {0}",
|
||||
"Proxy": "Proxy",
|
||||
"Protocol": "Protocole",
|
||||
"Options": "Options",
|
||||
"NoChanges": "Aucun changement",
|
||||
"NoChanges": "Aucuns changements",
|
||||
"NoChange": "Pas de changement",
|
||||
"MoreInfo": "Plus d'informations",
|
||||
"Grabbed": "Récupéré",
|
||||
"DownloadClientsSettingsSummary": "Clients de Téléchargement configuration pour l'intégration dans la recherche de l'interface utilisateur Prowlarr",
|
||||
"DownloadClient": "Client de Téléchargement",
|
||||
"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",
|
||||
"Logging": "Enregistrement",
|
||||
"LogFiles": "Fichiers Log",
|
||||
"View": "Vue",
|
||||
"LogFiles": "Fichiers journaux",
|
||||
"View": "Vues",
|
||||
"Updates": "Mises à jour",
|
||||
"UI": "UI",
|
||||
"Tasks": "Tâches",
|
||||
@@ -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,18 +116,18 @@
|
||||
"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": "Changement non sauvegardés",
|
||||
"ShowSearchHelpText": "Afficher le bouton de recherche au survol de la souris",
|
||||
"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",
|
||||
"SettingsShowRelativeDatesHelpText": "Afficher les dates relatives (aujourd'hui, hier, etc.) ou absolues",
|
||||
"SettingsShortDateFormat": "Format de date court",
|
||||
"SettingsLongDateFormat": "Format de date long",
|
||||
"SettingsEnableColorImpairedModeHelpText": "Style altéré pour aider les personnes daltoniennes à distinguer les informations en couleurs",
|
||||
"SettingsEnableColorImpairedMode": "Activer le mode daltonien",
|
||||
"PendingChangesStayReview": "Rester et vérifier les changements",
|
||||
"PendingChangesMessage": "Vous avez effectué des changements non sauvegardés, souhaitez vous quitter cette page ?",
|
||||
"PendingChangesDiscardChanges": "Abandonner les changements et quitter",
|
||||
"PendingChangesStayReview": "Rester et vérifier les modifications",
|
||||
"PendingChangesMessage": "Vous avez des modifications non sauvegardées, voulez-vous vraiment quitter cette page ?",
|
||||
"PendingChangesDiscardChanges": "Abandonner les modifications et quitter",
|
||||
"CloneProfile": "Dupliqué le profil",
|
||||
"ClientPriority": "Priorité du client",
|
||||
"ChangeHasNotBeenSavedYet": "Les changements n'ont pas encore été sauvegardés",
|
||||
@@ -135,28 +135,28 @@
|
||||
"CertificateValidation": "Validation du certificat",
|
||||
"BypassProxyForLocalAddresses": "Contourner le proxy pour les adresses locales",
|
||||
"Branch": "Branche",
|
||||
"BindAddressHelpText": "Adresse IP valide, localhost ou '*' pour toutes les interfaces",
|
||||
"BindAddress": "Adresse d'attache",
|
||||
"BindAddressHelpText": "Adresse IP valide, localhost ou « * » pour toutes les interfaces",
|
||||
"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",
|
||||
"Automatic": "Automatique",
|
||||
"AuthenticationMethodHelpText": "Exiger un identifiant et un mot de passe pour accéder à Prowlarr",
|
||||
"AuthenticationMethodHelpText": "Exiger un nom d'utilisateur et un mot de passe pour accéder à {appName}",
|
||||
"Authentication": "Authentification",
|
||||
"ApplyTags": "Appliquer les tags",
|
||||
"ApplyTags": "Appliquer les étiquettes",
|
||||
"AppDataDirectory": "Dossier AppData",
|
||||
"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",
|
||||
@@ -170,8 +170,8 @@
|
||||
"BranchUpdateMechanism": "Branche utilisée par le mécanisme de mise à jour extérieur",
|
||||
"BranchUpdate": "Branche à utiliser pour mettre Prowlarr à jour",
|
||||
"BeforeUpdate": "Avant la mise à jour",
|
||||
"DeleteDownloadClientMessageText": "Êtes-vous sûr de vouloir supprimer le client de téléchargement '{0}' ?",
|
||||
"DeleteBackupMessageText": "Êtes-vous sûr de vouloir supprimer la sauvegarde '{0}' ?",
|
||||
"DeleteDownloadClientMessageText": "Voulez-vous supprimer le client de téléchargement « {name} » ?",
|
||||
"DeleteBackupMessageText": "Voulez-vous supprimer la sauvegarde « {name} » ?",
|
||||
"ErrorLoadingContents": "Erreur lors du chargement du contenu",
|
||||
"EnableInteractiveSearchHelpText": "Sera utilisé lorsque la recherche interactive est utilisée",
|
||||
"EnableAutomaticSearchHelpText": "Sera utilisé lorsque les recherches automatiques sont effectuées via l'interface utilisateur ou par Prowlarr",
|
||||
@@ -180,7 +180,7 @@
|
||||
"Exception": "Exception",
|
||||
"EditIndexer": "Modifier l'indexeur",
|
||||
"Disabled": "Désactivé",
|
||||
"DeleteNotificationMessageText": "Êtes-vous sûr de vouloir supprimer la notification '{0}' ?",
|
||||
"DeleteNotificationMessageText": "Voulez-vous supprimer la notification « {name} » ?",
|
||||
"AutomaticSearch": "Recherche automatique",
|
||||
"AddIndexer": "Ajouter un indexeur",
|
||||
"Interval": "Intervalle",
|
||||
@@ -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,19 +198,19 @@
|
||||
"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": "Zone de recherche de focus",
|
||||
"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",
|
||||
"FocusSearchBox": "Placer le curseur sur la barre de recherche",
|
||||
"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 balises",
|
||||
"UnableToLoadTags": "Impossible de charger les étiquettes",
|
||||
"UnableToLoadHistory": "Impossible de charger l'historique",
|
||||
"UnableToLoadGeneralSettings": "Impossible de charger les paramètres généraux",
|
||||
"UnableToLoadDownloadClients": "Impossible de charger les clients de téléchargement",
|
||||
@@ -218,16 +218,16 @@
|
||||
"UnableToAddANewNotificationPleaseTryAgain": "Impossible d'ajouter une nouvelle notification, veuillez réessayer.",
|
||||
"UnableToAddANewIndexerPleaseTryAgain": "Impossible d'ajouter un nouvel indexeur, veuillez réessayer.",
|
||||
"UnableToAddANewDownloadClientPleaseTryAgain": "Impossible d'ajouter un nouveau client de téléchargement, veuillez réessayer.",
|
||||
"TagIsNotUsedAndCanBeDeleted": "La balise n'est pas utilisée et peut être supprimée",
|
||||
"TagsHelpText": "S'applique aux films avec au moins une balise correspondante",
|
||||
"StartTypingOrSelectAPathBelow": "Commencer à taper ou sélectionner un chemin ci-dessous",
|
||||
"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 identification n'a été ajoutée pour l'instant",
|
||||
"IndexerFlags": "Indicateurs d'indexeur",
|
||||
"DeleteTagMessageText": "Voulez-vous vraiment supprimer la balise '{0}' ?",
|
||||
"DeleteTagMessageText": "Voulez-vous vraiment supprimer l'étiquette « {label} » ?",
|
||||
"UISettings": "Paramètres UI",
|
||||
"UILanguageHelpTextWarning": "Rechargement du navigateur requis",
|
||||
"UILanguageHelpText": "Langue que Prowlarr utilisera pour l'interface utilisateur",
|
||||
"UILanguage": "UI Langue",
|
||||
"UILanguage": "Langue de l'IU",
|
||||
"Torrents": "Torrents",
|
||||
"TestAllClients": "Tester tous les clients",
|
||||
"TagCannotBeDeletedWhileInUse": "Ne peut pas être supprimé pendant l'utilisation",
|
||||
@@ -250,40 +250,40 @@
|
||||
"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 film",
|
||||
"ReadTheWikiForMoreInformation": "Consultez le Wiki pour plus d'informations",
|
||||
"RefreshMovie": "Actualiser le film",
|
||||
"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",
|
||||
"MovieIndexScrollTop": "Index des films : faire défiler vers le haut",
|
||||
"MovieIndexScrollBottom": "Index des Films : faire défiler vers le bas",
|
||||
"MovieIndexScrollBottom": "Index des films : faire défiler vers le bas",
|
||||
"MIA": "MIA",
|
||||
"LaunchBrowserHelpText": " Ouvrer un navigateur Web et accéder à la page d'accueil de Prowlarr au démarrage de l'application.",
|
||||
"CloseCurrentModal": "Fermer le modal actuel",
|
||||
"AddingTag": "Ajouter un tag",
|
||||
"CloseCurrentModal": "Fermer cette fenêtre modale",
|
||||
"AddingTag": "Ajout d'une étiquette",
|
||||
"OnHealthIssueHelpText": "Sur un problème de santé",
|
||||
"AcceptConfirmationModal": "Accepter les modalités d'utilisations",
|
||||
"OpenThisModal": "Ouvrer ce modal",
|
||||
"OpenThisModal": "Ouvrir cette fenêtre modale",
|
||||
"IndexerLongTermStatusCheckSingleClientMessage": "Indexeurs indisponibles en raison de pannes pendant plus de 6 heures : {0}",
|
||||
"IndexerLongTermStatusCheckAllClientMessage": "Tous les indexeurs sont indisponibles en raison d'échecs de plus de 6 heures",
|
||||
"Yesterday": "Hier",
|
||||
@@ -311,9 +311,9 @@
|
||||
"EnableRss": "Activer RSS",
|
||||
"EnableIndexer": "Activer l'indexeur",
|
||||
"DevelopmentSettings": "Paramètres de développement",
|
||||
"DeleteApplicationMessageText": "Etes-vous sûr de vouloir supprimer l'application '{0}' ?",
|
||||
"DeleteApplicationMessageText": "Voulez-vous supprimer l'application « {name} » ?",
|
||||
"DeleteApplication": "Supprimer l'application",
|
||||
"ClearHistoryMessageText": "Vous êtes sûr de vouloir effacer tout l'historique de Prowlarr ?",
|
||||
"ClearHistoryMessageText": "Voulez-vous vraiment effacer tout l'historique de Prowlarr ?",
|
||||
"ClearHistory": "Effacer l'historique",
|
||||
"ApplicationStatusCheckSingleClientMessage": "Applications indisponibles en raison de dysfonctionnements : {0}",
|
||||
"ApplicationStatusCheckAllClientMessage": "Toutes les applications sont indisponibles en raison de dysfonctionnements",
|
||||
@@ -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",
|
||||
@@ -347,7 +347,7 @@
|
||||
"Grabs": "Complétés",
|
||||
"Id": "Id",
|
||||
"Presets": "Préconfigurations",
|
||||
"Privacy": "Vie privée",
|
||||
"Privacy": "Visibilité",
|
||||
"Query": "Requête",
|
||||
"Stats": "Stats",
|
||||
"Torrent": "Torrent",
|
||||
@@ -363,14 +363,14 @@
|
||||
"AddDownloadClientToProwlarr": "L'ajout d'un client de téléchargement permet à Prowlarr d'envoyer des fichers directement depuis l'interface utilisateur tout en effectuant une recherche manuelle.",
|
||||
"NoSearchResultsFound": "Aucun résultat de recherche trouvé, essayez d'effectuer une nouvelle recherche ci-dessous.",
|
||||
"DeleteIndexerProxy": "Supprimer le proxy indexeur",
|
||||
"DeleteIndexerProxyMessageText": "Êtes-vous sur de vouloir supprimer le proxy '{0}' ?",
|
||||
"DeleteIndexerProxyMessageText": "Voulez-vous supprimer le proxy d'indexeur « {name} » ?",
|
||||
"AddIndexerProxy": "Ajouter proxy indexeur",
|
||||
"AppSettingsSummary": "Applications et paramètres pour configurer comment Prowlarr interagit avec vos programmes PVR",
|
||||
"IndexerTagsHelpText": "Utilisez des balises pour spécifier les proxys d'indexation ou les applications avec lesquelles l'indexeur est synchronisé. Les applications sans 1 ou plusieurs balises d'indexation correspondantes ne seront pas synchronisées.",
|
||||
"IndexerTagsHelpText": "Utilisez des étiquettes pour spécifier les proxies d'indexation ou les applications avec lesquelles l'indexeur est synchronisé.",
|
||||
"Notifications": "Notifications",
|
||||
"IndexerVipCheckExpiredClientMessage": "Les avantages VIP de l'indexeur ont expiré : {0}",
|
||||
"IndexerProxy": "Proxy d'indexation",
|
||||
"IndexerSettingsSummary": "Configuration de divers paramètres globaux de l'indexeur, y compris les proxys.",
|
||||
"IndexerSettingsSummary": "Configuration de divers paramètres globaux de l'indexeur, y compris les proxies.",
|
||||
"IndexerProxies": "Proxys d'indexation",
|
||||
"IndexerProxyStatusCheckAllClientMessage": "Tous les proxys sont indisponibles en raison d'échecs",
|
||||
"IndexerProxyStatusCheckSingleClientMessage": "Proxys indisponibles en raison d'échecs : {0}",
|
||||
@@ -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é",
|
||||
@@ -440,7 +440,7 @@
|
||||
"Started": "Démarré",
|
||||
"EditSyncProfile": "Modifier le profil de synchronisation",
|
||||
"AddSyncProfile": "Ajouter un profil de synchronisation",
|
||||
"ThemeHelpText": "Changez le thème de l'interface de l'application. Le thème \"Auto\" utilisera celui de votre système d'exploitation pour définir le mode clair ou foncé. Inspiré par {0}",
|
||||
"ThemeHelpText": "Changez le thème de l'interface de l'application. Le thème « Auto » utilisera celui de votre système d'exploitation pour déterminer entre le mode clair ou foncé. Inspiré par {inspiredBy}.",
|
||||
"SyncProfile": "Profil de synchronisation",
|
||||
"SyncProfiles": "Profils de synchronisation",
|
||||
"MinimumSeeders": "Nombre minimum de seeders",
|
||||
@@ -449,18 +449,18 @@
|
||||
"ElapsedTime": "Temps écoulé",
|
||||
"Parameters": "Paramètres",
|
||||
"AuthenticationRequired": "Authentification requise",
|
||||
"AuthenticationRequiredHelpText": "Modifier les demandes pour lesquelles l'authentification est requise. Ne changez rien si vous ne comprenez pas les risques.",
|
||||
"AuthenticationRequiredHelpText": "Modifier les demandes pour lesquelles l'authentification est requise. Ne rien modifier si vous n'en comprenez pas les risques.",
|
||||
"DeleteClientCategory": "Supprimer la catégorie du client de téléchargement",
|
||||
"DownloadClientCategory": "Catégorie du client de téléchargement",
|
||||
"MappedCategories": "Catégories mappées",
|
||||
"EnabledRedirected": "Activé, Redirigé",
|
||||
"GrabTitle": "Récupérer le titre",
|
||||
"AuthenticationRequiredWarning": "Pour empêcher l'accès à distance sans authentification, Prowlarr exige désormais que l'authentification soit activée. Configurez votre méthode d'authentification et vos informations d'identification. Vous pouvez éventuellement désactiver l'authentification à partir des adresses locales. Reportez-vous à la FAQ pour plus d'informations.",
|
||||
"AuthenticationRequiredWarning": "Pour empêcher l'accès à distance sans authentification, {appName} exige désormais que l'authentification soit activée. Vous pouvez éventuellement désactiver l'authentification pour les adresses locales.",
|
||||
"Remove": "Retirer",
|
||||
"Replace": "Remplacer",
|
||||
"TheLatestVersionIsAlreadyInstalled": "La dernière version de Prowlarr est déjà installée",
|
||||
"TheLatestVersionIsAlreadyInstalled": "La dernière version de {appName} est déjà installée",
|
||||
"AddCustomFilter": "Ajouter filtre personnalisé",
|
||||
"AddApplication": "Ajouter application",
|
||||
"AddApplication": "Ajouter une application",
|
||||
"IncludeManualGrabsHelpText": "Inclure les saisies manuelles effectuées dans Prowlarr",
|
||||
"OnGrabHelpText": "Récupéré à la sortie",
|
||||
"RssFeed": "Flux RSS",
|
||||
@@ -480,7 +480,7 @@
|
||||
"Book": "Livre",
|
||||
"Artist": "Artiste",
|
||||
"Author": "Auteur",
|
||||
"AverageResponseTimesMs": "Temps de réponse moyen (Ms)",
|
||||
"AverageResponseTimesMs": "Temps de réponse moyen des indexeurs (ms)",
|
||||
"IndexerFailureRate": "Taux d'échec de l'indexeur",
|
||||
"Label": "Label",
|
||||
"More": "Plus",
|
||||
@@ -494,57 +494,108 @@
|
||||
"Year": "Année",
|
||||
"ApplicationURL": "URL de l'application",
|
||||
"ApiKeyValidationHealthCheckMessage": "Veuillez mettre à jour votre clé API pour qu'elle contienne au moins {0} caractères. Vous pouvez le faire via les paramètres ou le fichier de configuration",
|
||||
"ApplicationUrlHelpText": "URL externe de cette application, y compris http(s)://, le port ainsi que la base de URL",
|
||||
"ApplicationUrlHelpText": "L'URL externe de cette application, y compris http(s)://, le port ainsi que la base de URL",
|
||||
"ApplyChanges": "Appliquer les modifications",
|
||||
"ApplyTagsHelpTextAdd": "Ajouter : Ajouter les tags à la liste de tags existantes",
|
||||
"ApplyTagsHelpTextHowToApplyApplications": "Comment appliquer des tags à l'auteur sélectionné",
|
||||
"CountIndexersSelected": "{0} indexeur(s) sélectionné(s)",
|
||||
"DeleteSelectedApplicationsMessageText": "Voulez-vous vraiment supprimer l'indexeur '{0}' ?",
|
||||
"DeleteSelectedIndexersMessageText": "Voulez-vous vraiment supprimer l'indexeur '{0}' ?",
|
||||
"ApplyTagsHelpTextAdd": "Ajouter : ajoute les étiquettes à la liste de étiquettes existantes",
|
||||
"ApplyTagsHelpTextHowToApplyApplications": "Comment appliquer les étiquettes aux applications sélectionnées",
|
||||
"CountIndexersSelected": "{count} indexeur(s) sélectionné(s)",
|
||||
"DeleteSelectedApplicationsMessageText": "Voulez-vous vraiment supprimer {count} application(s) sélectionnée(s) ?",
|
||||
"DeleteSelectedIndexersMessageText": "Voulez-vous vraiment supprimer les {count} indexeur(s) sélectionné(s) ?",
|
||||
"DownloadClientPriorityHelpText": "Donnez la priorité à plusieurs clients de téléchargement. Le Round-Robin est utilisé pour les clients ayant la même priorité.",
|
||||
"SelectIndexers": "Recherche indexeurs",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Comment appliquer des tags aux indexeurs sélectionnés",
|
||||
"ApplyTagsHelpTextRemove": "Suprimer : Suprime les étiquettes renseignées",
|
||||
"ApplyTagsHelpTextReplace": "Remplacer : Remplace les tags par les tags renseignés (ne pas renseigner de tags pour effacer tous les tags)",
|
||||
"DeleteSelectedDownloadClients": "Supprimer le client de téléchargement",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Voulez-vous vraiment supprimer l'indexeur '{0}' ?",
|
||||
"StopSelecting": "Arrêtez la sélection",
|
||||
"SelectIndexers": "Sélectionner les indexeurs",
|
||||
"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(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",
|
||||
"AdvancedSettingsHiddenClickToShow": "Paramètres avancés masqués, cliquez pour afficher",
|
||||
"AdvancedSettingsShownClickToHide": "Paramètres avancés affichés, cliquez pour masquer",
|
||||
"AppsMinimumSeeders": "Apps avec le nombre minimum de seeders disponibles",
|
||||
"AppsMinimumSeedersHelpText": "Minimum de seeders requis par les applications pour que l’indexeur les récupère, laisser vide utilise la valeur par défaut du profil Sync",
|
||||
"BasicSearch": "Recherche de base",
|
||||
"CountIndexersAvailable": "{0} indexeur(s) disponible(s)",
|
||||
"CountIndexersAvailable": "{count} indexeur(s) disponible(s)",
|
||||
"DeleteSelectedApplications": "Supprimer les applications sélectionnées",
|
||||
"DeleteSelectedIndexer": "Supprimer les indexeurs sélectionnés",
|
||||
"DeleteSelectedIndexers": "Supprimer les indexeurs sélectionnés",
|
||||
"EditSelectedDownloadClients": "Modifier les clients de téléchargement sélectionnés",
|
||||
"EditSelectedIndexers": "Modifier les indexeurs sélectionnés",
|
||||
"AreYouSureYouWantToDeleteIndexer": "Êtes-vous sûr de vouloir supprimer “{0}” de Prowlarr ?",
|
||||
"AreYouSureYouWantToDeleteIndexer": "Voulez-vous supprimer « {name} » de Prowlarr ?",
|
||||
"AuthQueries": "Requêtes d’authentification",
|
||||
"CountApplicationsSelected": "{0} application(s) sélectionnée(s)",
|
||||
"CountDownloadClientsSelected": "{0} client(s) de téléchargement sélectionné(s)",
|
||||
"ConnectionLostReconnect": "Radarr essaiera de se connecter automatiquement, ou vous pouvez cliquer sur \"Recharger\" en bas.",
|
||||
"ConnectionLostToBackend": "Radarr a perdu sa connexion au backend et devra être rechargé pour fonctionner à nouveau.",
|
||||
"CountApplicationsSelected": "{count} application(s) sélectionnée(s)",
|
||||
"CountDownloadClientsSelected": "{count} client(s) de téléchargement sélectionné(s)",
|
||||
"ConnectionLostReconnect": "{appName} essaiera de se connecter automatiquement, ou vous pouvez cliquer sur « Recharger » en bas.",
|
||||
"ConnectionLostToBackend": "{appName} a perdu sa connexion au backend et devra être rechargé pour fonctionner à nouveau.",
|
||||
"RecentChanges": "Changements récents",
|
||||
"WhatsNew": "Quoi de neuf ?",
|
||||
"minutes": "Minutes",
|
||||
"DeleteAppProfileMessageText": "Voulez-vous vraiment supprimer le profil de qualité {0} ?",
|
||||
"minutes": "minutes",
|
||||
"DeleteAppProfileMessageText": "Voulez-vous vraiment supprimer le profil d'application « {name} » ?",
|
||||
"AddConnection": "Ajouter une connexion",
|
||||
"AddConnectionImplementation": "Ajouter une connexion - {implementationName}",
|
||||
"AddApplicationImplementation": "Ajouter une condition - {implementationName}",
|
||||
"AddIndexerImplementation": "Ajouter une condition - {implementationName}",
|
||||
"EditConnectionImplementation": "Ajouter une connexion - {implementationName}",
|
||||
"NotificationStatusAllClientHealthCheckMessage": "Toutes les applications sont indisponibles en raison de dysfonctionnements",
|
||||
"NotificationStatusSingleClientHealthCheckMessage": "Applications indisponibles en raison de dysfonctionnements : {0}",
|
||||
"AddApplicationImplementation": "Ajouter une application - {implementationName}",
|
||||
"AddIndexerImplementation": "Ajouter un indexeur - {implementationName}",
|
||||
"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}",
|
||||
"EditIndexerProxyImplementation": "Ajouter une condition - {implementationName}",
|
||||
"AuthBasic": "Authentification de base (Basic) (popup dans le navigateur)",
|
||||
"AuthForm": "Authentification par un formulaire (page de connexion)",
|
||||
"DisabledForLocalAddresses": "Désactivé pour les adresses IP locales",
|
||||
"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": "Êtes vous sûr de vouloir réinitialiser votre Clé d'API ?",
|
||||
"AddIndexerProxyImplementation": "Ajouter une condition - {implementationName}"
|
||||
"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",
|
||||
"NoIndexersFound": "Aucun indexeur n'a été trouvé",
|
||||
"NoHistoryFound": "Aucun historique n'a été trouvé",
|
||||
"QueryType": "Type de requête",
|
||||
"SeedTimeHelpText": "Le temps qu'un torrent doit rester en seed avant de s'arrêter, la valeur vide est la valeur par défaut de l'application",
|
||||
"GoToApplication": "Aller sur l'application",
|
||||
"IndexerCategories": "Catégories d'indexeur",
|
||||
"NoDownloadClientsFound": "Aucun client de téléchargement n'a été trouvé",
|
||||
"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": "Mise en œuvre",
|
||||
"SearchCountIndexers": "Rechercher {count} indexeur(s)",
|
||||
"SearchAllIndexers": "Rechercher tous les indexeurs",
|
||||
"NewznabUrl": "URL Newznab",
|
||||
"RssQueries": "Requêtes RSS",
|
||||
"SeedTime": "Temps de seed",
|
||||
"SearchQueries": "Requêtes de recherche",
|
||||
"days": "jours",
|
||||
"ApplicationTagsHelpTextWarning": "Les étiquettes doivent être utilisées avec prudence, elles peuvent avoir des effets indésirables. Une application avec une étiquette va uniquement synchroniser les indexeurs ayant la même étiquette.",
|
||||
"DefaultNameCopiedProfile": "{name} - Copier",
|
||||
"EditCategory": "Modifier la catégorie",
|
||||
"EditDownloadClientImplementation": "Modifier le client de téléchargement - {implementationName}",
|
||||
"External": "Externe",
|
||||
"FoundCountReleases": "{itemCount} release(s) trouvée(s)",
|
||||
"IndexerHistoryLoadError": "Erreur lors du chargement de l'historique de l'indexeur",
|
||||
"IndexerTagsHelpTextWarning": "Les étiquettes doivent être utilisées avec prudence, elles peuvent avoir des effets indésirables. Un indexeur avec une étiquette va uniquement synchroniser les applications ayant la même étiquette.",
|
||||
"NoIndexerHistory": "Aucun historique n'a été trouvé pour cet indexeur",
|
||||
"PackSeedTimeHelpText": "Le temps qu'un pack (saison ou discographie) doit être seedé avant de s'arrêter, la valeur vide est la valeur par défaut de l'application",
|
||||
"SeedRatioHelpText": "Le ratio qu'un torrent doit atteindre avant de s'arrêter, une valeur vide est la valeur par défaut de l'application",
|
||||
"TorznabUrl": "URL Torznab",
|
||||
"TotalGrabs": "Récupéré au total",
|
||||
"TotalQueries": "Requêtes totales",
|
||||
"SeedRatio": "Ratio de seed",
|
||||
"SelectedCountOfCountReleases": "{selectedCount} sur {itemCount} releases sélectionnées",
|
||||
"AddCategory": "Ajouter une catégorie",
|
||||
"AddDownloadClientImplementation": "Ajouter un client de téléchargement - {implementationName}",
|
||||
"ManageDownloadClients": "Gérer les clients de téléchargement",
|
||||
"AuthenticationRequiredPasswordHelpTextWarning": "Saisir un nouveau mot de passe",
|
||||
"IndexerDownloadClientHealthCheckMessage": "Indexeurs avec des clients de téléchargement invalides : {0].",
|
||||
"AuthenticationMethod": "Méthode d'authentification",
|
||||
"AuthenticationMethodHelpTextWarning": "Veuillez choisir une méthode d'authentification valide",
|
||||
"ActiveIndexers": "Indexeurs actifs",
|
||||
"ActiveApps": "Applications actives",
|
||||
"AuthenticationRequiredUsernameHelpTextWarning": "Saisir un nouveau nom d'utilisateur",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user