Compare commits

...

31 Commits

Author SHA1 Message Date
Bogdan
f0a8d22e84 Improve Search Types selection for BHD 2024-03-16 21:25:28 +02:00
Weblate
50c6f15e12 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Davide <daddobiker@gmail.com>
Co-authored-by: Dennis Langthjem <dennis@langthjem.dk>
Co-authored-by: Gianmarco Novelli <rinogaetano94@live.it>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Ihor Mudryi <mudryy33@gmail.com>
Co-authored-by: MadaxDeLuXe <madaxdeluxe@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: infoaitek24 <info@aitekph.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translation: Servarr/Prowlarr
2024-03-16 18:12:37 +02:00
Bogdan
2e3a95f389 Remove Status from IndexerDefinition 2024-03-16 17:41:25 +02:00
Bogdan
3d52096eb4 Downgrade YamlDotNet and improve logging for definitions update 2024-03-16 17:39:03 +02:00
Mark McDowall
e981cacbda Fixed: Disabled select option still selectable
(cherry picked from commit 063dba22a803295adee4fdcbe42718af3e85ca78)
2024-03-14 16:12:35 +02:00
Mark McDowall
218371a318 Convert Queued Tasks to TS
(cherry picked from commit 6d552f2a60f44052079b5e8944f5e1bbabac56e0)
2024-03-14 16:02:43 +02:00
Bogdan
30fd7c8c2a Fix stylelint command in package.json
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-03-14 15:52:20 +02:00
Bogdan
96d2d61fa0 Add download clients notice about sync to applications 2024-03-12 23:39:20 +02:00
Mark McDowall
17ff86aaea Fixes: Missing default path for Download Station
(cherry picked from commit 4bf3ab1511b4ea25642476bf9df13f91b6f73d76)

Closes #2062
2024-03-12 13:57:35 +02:00
Bogdan
7f8c1ace14 Replace special chars in search term with wildcard for RuTracker 2024-03-11 10:28:22 +02:00
Bogdan
dc0edb7bc1 Bump YamlDotNet, AngleSharp, BenchmarkDotNet 2024-03-10 13:36:25 +02:00
Bogdan
2ac996c9f9 Bump version to 1.14.3 2024-03-10 09:07:12 +02:00
Weblate
2ebabd69b5 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Jason54 <jason54700.jg@gmail.com>
Co-authored-by: Mark Martines <mark-martines@hotmail.com>
Co-authored-by: Maxence Winandy <maxence.winandy@gmail.com>
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translation: Servarr/Prowlarr
2024-03-08 11:09:33 +02:00
Helvio Pedreschi
b3738f1602 Fixed: WebApp functionality on Apple devices
(cherry picked from commit c7dd7abf892eead7796fcc482aa2f2aabaf88712)
2024-03-08 11:05:27 +02:00
Bogdan
882152b911 Use proxied requests for indexers 2024-03-04 15:26:23 +02:00
Louis R
a25e79031f Fixed: Don't disable IPv6 in IPv6-only Environment
(cherry picked from commit 13af6f57796e54c3949cf340e03f020e6f8575c4)
2024-03-03 12:12:50 +02:00
Bogdan
cc85060b1b Bump version to 1.14.2 2024-03-03 12:11:15 +02:00
Weblate
00bd9c241a Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: GkhnGRBZ <gkhn.gurbuz@hotmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translation: Servarr/Prowlarr
2024-03-03 02:32:29 +02:00
Weblate
1283e06f95 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: aghus <aghus.m@outlook.com>
Co-authored-by: modo24ro <marius.odobasa@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translation: Servarr/Prowlarr
2024-02-29 04:00:49 +02:00
Devotee2161
ab0108778a Fixed: (AnimeBytes) Artist and album search improvements 2024-02-29 04:00:26 +02:00
Bogdan
099b04f718 Update caniuse-lite 2024-02-29 02:37:14 +02:00
Chris
ecdc0a51a9 Fixed: Cleanse Discord Webhook URLs
(cherry picked from commit d1f2a8a9486471f4986da2fa16d5439ccf0426e1)
2024-02-24 23:42:45 +02:00
Bogdan
6c7c37affe Bump node to v20.x on builder 2024-02-23 20:13:02 +02:00
Servarr
45d378a2d9 Automated API Docs update 2024-02-23 19:55:10 +02:00
Bogdan
007601cb19 Fixed: Selection of last added custom filter
Plus some translations and typos
2024-02-23 19:45:48 +02:00
Bogdan
5f0d6e2fdd New: Sync Pack Seed Time to Whisparr applications
Fixes #2039
2024-02-21 17:34:22 +02:00
Bogdan
ede9879c99 Cleanup obsolete definitions for ANT/ABB/BB/MTV/PTN/TVV 2024-02-21 17:12:35 +02:00
Benjamin Harder
7287abc77c New: Sync Reject Blocklisted Torrent Hashes While Grabbing for torrent indexers to Apps 2024-02-21 05:30:42 +02:00
Bogdan
8c653b5c09 Fixed: (GGn) Don't die on invalid FreeTorrent values in the API 2024-02-19 01:56:51 +02:00
Ryan S
15c6b3c308 Fixed: (Shazbat) Added season and episode to search capabilities 2024-02-18 23:11:05 +02:00
Bogdan
9676447c74 Bump version to 1.14.1 2024-02-18 23:09:35 +02:00
85 changed files with 1015 additions and 2569 deletions

View File

@@ -9,14 +9,14 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '1.14.0'
majorVersion: '1.14.3'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.417'
nodeVersion: '16.X'
nodeVersion: '20.X'
innoVersion: '6.2.2'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04'

View File

@@ -12,7 +12,6 @@ export interface CommandBody {
lastStartTime: string;
trigger: string;
suppressMessages: boolean;
seriesId?: number;
}
interface Command extends ModelBase {

View File

@@ -1,3 +1,4 @@
import { maxBy } from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -50,7 +51,7 @@ class FilterBuilderModalContent extends Component {
if (id) {
dispatchSetFilter({ selectedFilterKey: id });
} else {
const last = customFilters[customFilters.length -1];
const last = maxBy(customFilters, 'id');
dispatchSetFilter({ selectedFilterKey: last.id });
}
@@ -108,7 +109,7 @@ class FilterBuilderModalContent extends Component {
this.setState({
labelErrors: [
{
message: 'Label is required'
message: translate('LabelIsRequired')
}
]
});
@@ -146,13 +147,13 @@ class FilterBuilderModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Custom Filter
{translate('CustomFilter')}
</ModalHeader>
<ModalBody>
<div className={styles.labelContainer}>
<div className={styles.label}>
Label
{translate('Label')}
</div>
<div className={styles.labelInputContainer}>

View File

@@ -37,8 +37,8 @@ class CustomFilter extends Component {
dispatchSetFilter
} = this.props;
// Assume that delete and then unmounting means the delete was successful.
// Moving this check to a ancestor would be more accurate, but would have
// Assume that delete and then unmounting means the deletion was successful.
// Moving this check to an ancestor would be more accurate, but would have
// more boilerplate.
if (this.state.isDeleting && id === selectedFilterKey) {
dispatchSetFilter({ selectedFilterKey: 'all' });

View File

@@ -24,16 +24,20 @@ function createMapStateToProps() {
if (includeNoChange) {
values.unshift({
key: 'noChange',
value: translate('NoChange'),
disabled: true
get value() {
return translate('NoChange');
},
isDisabled: true
});
}
if (includeMixed) {
values.unshift({
key: 'mixed',
value: '(Mixed)',
disabled: true
get value() {
return `(${translate('Mixed')})`;
},
isDisabled: true
});
}

View File

@@ -1,54 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import SelectInput from './SelectInput';
const availabilityOptions = [
{ key: 'announced', value: 'Announced' },
{ key: 'inCinemas', value: 'In Cinemas' },
{ key: 'released', value: 'Released' },
{ key: 'preDB', value: 'PreDB' }
];
function AvailabilitySelectInput(props) {
const values = [...availabilityOptions];
const {
includeNoChange,
includeMixed
} = props;
if (includeNoChange) {
values.unshift({
key: 'noChange',
value: 'No Change',
disabled: true
});
}
if (includeMixed) {
values.unshift({
key: 'mixed',
value: '(Mixed)',
disabled: true
});
}
return (
<SelectInput
{...props}
values={values}
/>
);
}
AvailabilitySelectInput.propTypes = {
includeNoChange: PropTypes.bool.isRequired,
includeMixed: PropTypes.bool.isRequired
};
AvailabilitySelectInput.defaultProps = {
includeNoChange: false,
includeMixed: false
};
export default AvailabilitySelectInput;

View File

@@ -5,7 +5,6 @@ import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AppProfileSelectInputConnector from './AppProfileSelectInputConnector';
import AutoCompleteInput from './AutoCompleteInput';
import AvailabilitySelectInput from './AvailabilitySelectInput';
import CaptchaInputConnector from './CaptchaInputConnector';
import CardigannCaptchaInputConnector from './CardigannCaptchaInputConnector';
import CheckInput from './CheckInput';
@@ -37,9 +36,6 @@ function getComponent(type) {
case inputTypes.AUTO_COMPLETE:
return AutoCompleteInput;
case inputTypes.AVAILABILITY_SELECT:
return AvailabilitySelectInput;
case inputTypes.CAPTCHA:
return CaptchaInputConnector;

View File

@@ -0,0 +1,17 @@
import { useCallback, useState } from 'react';
export default function useModalOpenState(
initialState: boolean
): [boolean, () => void, () => void] {
const [isOpen, setOpen] = useState(initialState);
const setModalOpen = useCallback(() => {
setOpen(true);
}, [setOpen]);
const setModalClosed = useCallback(() => {
setOpen(false);
}, [setOpen]);
return [isOpen, setModalOpen, setModalClosed];
}

View File

@@ -1,6 +1,5 @@
export const AUTO_COMPLETE = 'autoComplete';
export const APP_PROFILE_SELECT = 'appProfileSelect';
export const AVAILABILITY_SELECT = 'availabilitySelect';
export const CAPTCHA = 'captcha';
export const CARDIGANNCAPTCHA = 'cardigannCaptcha';
export const CHECK = 'check';
@@ -27,7 +26,6 @@ export const TAG_SELECT = 'tagSelect';
export const all = [
AUTO_COMPLETE,
APP_PROFILE_SELECT,
AVAILABILITY_SELECT,
CAPTCHA,
CARDIGANNCAPTCHA,
CHECK,

View File

@@ -144,6 +144,7 @@ function EditIndexerModalContent(props) {
}) :
null
}
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}

View File

@@ -19,6 +19,7 @@ interface SavePayload {
seedRatio?: number;
seedTime?: number;
packSeedTime?: number;
rejectBlocklistedTorrentHashesWhileGrabbing?: boolean;
}
interface EditIndexerModalContentProps {
@@ -35,7 +36,7 @@ const enableOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'true',
@@ -65,6 +66,10 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
const [packSeedTime, setPackSeedTime] = useState<null | string | number>(
null
);
const [
rejectBlocklistedTorrentHashesWhileGrabbing,
setRejectBlocklistedTorrentHashesWhileGrabbing,
] = useState(NO_CHANGE);
const save = useCallback(() => {
let hasChanges = false;
@@ -105,6 +110,12 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
payload.packSeedTime = packSeedTime as number;
}
if (rejectBlocklistedTorrentHashesWhileGrabbing !== NO_CHANGE) {
hasChanges = true;
payload.rejectBlocklistedTorrentHashesWhileGrabbing =
rejectBlocklistedTorrentHashesWhileGrabbing === 'true';
}
if (hasChanges) {
onSavePress(payload);
}
@@ -118,6 +129,7 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
seedRatio,
seedTime,
packSeedTime,
rejectBlocklistedTorrentHashesWhileGrabbing,
onSavePress,
onModalClose,
]);
@@ -146,6 +158,9 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
case 'packSeedTime':
setPackSeedTime(value);
break;
case 'rejectBlocklistedTorrentHashesWhileGrabbing':
setRejectBlocklistedTorrentHashesWhileGrabbing(value);
break;
default:
console.warn(`EditIndexersModalContent Unknown Input: '${name}'`);
}
@@ -253,6 +268,23 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
onChange={onInputChange}
/>
</FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>
{translate('IndexerSettingsRejectBlocklistedTorrentHashes')}
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="rejectBlocklistedTorrentHashesWhileGrabbing"
value={rejectBlocklistedTorrentHashesWhileGrabbing}
values={enableOptions}
helpText={translate(
'IndexerSettingsRejectBlocklistedTorrentHashesHelpText'
)}
onChange={onInputChange}
/>
</FormGroup>
</ModalBody>
<ModalFooter className={styles.modalFooter}>

View File

@@ -30,7 +30,7 @@ const syncLevelOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: ApplicationSyncLevel.Disabled,

View File

@@ -1,10 +1,11 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Card from 'Components/Card';
import FieldSet from 'Components/FieldSet';
import Icon from 'Components/Icon';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AddDownloadClientModal from './AddDownloadClientModal';
import DownloadClient from './DownloadClient';
@@ -59,48 +60,59 @@ class DownloadClients extends Component {
} = this.state;
return (
<FieldSet legend={translate('DownloadClients')}>
<PageSectionContent
errorMessage={translate('DownloadClientsLoadError')}
{...otherProps}
>
<div className={styles.downloadClients}>
{
items.map((item) => {
return (
<DownloadClient
key={item.id}
{...item}
onConfirmDeleteDownloadClient={onConfirmDeleteDownloadClient}
/>
);
})
}
<Card
className={styles.addDownloadClient}
onPress={this.onAddDownloadClientPress}
>
<div className={styles.center}>
<Icon
name={icons.ADD}
size={45}
/>
</div>
</Card>
<div>
<Alert kind={kinds.INFO}>
<div>
{translate('ProwlarrDownloadClientsAlert')}
</div>
<div>
{translate('ProwlarrDownloadClientsInAppOnlyAlert')}
</div>
</Alert>
<AddDownloadClientModal
isOpen={isAddDownloadClientModalOpen}
onModalClose={this.onAddDownloadClientModalClose}
/>
<FieldSet legend={translate('DownloadClients')}>
<PageSectionContent
errorMessage={translate('DownloadClientsLoadError')}
{...otherProps}
>
<div className={styles.downloadClients}>
{
items.map((item) => {
return (
<DownloadClient
key={item.id}
{...item}
onConfirmDeleteDownloadClient={onConfirmDeleteDownloadClient}
/>
);
})
}
<EditDownloadClientModalConnector
isOpen={isEditDownloadClientModalOpen}
onModalClose={this.onEditDownloadClientModalClose}
/>
</PageSectionContent>
</FieldSet>
<Card
className={styles.addDownloadClient}
onPress={this.onAddDownloadClientPress}
>
<div className={styles.center}>
<Icon
name={icons.ADD}
size={45}
/>
</div>
</Card>
</div>
<AddDownloadClientModal
isOpen={isAddDownloadClientModalOpen}
onModalClose={this.onAddDownloadClientModalClose}
/>
<EditDownloadClientModalConnector
isOpen={isEditDownloadClientModalOpen}
onModalClose={this.onEditDownloadClientModalClose}
/>
</PageSectionContent>
</FieldSet>
</div>
);
}
}

View File

@@ -30,7 +30,7 @@ const enableOptions = [
get value() {
return translate('NoChange');
},
disabled: true,
isDisabled: true,
},
{
key: 'enabled',

View File

@@ -10,15 +10,6 @@
width: 100%;
}
.commandName {
display: inline-block;
min-width: 220px;
}
.userAgent {
color: #b0b0b0;
}
.queued,
.started,
.ended {

View File

@@ -2,14 +2,12 @@
// Please do not change this file!
interface CssExports {
'actions': string;
'commandName': string;
'duration': string;
'ended': string;
'queued': string;
'started': string;
'trigger': string;
'triggerContent': string;
'userAgent': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -1,279 +0,0 @@
import moment from 'moment';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props';
import formatDate from 'Utilities/Date/formatDate';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import styles from './QueuedTaskRow.css';
function getStatusIconProps(status, message) {
const title = titleCase(status);
switch (status) {
case 'queued':
return {
name: icons.PENDING,
title
};
case 'started':
return {
name: icons.REFRESH,
isSpinning: true,
title
};
case 'completed':
return {
name: icons.CHECK,
kind: kinds.SUCCESS,
title: message === 'Completed' ? title : `${title}: ${message}`
};
case 'failed':
return {
name: icons.FATAL,
kind: kinds.DANGER,
title: `${title}: ${message}`
};
default:
return {
name: icons.UNKNOWN,
title
};
}
}
function getFormattedDates(props) {
const {
queued,
started,
ended,
showRelativeDates,
shortDateFormat
} = props;
if (showRelativeDates) {
return {
queuedAt: moment(queued).fromNow(),
startedAt: started ? moment(started).fromNow() : '-',
endedAt: ended ? moment(ended).fromNow() : '-'
};
}
return {
queuedAt: formatDate(queued, shortDateFormat),
startedAt: started ? formatDate(started, shortDateFormat) : '-',
endedAt: ended ? formatDate(ended, shortDateFormat) : '-'
};
}
class QueuedTaskRow extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
...getFormattedDates(props),
isCancelConfirmModalOpen: false
};
this._updateTimeoutId = null;
}
componentDidMount() {
this.setUpdateTimer();
}
componentDidUpdate(prevProps) {
const {
queued,
started,
ended
} = this.props;
if (
queued !== prevProps.queued ||
started !== prevProps.started ||
ended !== prevProps.ended
) {
this.setState(getFormattedDates(this.props));
}
}
componentWillUnmount() {
if (this._updateTimeoutId) {
this._updateTimeoutId = clearTimeout(this._updateTimeoutId);
}
}
//
// Control
setUpdateTimer() {
this._updateTimeoutId = setTimeout(() => {
this.setState(getFormattedDates(this.props));
this.setUpdateTimer();
}, 30000);
}
//
// Listeners
onCancelPress = () => {
this.setState({
isCancelConfirmModalOpen: true
});
};
onAbortCancel = () => {
this.setState({
isCancelConfirmModalOpen: false
});
};
//
// Render
render() {
const {
trigger,
commandName,
queued,
started,
ended,
status,
duration,
message,
clientUserAgent,
longDateFormat,
timeFormat,
onCancelPress
} = this.props;
const {
queuedAt,
startedAt,
endedAt,
isCancelConfirmModalOpen
} = this.state;
let triggerIcon = icons.QUICK;
if (trigger === 'manual') {
triggerIcon = icons.INTERACTIVE;
} else if (trigger === 'scheduled') {
triggerIcon = icons.SCHEDULED;
}
return (
<TableRow>
<TableRowCell className={styles.trigger}>
<span className={styles.triggerContent}>
<Icon
name={triggerIcon}
title={titleCase(trigger)}
/>
<Icon
{...getStatusIconProps(status, message)}
/>
</span>
</TableRowCell>
<TableRowCell>
<span className={styles.commandName}>
{commandName}
</span>
{
clientUserAgent ?
<span className={styles.userAgent} title={translate('UserAgentProvidedByTheAppThatCalledTheAPI')}>
from: {clientUserAgent}
</span> :
null
}
</TableRowCell>
<TableRowCell
className={styles.queued}
title={formatDateTime(queued, longDateFormat, timeFormat)}
>
{queuedAt}
</TableRowCell>
<TableRowCell
className={styles.started}
title={formatDateTime(started, longDateFormat, timeFormat)}
>
{startedAt}
</TableRowCell>
<TableRowCell
className={styles.ended}
title={formatDateTime(ended, longDateFormat, timeFormat)}
>
{endedAt}
</TableRowCell>
<TableRowCell className={styles.duration}>
{formatTimeSpan(duration)}
</TableRowCell>
<TableRowCell
className={styles.actions}
>
{
status === 'queued' &&
<IconButton
title={translate('RemovedFromTaskQueue')}
name={icons.REMOVE}
onPress={this.onCancelPress}
/>
}
</TableRowCell>
<ConfirmModal
isOpen={isCancelConfirmModalOpen}
kind={kinds.DANGER}
title={translate('Cancel')}
message={translate('CancelPendingTask')}
confirmLabel={translate('YesCancel')}
cancelLabel={translate('NoLeaveIt')}
onConfirm={onCancelPress}
onCancel={this.onAbortCancel}
/>
</TableRow>
);
}
}
QueuedTaskRow.propTypes = {
trigger: PropTypes.string.isRequired,
commandName: PropTypes.string.isRequired,
queued: PropTypes.string.isRequired,
started: PropTypes.string,
ended: PropTypes.string,
status: PropTypes.string.isRequired,
duration: PropTypes.string,
message: PropTypes.string,
clientUserAgent: PropTypes.string,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
onCancelPress: PropTypes.func.isRequired
};
export default QueuedTaskRow;

View File

@@ -0,0 +1,238 @@
import moment from 'moment';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { CommandBody } from 'Commands/Command';
import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
import { icons, kinds } from 'Helpers/Props';
import { cancelCommand } from 'Store/Actions/commandActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import formatDate from 'Utilities/Date/formatDate';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import QueuedTaskRowNameCell from './QueuedTaskRowNameCell';
import styles from './QueuedTaskRow.css';
function getStatusIconProps(status: string, message: string | undefined) {
const title = titleCase(status);
switch (status) {
case 'queued':
return {
name: icons.PENDING,
title,
};
case 'started':
return {
name: icons.REFRESH,
isSpinning: true,
title,
};
case 'completed':
return {
name: icons.CHECK,
kind: kinds.SUCCESS,
title: message === 'Completed' ? title : `${title}: ${message}`,
};
case 'failed':
return {
name: icons.FATAL,
kind: kinds.DANGER,
title: `${title}: ${message}`,
};
default:
return {
name: icons.UNKNOWN,
title,
};
}
}
function getFormattedDates(
queued: string,
started: string | undefined,
ended: string | undefined,
showRelativeDates: boolean,
shortDateFormat: string
) {
if (showRelativeDates) {
return {
queuedAt: moment(queued).fromNow(),
startedAt: started ? moment(started).fromNow() : '-',
endedAt: ended ? moment(ended).fromNow() : '-',
};
}
return {
queuedAt: formatDate(queued, shortDateFormat),
startedAt: started ? formatDate(started, shortDateFormat) : '-',
endedAt: ended ? formatDate(ended, shortDateFormat) : '-',
};
}
interface QueuedTimes {
queuedAt: string;
startedAt: string;
endedAt: string;
}
export interface QueuedTaskRowProps {
id: number;
trigger: string;
commandName: string;
queued: string;
started?: string;
ended?: string;
status: string;
duration?: string;
message?: string;
body: CommandBody;
clientUserAgent?: string;
}
export default function QueuedTaskRow(props: QueuedTaskRowProps) {
const {
id,
trigger,
commandName,
queued,
started,
ended,
status,
duration,
message,
body,
clientUserAgent,
} = props;
const dispatch = useDispatch();
const { longDateFormat, shortDateFormat, showRelativeDates, timeFormat } =
useSelector(createUISettingsSelector());
const updateTimeTimeoutId = useRef<ReturnType<typeof setTimeout> | null>(
null
);
const [times, setTimes] = useState<QueuedTimes>(
getFormattedDates(
queued,
started,
ended,
showRelativeDates,
shortDateFormat
)
);
const [
isCancelConfirmModalOpen,
openCancelConfirmModal,
closeCancelConfirmModal,
] = useModalOpenState(false);
const handleCancelPress = useCallback(() => {
dispatch(cancelCommand({ id }));
}, [id, dispatch]);
useEffect(() => {
updateTimeTimeoutId.current = setTimeout(() => {
setTimes(
getFormattedDates(
queued,
started,
ended,
showRelativeDates,
shortDateFormat
)
);
}, 30000);
return () => {
if (updateTimeTimeoutId.current) {
clearTimeout(updateTimeTimeoutId.current);
}
};
}, [queued, started, ended, showRelativeDates, shortDateFormat, setTimes]);
const { queuedAt, startedAt, endedAt } = times;
let triggerIcon = icons.QUICK;
if (trigger === 'manual') {
triggerIcon = icons.INTERACTIVE;
} else if (trigger === 'scheduled') {
triggerIcon = icons.SCHEDULED;
}
return (
<TableRow>
<TableRowCell className={styles.trigger}>
<span className={styles.triggerContent}>
<Icon name={triggerIcon} title={titleCase(trigger)} />
<Icon {...getStatusIconProps(status, message)} />
</span>
</TableRowCell>
<QueuedTaskRowNameCell
commandName={commandName}
body={body}
clientUserAgent={clientUserAgent}
/>
<TableRowCell
className={styles.queued}
title={formatDateTime(queued, longDateFormat, timeFormat)}
>
{queuedAt}
</TableRowCell>
<TableRowCell
className={styles.started}
title={formatDateTime(started, longDateFormat, timeFormat)}
>
{startedAt}
</TableRowCell>
<TableRowCell
className={styles.ended}
title={formatDateTime(ended, longDateFormat, timeFormat)}
>
{endedAt}
</TableRowCell>
<TableRowCell className={styles.duration}>
{formatTimeSpan(duration)}
</TableRowCell>
<TableRowCell className={styles.actions}>
{status === 'queued' && (
<IconButton
title={translate('RemovedFromTaskQueue')}
name={icons.REMOVE}
onPress={openCancelConfirmModal}
/>
)}
</TableRowCell>
<ConfirmModal
isOpen={isCancelConfirmModalOpen}
kind={kinds.DANGER}
title={translate('Cancel')}
message={translate('CancelPendingTask')}
confirmLabel={translate('YesCancel')}
cancelLabel={translate('NoLeaveIt')}
onConfirm={handleCancelPress}
onCancel={closeCancelConfirmModal}
/>
</TableRow>
);
}

View File

@@ -1,31 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { cancelCommand } from 'Store/Actions/commandActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import QueuedTaskRow from './QueuedTaskRow';
function createMapStateToProps() {
return createSelector(
createUISettingsSelector(),
(uiSettings) => {
return {
showRelativeDates: uiSettings.showRelativeDates,
shortDateFormat: uiSettings.shortDateFormat,
longDateFormat: uiSettings.longDateFormat,
timeFormat: uiSettings.timeFormat
};
}
);
}
function createMapDispatchToProps(dispatch, props) {
return {
onCancelPress() {
dispatch(cancelCommand({
id: props.id
}));
}
};
}
export default connect(createMapStateToProps, createMapDispatchToProps)(QueuedTaskRow);

View File

@@ -0,0 +1,8 @@
.commandName {
display: inline-block;
min-width: 220px;
}
.userAgent {
color: #b0b0b0;
}

View File

@@ -0,0 +1,8 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'commandName': string;
'userAgent': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -0,0 +1,32 @@
import React from 'react';
import { CommandBody } from 'Commands/Command';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import translate from 'Utilities/String/translate';
import styles from './QueuedTaskRowNameCell.css';
export interface QueuedTaskRowNameCellProps {
commandName: string;
body: CommandBody;
clientUserAgent?: string;
}
export default function QueuedTaskRowNameCell(
props: QueuedTaskRowNameCellProps
) {
const { commandName, clientUserAgent } = props;
return (
<TableRowCell>
<span className={styles.commandName}>{commandName}</span>
{clientUserAgent ? (
<span
className={styles.userAgent}
title={translate('TaskUserAgentTooltip')}
>
{translate('From')}: {clientUserAgent}
</span>
) : null}
</TableRowCell>
);
}

View File

@@ -1,90 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import FieldSet from 'Components/FieldSet';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import translate from 'Utilities/String/translate';
import QueuedTaskRowConnector from './QueuedTaskRowConnector';
const columns = [
{
name: 'trigger',
label: '',
isVisible: true
},
{
name: 'commandName',
label: () => translate('Name'),
isVisible: true
},
{
name: 'queued',
label: () => translate('Queued'),
isVisible: true
},
{
name: 'started',
label: () => translate('Started'),
isVisible: true
},
{
name: 'ended',
label: () => translate('Ended'),
isVisible: true
},
{
name: 'duration',
label: () => translate('Duration'),
isVisible: true
},
{
name: 'actions',
isVisible: true
}
];
function QueuedTasks(props) {
const {
isFetching,
isPopulated,
items
} = props;
return (
<FieldSet legend={translate('Queue')}>
{
isFetching && !isPopulated &&
<LoadingIndicator />
}
{
isPopulated &&
<Table
columns={columns}
>
<TableBody>
{
items.map((item) => {
return (
<QueuedTaskRowConnector
key={item.id}
{...item}
/>
);
})
}
</TableBody>
</Table>
}
</FieldSet>
);
}
QueuedTasks.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
items: PropTypes.array.isRequired
};
export default QueuedTasks;

View File

@@ -0,0 +1,74 @@
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import FieldSet from 'Components/FieldSet';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { fetchCommands } from 'Store/Actions/commandActions';
import translate from 'Utilities/String/translate';
import QueuedTaskRow from './QueuedTaskRow';
const columns = [
{
name: 'trigger',
label: '',
isVisible: true,
},
{
name: 'commandName',
label: () => translate('Name'),
isVisible: true,
},
{
name: 'queued',
label: () => translate('Queued'),
isVisible: true,
},
{
name: 'started',
label: () => translate('Started'),
isVisible: true,
},
{
name: 'ended',
label: () => translate('Ended'),
isVisible: true,
},
{
name: 'duration',
label: () => translate('Duration'),
isVisible: true,
},
{
name: 'actions',
isVisible: true,
},
];
export default function QueuedTasks() {
const dispatch = useDispatch();
const { isFetching, isPopulated, items } = useSelector(
(state: AppState) => state.commands
);
useEffect(() => {
dispatch(fetchCommands());
}, [dispatch]);
return (
<FieldSet legend={translate('Queue')}>
{isFetching && !isPopulated && <LoadingIndicator />}
{isPopulated && (
<Table columns={columns}>
<TableBody>
{items.map((item) => {
return <QueuedTaskRow key={item.id} {...item} />;
})}
</TableBody>
</Table>
)}
</FieldSet>
);
}

View File

@@ -1,46 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchCommands } from 'Store/Actions/commandActions';
import QueuedTasks from './QueuedTasks';
function createMapStateToProps() {
return createSelector(
(state) => state.commands,
(commands) => {
return commands;
}
);
}
const mapDispatchToProps = {
dispatchFetchCommands: fetchCommands
};
class QueuedTasksConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.dispatchFetchCommands();
}
//
// Render
render() {
return (
<QueuedTasks
{...this.props}
/>
);
}
}
QueuedTasksConnector.propTypes = {
dispatchFetchCommands: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(QueuedTasksConnector);

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import translate from 'Utilities/String/translate';
import QueuedTasksConnector from './Queued/QueuedTasksConnector';
import QueuedTasks from './Queued/QueuedTasks';
import ScheduledTasksConnector from './Scheduled/ScheduledTasksConnector';
function Tasks() {
@@ -10,7 +10,7 @@ function Tasks() {
<PageContent title={translate('Tasks')}>
<PageContentBody>
<ScheduledTasksConnector />
<QueuedTasksConnector />
<QueuedTasks />
</PageContentBody>
</PageContent>
);

View File

@@ -3,13 +3,16 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Chrome, Safari, Opera, and Firefox OS -->
<meta name="theme-color" content="#e66001" />
<!-- Windows Phone -->
<meta name="msapplication-navbutton-color" content="#3a3f51" />
<meta name="msapplication-navbutton-color" content="#464b51" />
<!-- Android/Apple Phone -->
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="format-detection" content="telephone=no">
<meta name="description" content="Prowlarr" />

View File

@@ -3,13 +3,16 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Chrome, Safari, Opera, and Firefox OS -->
<meta name="theme-color" content="#e66001" />
<!-- Windows Phone -->
<meta name="msapplication-navbutton-color" content="#464b51" />
<!-- Android/Apple Phone -->
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="format-detection" content="telephone=no">
<meta name="description" content="Prowlarr (Preview)" />

View File

@@ -11,7 +11,7 @@
"lint": "eslint --config frontend/.eslintrc.js --ignore-path frontend/.eslintignore frontend/",
"lint-fix": "yarn lint --fix",
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc",
"stylelint-windows": "stylelint \"frontend/**/*.css\" --config frontend/.stylelintrc",
"check-modules": "are-you-es5 check . -r"
},
"repository": "https://github.com/Prowlarr/Prowlarr",

View File

@@ -119,6 +119,10 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"[Info] MigrationController: *** Migrating Database=prowlarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
[TestCase(@"[Info] MigrationController: *** Migrating Database=prowlarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
// Discord
[TestCase(@"https://discord.com/api/webhooks/mySecret")]
[TestCase(@"https://discord.com/api/webhooks/mySecret/01233210")]
public void should_clean_message(string message)
{
var cleansedMessage = CleanseLogMessage.Cleanse(message);

View File

@@ -1,8 +1,10 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
@@ -275,6 +277,18 @@ namespace NzbDrone.Common.Http.Dispatchers
return _credentialCache.Get("credentialCache", () => new CredentialCache());
}
private static bool HasRoutableIPv4Address()
{
// Get all IPv4 addresses from all interfaces and return true if there are any with non-loopback addresses
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
return networkInterfaces.Any(ni =>
ni.OperationalStatus == OperationalStatus.Up &&
ni.GetIPProperties().UnicastAddresses.Any(ip =>
ip.Address.AddressFamily == AddressFamily.InterNetwork &&
!IPAddress.IsLoopback(ip.Address)));
}
private static async ValueTask<Stream> onConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
// Until .NET supports an implementation of Happy Eyeballs (https://tools.ietf.org/html/rfc8305#section-2), let's make IPv4 fallback work in a simple way.
@@ -298,10 +312,8 @@ namespace NzbDrone.Common.Http.Dispatchers
}
catch
{
// very naively fallback to ipv4 permanently for this execution based on the response of the first connection attempt.
// note that this may cause users to eventually get switched to ipv4 (on a random failure when they are switching networks, for instance)
// but in the interest of keeping this implementation simple, this is acceptable.
useIPv6 = false;
// Do not retry IPv6 if a routable IPv4 address is available, otherwise continue to attempt IPv6 connections.
useIPv6 = !HasRoutableIPv4Address();
}
finally
{

View File

@@ -61,6 +61,9 @@ namespace NzbDrone.Common.Instrumentation
// Applications
new (@"""name"":""apikey"",""value"":""(?<secret>[^&=]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Discord
new (@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
};
private static readonly Regex CleanseRemoteIPRegex = new (@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);

View File

@@ -218,7 +218,7 @@ namespace NzbDrone.Core.Applications.Lidarr
{
var cacheKey = $"{Settings.BaseUrl}";
var schemas = _schemaCache.Get(cacheKey, () => _lidarrV1Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime" };
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime", "rejectBlocklistedTorrentHashesWhileGrabbing" };
if (id == 0)
{
@@ -258,10 +258,15 @@ namespace NzbDrone.Core.Applications.Lidarr
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
if (lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime") != null)
if (lidarrIndexer.Fields.Any(x => x.Name == "seedCriteria.discographySeedTime"))
{
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.PackSeedTime ?? ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
}
if (lidarrIndexer.Fields.Any(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing"))
{
lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.RejectBlocklistedTorrentHashesWhileGrabbing;
}
}
return lidarrIndexer;

View File

@@ -55,6 +55,10 @@ namespace NzbDrone.Core.Applications.Lidarr
var otherSeedRatio = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value);
var seedRatioCompare = seedRatio == otherSeedRatio;
var rejectBlocklistedTorrentHashesWhileGrabbing = Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var otherRejectBlocklistedTorrentHashesWhileGrabbing = other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var rejectBlocklistedTorrentHashesWhileGrabbingCompare = rejectBlocklistedTorrentHashesWhileGrabbing == otherRejectBlocklistedTorrentHashesWhileGrabbing;
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
@@ -62,7 +66,7 @@ namespace NzbDrone.Core.Applications.Lidarr
other.Implementation == Implementation &&
other.Priority == Priority &&
other.Id == Id &&
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && discographySeedTimeCompare;
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && discographySeedTimeCompare && rejectBlocklistedTorrentHashesWhileGrabbingCompare;
}
}
}

View File

@@ -216,7 +216,7 @@ namespace NzbDrone.Core.Applications.Radarr
{
var cacheKey = $"{Settings.BaseUrl}";
var schemas = _schemaCache.Get(cacheKey, () => _radarrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime" };
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "rejectBlocklistedTorrentHashesWhileGrabbing" };
if (id == 0)
{
@@ -255,6 +255,11 @@ namespace NzbDrone.Core.Applications.Radarr
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
if (radarrIndexer.Fields.Any(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing"))
{
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.RejectBlocklistedTorrentHashesWhileGrabbing;
}
}
return radarrIndexer;

View File

@@ -51,6 +51,10 @@ namespace NzbDrone.Core.Applications.Radarr
var otherSeedRatio = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value);
var seedRatioCompare = seedRatio == otherSeedRatio;
var rejectBlocklistedTorrentHashesWhileGrabbing = Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var otherRejectBlocklistedTorrentHashesWhileGrabbing = other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var rejectBlocklistedTorrentHashesWhileGrabbingCompare = rejectBlocklistedTorrentHashesWhileGrabbing == otherRejectBlocklistedTorrentHashesWhileGrabbing;
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
@@ -58,7 +62,7 @@ namespace NzbDrone.Core.Applications.Radarr
other.Implementation == Implementation &&
other.Priority == Priority &&
other.Id == Id &&
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare;
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && rejectBlocklistedTorrentHashesWhileGrabbingCompare;
}
}
}

View File

@@ -218,7 +218,7 @@ namespace NzbDrone.Core.Applications.Readarr
{
var cacheKey = $"{Settings.BaseUrl}";
var schemas = _schemaCache.Get(cacheKey, () => _readarrV1Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime" };
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.discographySeedTime", "rejectBlocklistedTorrentHashesWhileGrabbing" };
var newznab = schemas.First(i => i.Implementation == "Newznab");
var torznab = schemas.First(i => i.Implementation == "Torznab");
@@ -252,10 +252,15 @@ namespace NzbDrone.Core.Applications.Readarr
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
if (readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime") != null)
if (readarrIndexer.Fields.Any(x => x.Name == "seedCriteria.discographySeedTime"))
{
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.discographySeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.PackSeedTime ?? ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
}
if (readarrIndexer.Fields.Any(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing"))
{
readarrIndexer.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.RejectBlocklistedTorrentHashesWhileGrabbing;
}
}
return readarrIndexer;

View File

@@ -55,6 +55,10 @@ namespace NzbDrone.Core.Applications.Readarr
var otherSeedRatio = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value);
var seedRatioCompare = seedRatio == otherSeedRatio;
var rejectBlocklistedTorrentHashesWhileGrabbing = Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var otherRejectBlocklistedTorrentHashesWhileGrabbing = other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var rejectBlocklistedTorrentHashesWhileGrabbingCompare = rejectBlocklistedTorrentHashesWhileGrabbing == otherRejectBlocklistedTorrentHashesWhileGrabbing;
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
@@ -62,7 +66,7 @@ namespace NzbDrone.Core.Applications.Readarr
other.Implementation == Implementation &&
other.Priority == Priority &&
other.Id == Id &&
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && discographySeedTimeCompare;
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && discographySeedTimeCompare && rejectBlocklistedTorrentHashesWhileGrabbingCompare;
}
}
}

View File

@@ -224,7 +224,7 @@ namespace NzbDrone.Core.Applications.Sonarr
{
var cacheKey = $"{Settings.BaseUrl}";
var schemas = _schemaCache.Get(cacheKey, () => _sonarrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "animeCategories", "animeStandardFormatSearch", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.seasonPackSeedTime" };
var syncFields = new List<string> { "baseUrl", "apiPath", "apiKey", "categories", "animeCategories", "animeStandardFormatSearch", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.seasonPackSeedTime", "rejectBlocklistedTorrentHashesWhileGrabbing" };
if (id == 0)
{
@@ -270,6 +270,11 @@ namespace NzbDrone.Core.Applications.Sonarr
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.PackSeedTime ?? ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
if (sonarrIndexer.Fields.Any(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing"))
{
sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.RejectBlocklistedTorrentHashesWhileGrabbing;
}
}
return sonarrIndexer;

View File

@@ -61,6 +61,10 @@ namespace NzbDrone.Core.Applications.Sonarr
var otherSeedRatio = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value);
var seedRatioCompare = seedRatio == otherSeedRatio;
var rejectBlocklistedTorrentHashesWhileGrabbing = Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var otherRejectBlocklistedTorrentHashesWhileGrabbing = other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var rejectBlocklistedTorrentHashesWhileGrabbingCompare = rejectBlocklistedTorrentHashesWhileGrabbing == otherRejectBlocklistedTorrentHashesWhileGrabbing;
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
@@ -68,7 +72,7 @@ namespace NzbDrone.Core.Applications.Sonarr
other.Implementation == Implementation &&
other.Priority == Priority &&
other.Id == Id &&
apiKeyCompare && apiPathCompare && baseUrl && cats && animeCats && animeStandardFormatSearchCompare && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && seasonSeedTimeCompare;
apiKeyCompare && apiPathCompare && baseUrl && cats && animeCats && animeStandardFormatSearchCompare && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && seasonSeedTimeCompare && rejectBlocklistedTorrentHashesWhileGrabbingCompare;
}
}
}

View File

@@ -218,7 +218,7 @@ namespace NzbDrone.Core.Applications.Whisparr
{
var cacheKey = $"{Settings.BaseUrl}";
var schemas = _schemaCache.Get(cacheKey, () => _whisparrV3Proxy.GetIndexerSchema(Settings), TimeSpan.FromDays(7));
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime" };
var syncFields = new[] { "baseUrl", "apiPath", "apiKey", "categories", "minimumSeeders", "seedCriteria.seedRatio", "seedCriteria.seedTime", "seedCriteria.seasonPackSeedTime", "rejectBlocklistedTorrentHashesWhileGrabbing" };
var newznab = schemas.First(i => i.Implementation == "Newznab");
var torznab = schemas.First(i => i.Implementation == "Torznab");
@@ -251,6 +251,16 @@ namespace NzbDrone.Core.Applications.Whisparr
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.AppMinimumSeeders ?? indexer.AppProfile.Value.MinimumSeeders;
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio;
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
if (whisparrIndexer.Fields.Any(x => x.Name == "seedCriteria.seasonPackSeedTime"))
{
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.PackSeedTime ?? ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime;
}
if (whisparrIndexer.Fields.Any(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing"))
{
whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.RejectBlocklistedTorrentHashesWhileGrabbing;
}
}
return whisparrIndexer;

View File

@@ -47,10 +47,18 @@ namespace NzbDrone.Core.Applications.Whisparr
var otherSeedTime = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value);
var seedTimeCompare = seedTime == otherSeedTime;
var seasonSeedTime = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value);
var otherSeasonSeedTime = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seasonPackSeedTime").Value);
var seasonSeedTimeCompare = seasonSeedTime == otherSeasonSeedTime;
var seedRatio = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value);
var otherSeedRatio = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value);
var seedRatioCompare = seedRatio == otherSeedRatio;
var rejectBlocklistedTorrentHashesWhileGrabbing = Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var otherRejectBlocklistedTorrentHashesWhileGrabbing = other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing")?.Value == null ? null : (bool?)Convert.ToBoolean(other.Fields.FirstOrDefault(x => x.Name == "rejectBlocklistedTorrentHashesWhileGrabbing").Value);
var rejectBlocklistedTorrentHashesWhileGrabbingCompare = rejectBlocklistedTorrentHashesWhileGrabbing == otherRejectBlocklistedTorrentHashesWhileGrabbing;
return other.EnableRss == EnableRss &&
other.EnableAutomaticSearch == EnableAutomaticSearch &&
other.EnableInteractiveSearch == EnableInteractiveSearch &&
@@ -58,7 +66,7 @@ namespace NzbDrone.Core.Applications.Whisparr
other.Implementation == Implementation &&
other.Priority == Priority &&
other.Id == Id &&
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare;
apiKeyCompare && apiPathCompare && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare && seasonSeedTimeCompare && rejectBlocklistedTorrentHashesWhileGrabbingCompare;
}
}
}

View File

@@ -308,14 +308,15 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
return Settings.TvDirectory.TrimStart('/');
}
else if (Settings.Category.IsNotNullOrWhiteSpace())
{
var destDir = GetDefaultDir();
var destDir = GetDefaultDir();
if (destDir.IsNotNullOrWhiteSpace() && Settings.Category.IsNotNullOrWhiteSpace())
{
return $"{destDir.TrimEnd('/')}/{Settings.Category}";
}
return null;
return destDir.TrimEnd('/');
}
protected override string AddFromTorrentLink(TorrentInfo release, string hash, string torrentLink)

View File

@@ -274,14 +274,15 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
{
return Settings.TvDirectory.TrimStart('/');
}
else if (Settings.Category.IsNotNullOrWhiteSpace())
{
var destDir = GetDefaultDir();
var destDir = GetDefaultDir();
if (destDir.IsNotNullOrWhiteSpace() && Settings.Category.IsNotNullOrWhiteSpace())
{
return $"{destDir.TrimEnd('/')}/{Settings.Category}";
}
return null;
return destDir.TrimEnd('/');
}
protected override string AddFromLink(ReleaseInfo release)

View File

@@ -137,11 +137,11 @@ namespace NzbDrone.Core.IndexerVersions
if (directoryInfo.Exists)
{
var files = directoryInfo.GetFiles($"*.yml", options);
var files = directoryInfo.GetFiles("*.yml", options);
foreach (var file in files)
{
_logger.Debug("Loading definition " + file.FullName);
_logger.Debug("Loading definition {0}", file.FullName);
try
{
@@ -158,9 +158,9 @@ namespace NzbDrone.Core.IndexerVersions
indexerList.Add(definition);
}
catch (Exception e)
catch (Exception ex)
{
_logger.Error($"Error while parsing Cardigann definition {file.FullName}\n{e}");
_logger.Error(ex, "Error while parsing Cardigann definition {0}", file.FullName);
}
}
}
@@ -188,7 +188,8 @@ namespace NzbDrone.Core.IndexerVersions
if (files.Any())
{
var file = files.First();
_logger.Trace("Loading Cardigann definition " + file.FullName);
_logger.Trace("Loading Cardigann definition {0}", file.FullName);
try
{
var definitionString = File.ReadAllText(file.FullName);
@@ -196,9 +197,9 @@ namespace NzbDrone.Core.IndexerVersions
return CleanIndexerDefinition(definition);
}
catch (Exception e)
catch (Exception ex)
{
_logger.Error($"Error while parsing Cardigann definition {file.FullName}\n{e}");
_logger.Error(ex, "Error while parsing Cardigann definition {0}", file.FullName);
}
}
}
@@ -206,7 +207,7 @@ namespace NzbDrone.Core.IndexerVersions
var dbDefs = _versionService.All();
//Check to ensure it's in versioned defs before we go to web
if (dbDefs.Count > 0 && !dbDefs.Any(x => x.File == fileKey))
if (dbDefs.Count > 0 && dbDefs.All(x => x.File != fileKey))
{
throw new ArgumentNullException(nameof(fileKey));
}
@@ -217,9 +218,10 @@ namespace NzbDrone.Core.IndexerVersions
private CardigannDefinition GetHttpDefinition(string id)
{
var req = new HttpRequest($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}/{id}");
var response = _httpClient.Get(req);
var request = new HttpRequest($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}/{id}");
var response = _httpClient.Get(request);
var definition = _deserializer.Deserialize<CardigannDefinition>(response.Content);
return CleanIndexerDefinition(definition);
}
@@ -289,7 +291,7 @@ namespace NzbDrone.Core.IndexerVersions
EnsureDefinitionsFolder();
var definitionsFolder = Path.Combine(startupFolder, "Definitions");
var saveFile = Path.Combine(definitionsFolder, $"indexers.zip");
var saveFile = Path.Combine(definitionsFolder, "indexers.zip");
_httpClient.DownloadFile($"https://indexers.prowlarr.com/{DEFINITION_BRANCH}/{DEFINITION_VERSION}/package.zip", saveFile);

View File

@@ -18,6 +18,7 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers.Definitions
{
@@ -43,7 +44,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IParseIndexerResponse GetParser()
{
return new AnidubParser(Settings, Capabilities.Categories, RateLimit, _httpClient, _logger);
return new AnidubParser(Definition, Settings, Capabilities.Categories, RateLimit, _httpClient, _logger);
}
protected override async Task DoLogin()
@@ -244,6 +245,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class AnidubParser : IParseIndexerResponse
{
private readonly ProviderDefinition _definition;
private readonly UserPassTorrentBaseSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private readonly TimeSpan _rateLimit;
@@ -270,8 +272,9 @@ namespace NzbDrone.Core.Indexers.Definitions
{ "/anons_ongoing", "12" }
};
public AnidubParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories, TimeSpan rateLimit, IIndexerHttpClient httpClient, Logger logger)
public AnidubParser(ProviderDefinition definition, UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories, TimeSpan rateLimit, IIndexerHttpClient httpClient, Logger logger)
{
_definition = definition;
_settings = settings;
_categories = categories;
_rateLimit = rateLimit;
@@ -479,7 +482,7 @@ namespace NzbDrone.Core.Indexers.Definitions
.Build();
var releaseIndexerRequest = new IndexerRequest(releaseRequest);
var releaseResponse = new IndexerResponse(releaseIndexerRequest, _httpClient.Execute(releaseIndexerRequest.HttpRequest));
var releaseResponse = new IndexerResponse(releaseIndexerRequest, _httpClient.ExecuteProxied(releaseIndexerRequest.HttpRequest, _definition));
// Throw common http errors here before we try to parse
if (releaseResponse.HttpResponse.HasHttpError)

View File

@@ -113,7 +113,7 @@ namespace NzbDrone.Core.Indexers.Definitions
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
MusicSearchParam.Q, MusicSearchParam.Artist, MusicSearchParam.Album, MusicSearchParam.Year
},
BookSearchParams = new List<BookSearchParam>
{
@@ -209,6 +209,24 @@ namespace NzbDrone.Core.Indexers.Definitions
}
}
if (searchType == "music" && searchCriteria is MusicSearchCriteria musicSearchCriteria)
{
if (musicSearchCriteria.Artist.IsNotNullOrWhiteSpace() && musicSearchCriteria.Artist != "VA")
{
parameters.Set("artistnames", musicSearchCriteria.Artist);
}
if (musicSearchCriteria.Album.IsNotNullOrWhiteSpace())
{
parameters.Set("groupname", musicSearchCriteria.Album);
}
if (musicSearchCriteria.Year is > 0)
{
parameters.Set("year", musicSearchCriteria.Year.ToString());
}
}
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(searchCriteria.Categories);
if (queryCats.Any())

View File

@@ -14,6 +14,7 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers.Definitions
{
@@ -40,7 +41,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IParseIndexerResponse GetParser()
{
return new AnimediaParser(Settings, Capabilities.Categories, RateLimit, _httpClient);
return new AnimediaParser(Definition, Settings, Capabilities.Categories, RateLimit, _httpClient);
}
private IndexerCapabilities SetCapabilities()
@@ -144,6 +145,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class AnimediaParser : IParseIndexerResponse
{
private readonly ProviderDefinition _definition;
private readonly NoAuthTorrentBaseSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
private readonly TimeSpan _rateLimit;
@@ -157,8 +159,9 @@ namespace NzbDrone.Core.Indexers.Definitions
private static readonly Regex CategorieOVARegex = new Regex(@"ОВА|OVA|ОНА|ONA|Special", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex CategorieDoramaRegex = new Regex(@"Дорама", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public AnimediaParser(NoAuthTorrentBaseSettings settings, IndexerCapabilitiesCategories categories, TimeSpan rateLimit, IIndexerHttpClient httpClient)
public AnimediaParser(ProviderDefinition definition, NoAuthTorrentBaseSettings settings, IndexerCapabilitiesCategories categories, TimeSpan rateLimit, IIndexerHttpClient httpClient)
{
_definition = definition;
_settings = settings;
_categories = categories;
_rateLimit = rateLimit;
@@ -311,7 +314,7 @@ namespace NzbDrone.Core.Indexers.Definitions
.Build();
var releaseIndexerRequest = new IndexerRequest(releaseRequest);
var releaseResponse = new IndexerResponse(releaseIndexerRequest, _httpClient.Execute(releaseIndexerRequest.HttpRequest));
var releaseResponse = new IndexerResponse(releaseIndexerRequest, _httpClient.ExecuteProxied(releaseIndexerRequest.HttpRequest, _definition));
// Throw common http errors here before we try to parse
if (releaseResponse.HttpResponse.HasHttpError)

View File

@@ -1,280 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using AngleSharp.Html.Parser;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete("Moved to YML for Cardigann")]
public class Anthelion : TorrentIndexerBase<UserPassTorrentBaseSettings>
{
public override string Name => "Anthelion";
public override string[] IndexerUrls => new string[] { "https://anthelion.me/" };
private string LoginUrl => Settings.BaseUrl + "login.php";
public override string Description => "A movies tracker";
public override string Language => "en-US";
public override Encoding Encoding => Encoding.UTF8;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
public Anthelion(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AnthelionRequestGenerator() { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new AnthelionParser(Settings, Capabilities.Categories);
}
protected override async Task DoLogin()
{
var requestBuilder = new HttpRequestBuilder(LoginUrl)
{
LogResponseContent = true,
AllowAutoRedirect = true,
Method = HttpMethod.Post
};
var cookies = Cookies;
Cookies = null;
var authLoginRequest = requestBuilder
.AddFormParameter("username", Settings.Username)
.AddFormParameter("password", Settings.Password)
.AddFormParameter("keeplogged", "1")
.AddFormParameter("login", "Log+In!")
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
.SetHeader("Referer", LoginUrl)
.Build();
var response = await ExecuteAuth(authLoginRequest);
if (CheckIfLoginNeeded(response))
{
var parser = new HtmlParser();
using var dom = parser.ParseDocument(response.Content);
var errorMessage = dom.QuerySelector("form#loginform")?.TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
}
cookies = response.GetCookies();
UpdateCookies(cookies, DateTime.Now.AddDays(30));
_logger.Debug("Anthelion authentication succeeded.");
}
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{
return !httpResponse.Content.Contains("logout.php");
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
}
};
caps.Categories.AddCategoryMapping("1", NewznabStandardCategory.Movies, "Film/Feature");
caps.Categories.AddCategoryMapping("2", NewznabStandardCategory.Movies, "Film/Short");
caps.Categories.AddCategoryMapping("3", NewznabStandardCategory.TV, "TV/Miniseries");
caps.Categories.AddCategoryMapping("4", NewznabStandardCategory.Other, "Other");
return caps;
}
}
public class AnthelionRequestGenerator : IIndexerRequestGenerator
{
public UserPassTorrentBaseSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
{
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
// TODO: IMDB search is available but it requires to parse the details page
var qc = new NameValueCollection
{
{ "order_by", "time" },
{ "order_way", "desc" },
{ "action", "basic" },
{ "searchsubmit", "1" },
{ "searchstr", imdbId.IsNotNullOrWhiteSpace() ? imdbId : term.Replace(".", " ") }
};
var catList = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
foreach (var cat in catList)
{
qc.Add($"filter_cat[{cat}]", "1");
}
searchUrl = searchUrl + "?" + qc.GetQueryString();
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
yield return request;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.FullImdbId));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories, searchCriteria.FullImdbId));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AnthelionParser : IParseIndexerResponse
{
private readonly UserPassTorrentBaseSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public AnthelionParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
using var doc = parser.ParseDocument(indexerResponse.Content);
var rows = doc.QuerySelectorAll("table.torrent_table > tbody > tr.torrent");
foreach (var row in rows)
{
var qDetailsLink = row.QuerySelector("a.torrent_name");
var year = qDetailsLink.NextSibling.TextContent.Replace("[", "").Replace("]", "").Trim();
var tags = row.QuerySelector("div.torrent_info").FirstChild.TextContent.Replace(" / ", " ").Trim();
var title = $"{qDetailsLink.TextContent} {year} {tags}";
var description = row.QuerySelector("div.tags").TextContent.Trim();
var details = _settings.BaseUrl + qDetailsLink.GetAttribute("href");
var torrentId = qDetailsLink.GetAttribute("href").Split('=').Last();
var link = _settings.BaseUrl + "torrents.php?action=download&id=" + torrentId;
var posterStr = qDetailsLink.GetAttribute("data-cover");
var poster = !string.IsNullOrWhiteSpace(posterStr) ? posterStr : null;
var files = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(3)").TextContent);
var publishDate = DateTimeUtil.FromTimeAgo(row.QuerySelector("td:nth-child(4)").TextContent);
var size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(5)").FirstChild.TextContent);
var grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(6)").TextContent);
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(7)").TextContent);
var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(8)").TextContent);
var dlVolumeFactor = row.QuerySelector("strong.tl_free") != null ? 0 : 1;
var cat = row.QuerySelector("td.cats_col > div").GetAttribute("class").Replace("tooltip cats_", "");
var category = new List<IndexerCategory>
{
cat switch
{
"featurefilm" => NewznabStandardCategory.Movies,
"shortfilm" => NewznabStandardCategory.Movies,
"miniseries" => NewznabStandardCategory.TV,
"other" => NewznabStandardCategory.Other,
_ => throw new Exception($"Unknown category: {cat}")
}
};
// TODO: TMDb is also available
var qImdb = row.QuerySelector("a[href^=\"https://www.imdb.com\"]");
var imdb = qImdb != null ? ParseUtil.GetImdbId(qImdb.GetAttribute("href").Split('/').Last()) : null;
var release = new TorrentInfo
{
MinimumRatio = 1,
MinimumSeedTime = 259200,
Description = description,
Title = title,
PublishDate = publishDate,
Categories = category,
DownloadUrl = link,
InfoUrl = details,
PosterUrl = poster,
Guid = link,
ImdbId = imdb.GetValueOrDefault(),
Seeders = seeders,
Peers = leechers + seeders,
Size = size,
Grabs = grabs,
Files = files,
DownloadVolumeFactor = dlVolumeFactor,
UploadVolumeFactor = 1
};
torrentInfos.Add(release);
}
return torrentInfos.ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
}

View File

@@ -1,299 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions;
[Obsolete("User Agent blocked")]
public class AudioBookBay : TorrentIndexerBase<NoAuthTorrentBaseSettings>
{
public override string Name => "AudioBook Bay";
public override string[] IndexerUrls => new[]
{
"https://audiobookbay.is/"
};
public override string[] LegacyUrls => new[]
{
"https://audiobookbay.la/",
"http://audiobookbay.net/",
"https://audiobookbay.unblockit.tv/",
"http://audiobookbay.nl/",
"http://audiobookbay.ws/",
"https://audiobookbay.unblockit.how/",
"https://audiobookbay.unblockit.cam/",
"https://audiobookbay.unblockit.biz/",
"https://audiobookbay.unblockit.day/",
"https://audiobookbay.unblockit.llc/",
"https://audiobookbay.unblockit.blue/",
"https://audiobookbay.unblockit.name/",
"http://audiobookbay.fi/",
"http://audiobookbay.se/",
"http://audiobookbayabb.com/",
"https://audiobookbay.unblockit.ist/",
"https://audiobookbay.unblockit.bet/",
"https://audiobookbay.unblockit.cat/",
"https://audiobookbay.unblockit.nz/",
"https://audiobookbay.fi/",
"https://audiobookbay.unblockit.page/",
"https://audiobookbay.unblockit.pet/",
"https://audiobookbay.unblockit.ink/",
"https://audiobookbay.unblockit.bio/", // error 502
"https://audiobookbay.li/",
"https://audiobookbay.se/" // redirects to .is but has invalid CA
};
public override string Description => "AudioBook Bay (ABB) is a public Torrent Tracker for AUDIOBOOKS";
public override string Language => "en-US";
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
public override int PageSize => 15;
public override TimeSpan RateLimit => TimeSpan.FromSeconds(5);
public override IndexerCapabilities Capabilities => SetCapabilities();
public AudioBookBay(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new AudioBookBayRequestGenerator(Settings);
}
public override IParseIndexerResponse GetParser()
{
return new AudioBookBayParser(Settings, Capabilities.Categories);
}
public override async Task<byte[]> Download(Uri link)
{
var request = new HttpRequestBuilder(link.ToString())
.SetCookies(GetCookies() ?? new Dictionary<string, string>())
.Accept(HttpAccept.Html)
.Build();
var response = await _httpClient.ExecuteProxiedAsync(request, Definition);
var parser = new HtmlParser();
using var dom = parser.ParseDocument(response.Content);
var hash = dom.QuerySelector("td:contains(\"Info Hash:\") ~ td")?.TextContent.Trim();
if (hash == null)
{
throw new Exception($"Failed to fetch hash from {link}");
}
var title = dom.QuerySelector("div.postTitle h1")?.TextContent.Trim();
if (title == null)
{
throw new Exception($"Failed to fetch title from {link}");
}
title = StringUtil.MakeValidFileName(title, '_', false);
var magnet = MagnetLinkBuilder.BuildPublicMagnetLink(hash, title);
return await base.Download(new Uri(magnet));
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.AudioAudiobook);
return caps;
}
}
public class AudioBookBayRequestGenerator : IIndexerRequestGenerator
{
private readonly NoAuthTorrentBaseSettings _settings;
public AudioBookBayRequestGenerator(NoAuthTorrentBaseSettings settings)
{
_settings = settings;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}"));
return pageableRequests;
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term)
{
var searchUrl = _settings.BaseUrl;
var parameters = new NameValueCollection();
term = Regex.Replace(term, @"[\W]+", " ").Trim();
if (term.IsNotNullOrWhiteSpace())
{
parameters.Set("s", term);
parameters.Set("tt", "1");
}
if (parameters.Count > 0)
{
searchUrl += $"?{parameters.GetQueryString()}";
}
yield return new IndexerRequest(new UriBuilder(searchUrl) { Path = "/" }.Uri.AbsoluteUri, HttpAccept.Html);
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class AudioBookBayParser : IParseIndexerResponse
{
private readonly NoAuthTorrentBaseSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public AudioBookBayParser(NoAuthTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var releaseInfos = new List<ReleaseInfo>();
using var doc = ParseHtmlDocument(indexerResponse.Content);
var rows = doc.QuerySelectorAll("div.post:has(div[class=\"postTitle\"])");
foreach (var row in rows)
{
var infoUrl = _settings.BaseUrl + row.QuerySelector("div.postTitle h2 a")?.GetAttribute("href")?.Trim().TrimStart('/');
var title = row.QuerySelector("div.postTitle")?.TextContent.Trim();
var infoString = row.QuerySelector("div.postContent")?.TextContent.Trim() ?? string.Empty;
var matchFormat = Regex.Match(infoString, @"Format: (.+) \/", RegexOptions.IgnoreCase);
if (matchFormat.Groups[1].Success && matchFormat.Groups[1].Value.Length > 0 && matchFormat.Groups[1].Value != "?")
{
title += $" [{matchFormat.Groups[1].Value.Trim()}]";
}
var matchBitrate = Regex.Match(infoString, @"Bitrate: (.+)File", RegexOptions.IgnoreCase);
if (matchBitrate.Groups[1].Success && matchBitrate.Groups[1].Value.Length > 0 && matchBitrate.Groups[1].Value != "?")
{
title += $" [{matchBitrate.Groups[1].Value.Trim()}]";
}
var matchSize = Regex.Match(infoString, @"File Size: (.+?)s?$", RegexOptions.IgnoreCase);
var size = matchSize.Groups[1].Success ? ParseUtil.GetBytes(matchSize.Groups[1].Value) : 0;
var matchDateAdded = Regex.Match(infoString, @"Posted: (\d{1,2} \D{3} \d{4})", RegexOptions.IgnoreCase);
var publishDate = matchDateAdded.Groups[1].Success && DateTime.TryParseExact(matchDateAdded.Groups[1].Value, "d MMM yyyy", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsedDate) ? parsedDate : DateTime.Now;
var postInfo = row.QuerySelector("div.postInfo")?.FirstChild?.TextContent.Trim().Replace("\xA0", ";") ?? string.Empty;
var matchCategory = Regex.Match(postInfo, @"Category: (.+)$", RegexOptions.IgnoreCase);
var genres = matchCategory.Groups[1].Success ? matchCategory.Groups[1].Value.Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList() : new List<string>();
var release = new TorrentInfo
{
Guid = infoUrl,
InfoUrl = infoUrl,
DownloadUrl = infoUrl,
Title = CleanTitle(title),
Categories = new List<IndexerCategory> { NewznabStandardCategory.AudioAudiobook },
Size = size,
Seeders = 1,
Peers = 1,
PublishDate = publishDate,
DownloadVolumeFactor = 0,
UploadVolumeFactor = 1,
Genres = genres
};
var cover = row.QuerySelector("img[src]")?.GetAttribute("src")?.Trim();
if (!string.IsNullOrEmpty(cover))
{
release.PosterUrl = cover.StartsWith("http") ? cover : _settings.BaseUrl + cover;
}
releaseInfos.Add(release);
}
return releaseInfos;
}
private static IHtmlDocument ParseHtmlDocument(string response)
{
var parser = new HtmlParser();
var doc = parser.ParseDocument(response);
var hidden = doc.QuerySelectorAll("div.post.re-ab");
foreach (var element in hidden)
{
var body = doc.CreateElement("div");
body.ClassList.Add("post");
body.InnerHtml = Encoding.UTF8.GetString(Convert.FromBase64String(element.TextContent));
element.Parent.ReplaceChild(body, element);
}
return doc;
}
private static string CleanTitle(string title)
{
title = Regex.Replace(title, @"[\u0000-\u0008\u000A-\u001F\u0100-\uFFFF]", string.Empty, RegexOptions.Compiled);
title = Regex.Replace(title, @"\s+", " ", RegexOptions.Compiled | RegexOptions.IgnoreCase);
return title.Trim();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}

View File

@@ -1,315 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AngleSharp.Dom;
using AngleSharp.Html.Parser;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete("Site has shutdown")]
public class BB : TorrentIndexerBase<UserPassTorrentBaseSettings>
{
public override string Name => "BB";
public override string[] IndexerUrls => new[] { Base64Extensions.FromBase64("aHR0cHM6Ly9iYWNvbmJpdHMub3JnLw==") };
private string LoginUrl => Settings.BaseUrl + "login.php";
public override string Description => "BB is a Private Torrent Tracker for 0DAY / GENERAL";
public override string Language => "en-US";
public override Encoding Encoding => Encoding.UTF8;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
public BB(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new BBRequestGenerator { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new BBParser(Settings, Capabilities.Categories);
}
protected override async Task DoLogin()
{
var requestBuilder = new HttpRequestBuilder(LoginUrl)
{
LogResponseContent = true,
AllowAutoRedirect = true,
Method = HttpMethod.Post
};
var cookies = Cookies;
Cookies = null;
var authLoginRequest = requestBuilder
.AddFormParameter("username", Settings.Username)
.AddFormParameter("password", Settings.Password)
.AddFormParameter("keeplogged", "1")
.AddFormParameter("login", "Log+In!")
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
.SetHeader("Referer", LoginUrl)
.Build();
var response = await ExecuteAuth(authLoginRequest);
if (CheckIfLoginNeeded(response))
{
var parser = new HtmlParser();
using var dom = parser.ParseDocument(response.Content);
var messageEl = dom.QuerySelectorAll("#loginform");
var messages = new List<string>();
for (var i = 0; i < 13; i++)
{
var child = messageEl[0].ChildNodes[i];
messages.Add(child.Text().Trim());
}
var message = string.Join(" ", messages);
throw new IndexerAuthException(message);
}
cookies = response.GetCookies();
UpdateCookies(cookies, DateTime.Now.AddDays(30));
_logger.Debug("BB authentication succeeded.");
}
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{
return !httpResponse.Content.Contains("logout.php");
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
},
MusicSearchParams = new List<MusicSearchParam>
{
MusicSearchParam.Q
},
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Audio);
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.AudioMP3);
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.AudioLossless);
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.PC);
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.BooksEBook);
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.AudioAudiobook);
caps.Categories.AddCategoryMapping(5, NewznabStandardCategory.Other);
caps.Categories.AddCategoryMapping(6, NewznabStandardCategory.BooksMags);
caps.Categories.AddCategoryMapping(7, NewznabStandardCategory.BooksComics);
caps.Categories.AddCategoryMapping(8, NewznabStandardCategory.TVAnime);
caps.Categories.AddCategoryMapping(9, NewznabStandardCategory.Movies);
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.TVHD);
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.TVSD);
caps.Categories.AddCategoryMapping(10, NewznabStandardCategory.TV);
caps.Categories.AddCategoryMapping(11, NewznabStandardCategory.PCGames);
caps.Categories.AddCategoryMapping(12, NewznabStandardCategory.Console);
caps.Categories.AddCategoryMapping(13, NewznabStandardCategory.Other);
caps.Categories.AddCategoryMapping(14, NewznabStandardCategory.Other);
return caps;
}
}
public class BBRequestGenerator : IIndexerRequestGenerator
{
public UserPassTorrentBaseSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories)
{
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
// TODO: IMDB search is available but it requires to parse the details page
var qc = new NameValueCollection
{
{ "order_by", "s3" },
{ "order_way", "desc" },
{ "disablegrouping", "1" },
{ "searchtags", "" },
{ "tags_type", "0" },
{ "action", "basic" },
{ "searchstr", term.Replace(".", " ") }
};
var catList = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
foreach (var cat in catList)
{
qc.Add($"filter_cat[{cat}]", "1");
}
searchUrl = searchUrl + "?" + qc.GetQueryString();
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
yield return request;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class BBParser : IParseIndexerResponse
{
private readonly UserPassTorrentBaseSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public BBParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
using var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("#torrent_table > tbody > tr.torrent");
foreach (var row in rows)
{
var release = new TorrentInfo();
release.MinimumRatio = 1;
release.MinimumSeedTime = 172800; // 48 hours
var catStr = row.Children[0].FirstElementChild.GetAttribute("href").Split(new[] { '[', ']' })[1];
release.Categories = _categories.MapTrackerCatToNewznab(catStr);
var qDetails = row.Children[1].QuerySelector("a[title='View Torrent']");
release.InfoUrl = _settings.BaseUrl + qDetails.GetAttribute("href");
release.Guid = release.InfoUrl;
var qDownload = row.Children[1].QuerySelector("a[title='Download']");
release.DownloadUrl = _settings.BaseUrl + qDownload.GetAttribute("href");
var dateStr = row.Children[3].TextContent.Trim().Replace(" and", "");
release.PublishDate = DateTimeUtil.FromTimeAgo(dateStr);
var sizeStr = row.Children[4].TextContent;
release.Size = ParseUtil.GetBytes(sizeStr);
release.Files = ParseUtil.CoerceInt(row.Children[2].TextContent.Trim());
release.Seeders = ParseUtil.CoerceInt(row.Children[7].TextContent.Trim());
release.Peers = ParseUtil.CoerceInt(row.Children[8].TextContent.Trim()) + release.Seeders;
var grabs = row.QuerySelector("td:nth-child(6)").TextContent;
release.Grabs = ParseUtil.CoerceInt(grabs);
if (row.QuerySelector("strong:contains(\"Freeleech!\")") != null)
{
release.DownloadVolumeFactor = 0;
}
else
{
release.DownloadVolumeFactor = 1;
}
release.UploadVolumeFactor = 1;
var title = row.QuerySelector("td:nth-child(2)");
foreach (var element in title.QuerySelectorAll("span, strong, div, br"))
{
element.Remove();
}
release.Title = ParseUtil.NormalizeMultiSpaces(title.TextContent.Replace(" - ]", "]"));
//change "Season #" to "S##" for TV shows
if (catStr == "10")
{
release.Title = Regex.Replace(release.Title,
@"Season (\d+)",
m => string.Format("S{0:00}",
int.Parse(m.Groups[1].Value)));
}
torrentInfos.Add(release);
}
return torrentInfos.ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
}

View File

@@ -352,7 +352,7 @@ namespace NzbDrone.Core.Indexers.Definitions
[FieldDefinition(7, Label = "IndexerBeyondHDSettingsRewindOnly", Type = FieldType.Checkbox, HelpText = "IndexerBeyondHDSettingsRewindOnlyHelpText")]
public bool RewindOnly { get; set; }
[FieldDefinition(8, Label = "IndexerBeyondHDSettingsSearchTypes", Type = FieldType.TagSelect, SelectOptions = typeof(BeyondHDSearchType), HelpText = "IndexerBeyondHDSettingsSearchTypesHelpText", Advanced = true)]
[FieldDefinition(8, Label = "IndexerBeyondHDSettingsSearchTypes", Type = FieldType.Select, SelectOptions = typeof(BeyondHDSearchType), HelpText = "IndexerBeyondHDSettingsSearchTypesHelpText", Advanced = true)]
public IEnumerable<int> SearchTypes { get; set; }
public override NzbDroneValidationResult Validate()

View File

@@ -205,7 +205,7 @@ namespace NzbDrone.Core.Indexers.Definitions
.AddQueryParam("request", "quick_user")
.Build();
var indexResponse = await _httpClient.ExecuteAsync(request).ConfigureAwait(false);
var indexResponse = await _httpClient.ExecuteProxiedAsync(request, Definition).ConfigureAwait(false);
var index = Json.Deserialize<GazelleGamesUserResponse>(indexResponse.Content);
@@ -412,6 +412,8 @@ namespace NzbDrone.Core.Indexers.Definitions
categories = _categories.MapTrackerCatToNewznab(torrent.Value.CategoryId.ToString()).ToArray();
}
Enum.TryParse(torrent.Value.FreeTorrent, true, out GazelleGamesFreeTorrent freeTorrent);
var release = new TorrentInfo
{
Guid = infoUrl,
@@ -426,8 +428,8 @@ namespace NzbDrone.Core.Indexers.Definitions
Peers = torrent.Value.Leechers + torrent.Value.Seeders,
PublishDate = torrent.Value.Time.ToUniversalTime(),
Scene = torrent.Value.Scene == 1,
DownloadVolumeFactor = torrent.Value.FreeTorrent is GazelleGamesFreeTorrent.FreeLeech or GazelleGamesFreeTorrent.Neutral || torrent.Value.LowSeedFL ? 0 : 1,
UploadVolumeFactor = torrent.Value.FreeTorrent == GazelleGamesFreeTorrent.Neutral ? 0 : 1,
DownloadVolumeFactor = freeTorrent is GazelleGamesFreeTorrent.FreeLeech or GazelleGamesFreeTorrent.Neutral || torrent.Value.LowSeedFL ? 0 : 1,
UploadVolumeFactor = freeTorrent == GazelleGamesFreeTorrent.Neutral ? 0 : 1,
MinimumSeedTime = 288000 // Minimum of 3 days and 8 hours (80 hours in total)
};
@@ -588,7 +590,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public int? Snatched { get; set; }
public int Seeders { get; set; }
public int Leechers { get; set; }
public GazelleGamesFreeTorrent FreeTorrent { get; set; }
public string FreeTorrent { get; set; }
public bool PersonalFL { get; set; }
public bool LowSeedFL { get; set; }

View File

@@ -1,310 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using AngleSharp.Dom;
using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions;
[Obsolete("Converted to Torznab")]
public class MoreThanTV : TorrentIndexerBase<CookieTorrentBaseSettings>
{
public override string Name => "MoreThanTV";
public override string[] IndexerUrls => new[] { "https://www.morethantv.me/" };
public override string Description => "Private torrent tracker for TV / MOVIES";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
public override bool FollowRedirect => true;
public MoreThanTV(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
=> new MoreThanTVRequestGenerator(Settings, Capabilities);
public override IParseIndexerResponse GetParser()
=> new MoreThanTVParser
{
Settings = Settings
};
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.Movies, "Movies");
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.TV, "TV");
return caps;
}
protected override IDictionary<string, string> GetCookies()
{
return CookieUtil.CookieHeaderToDictionary(Settings.Cookie);
}
}
public class MoreThanTVRequestGenerator : IIndexerRequestGenerator
{
private CookieTorrentBaseSettings Settings { get; }
private IndexerCapabilities Capabilities { get; }
private NameValueCollection BrowserHeaders { get; }
public MoreThanTVRequestGenerator(CookieTorrentBaseSettings settings, IndexerCapabilities capabilities)
{
Settings = settings;
Capabilities = capabilities;
BrowserHeaders = new NameValueCollection()
{
{ "referer", settings.BaseUrl },
{ "Upgrade-Insecure-Requests", "1" },
{ "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36" }
};
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
=> PerformRequest(searchCriteria);
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
=> PerformRequest(searchCriteria);
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
=> PerformRequest(searchCriteria);
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
=> PerformRequest(searchCriteria);
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
=> PerformRequest(searchCriteria);
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
private IndexerPageableRequestChain PerformRequest(SearchCriteriaBase query)
{
var chain = new IndexerPageableRequestChain();
var requests = new List<IndexerRequest> { new (new HttpRequest(GetTorrentSearchUrl(query)) { Headers = new HttpHeader(BrowserHeaders), AllowAutoRedirect = true }) };
if (query is TvSearchCriteria tvSearchCriteria)
{
// Always search for torrent groups (complete seasons) too
var seasonRegex = new Regex(@".*\s[Ss]{1}\d{2}([Ee]{1}\d{2,3})?$", RegexOptions.Compiled);
var seasonMatch = seasonRegex.Match(query.SanitizedSearchTerm);
if (seasonMatch.Success)
{
var seasonReplaceRegex = new Regex(@"[Ss]{1}\d{2}([Ee]{1}\d{2,3})?", RegexOptions.Compiled);
var newSearchQuery = seasonReplaceRegex.Replace(query.SanitizedSearchTerm, $"Season {tvSearchCriteria.Season}");
requests.Add(new IndexerRequest(new HttpRequest(GetTorrentSearchUrl(query, newSearchQuery)) { Headers = new HttpHeader(BrowserHeaders), AllowAutoRedirect = true }));
}
}
chain.Add(requests);
return chain;
}
private string GetTorrentSearchUrl(SearchCriteriaBase query, string overrideSearchTerm = null)
{
var qc = new NameValueCollection
{
{ "action", "advanced" },
{ "sizetype", "gb" },
{ "sizerange", "0.01" },
{ "title", overrideSearchTerm ?? GetSearchString(query.SanitizedSearchTerm) }
};
switch (query)
{
case MovieSearchCriteria:
qc.Add("filter_cat[1]", "1"); // HD Movies
qc.Add("filter_cat[2]", "1"); // SD Movies
break;
case TvSearchCriteria:
qc.Add("filter_cat[3]", "1"); // HD Episode
qc.Add("filter_cat[4]", "1"); // SD Episode
qc.Add("filter_cat[5]", "1"); // HD Season
qc.Add("filter_cat[6]", "1"); // SD Season
break;
}
return $"{Settings.BaseUrl}torrents/browse?{qc.GetQueryString()}";
}
private string GetSearchString(string input)
{
input = input.Replace("Marvels", "Marvel"); // strip 's for better results
var regex = new Regex(@"(S\d{2})$", RegexOptions.Compiled);
return regex.Replace(input, "$1*"); // If we're just seaching for a season (no episode) append an * to include all episodes of that season.
}
}
public class MoreThanTVParser : IParseIndexerResponse
{
public CookieTorrentBaseSettings Settings { get; init; }
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var releases = new List<ReleaseInfo>();
try
{
var parser = new HtmlParser();
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" };
// Loop through all torrents checking for groups
foreach (var torrent in torrents)
{
// Parse required data
var downloadAnchor = torrent.QuerySelector("span a[href^=\"/torrents.php?action=download\"]");
if (downloadAnchor == null)
{
continue;
}
var title = downloadAnchor.ParentElement.ParentElement.ParentElement.QuerySelector("a[class=\"overlay_torrent\"]").TextContent.Trim();
title = CleanUpTitle(title);
var category = torrent.QuerySelector(".cats_col div").GetAttribute("title");
// default to Other
var indexerCategory = NewznabStandardCategory.Other;
if (movies.Any(category.Contains))
{
indexerCategory = NewznabStandardCategory.Movies;
}
else if (tv.Any(category.Contains))
{
indexerCategory = NewznabStandardCategory.TV;
}
releases.Add(GetReleaseInfo(torrent, downloadAnchor, title, indexerCategory));
}
return releases;
}
catch (Exception ex)
{
throw new Exception("Error while parsing torrent response", ex);
}
}
/// <summary>
/// Gather Release info from torrent table. Target using css
/// </summary>
/// <param name="row"></param>
/// <param name="downloadAnchor"></param>
/// <param name="title"></param>
/// <param name="category"></param>
/// <returns></returns>
private ReleaseInfo GetReleaseInfo(IElement row, IElement downloadAnchor, string title, IndexerCategory category)
{
// count from bottom
const int FILES_COL = 7;
/*const int COMMENTS_COL = 7;*/
const int DATE_COL = 6;
const int FILESIZE_COL = 5;
const int SNATCHED_COL = 4;
const int SEEDS_COL = 3;
const int LEECHERS_COL = 2;
/*const int USER_COL = 1;*/
var downloadAnchorHref = (downloadAnchor as IHtmlAnchorElement).Href;
var queryParams = HttpUtility.ParseQueryString(downloadAnchorHref, Encoding.UTF8);
var torrentId = queryParams["id"];
var qFiles = row.QuerySelector("td:nth-last-child(" + FILES_COL + ")").TextContent;
var fileCount = ParseUtil.CoerceInt(qFiles);
var qPublishDate = row.QuerySelector("td:nth-last-child(" + DATE_COL + ") .time").Attributes["title"].Value;
var publishDate = DateTime.ParseExact(qPublishDate, "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToLocalTime();
var qPoster = row.QuerySelector("div.tp-banner img")?.GetAttribute("src");
var poster = (qPoster != null && !qPoster.Contains("caticons")) ? qPoster : null;
var description = row.QuerySelector("div.tags")?.TextContent.Trim();
var fileSize = row.QuerySelector("td:nth-last-child(" + FILESIZE_COL + ")").TextContent.Trim();
var snatched = row.QuerySelector("td:nth-last-child(" + SNATCHED_COL + ")").TextContent.Trim();
var seeds = row.QuerySelector("td:nth-last-child(" + SEEDS_COL + ")").TextContent.Trim();
var leechs = row.QuerySelector("td:nth-last-child(" + LEECHERS_COL + ")").TextContent.Trim();
if (fileSize.Length <= 0 || snatched.Length <= 0 || seeds.Length <= 0 || leechs.Length <= 0)
{
// Size (xx.xx GB[ (Max)]) Snatches (xx) Seeders (xx) Leechers (xx)
throw new Exception($"We expected 4 torrent datas.");
}
var detailUrl = $"{Settings.BaseUrl}details.php";
var size = ParseUtil.GetBytes(fileSize);
var grabs = int.Parse(snatched, NumberStyles.AllowThousands, CultureInfo.InvariantCulture);
var seeders = int.Parse(seeds, NumberStyles.AllowThousands, CultureInfo.InvariantCulture);
var leechers = int.Parse(leechs, NumberStyles.AllowThousands, CultureInfo.InvariantCulture);
var detailsUrl = $"{detailUrl}?torrentid={torrentId}";
var downloadUrl = $"{detailUrl}?action=download&id={torrentId}";
var categories = new List<IndexerCategory> { category };
return new TorrentInfo
{
Title = title,
Categories = categories,
DownloadUrl = downloadUrl,
PublishDate = publishDate,
PosterUrl = poster,
Description = description,
Seeders = seeders,
Peers = seeders + leechers,
Files = fileCount,
Size = size,
Grabs = grabs,
Guid = downloadUrl,
InfoUrl = detailsUrl,
DownloadVolumeFactor = 0, // ratioless tracker
UploadVolumeFactor = 1
};
}
/// <summary>
/// Clean Up any title stuff
/// </summary>
/// <param name="title"></param>
/// <returns></returns>
private string CleanUpTitle(string title)
{
return title
.Replace(".", " ")
.Replace("4K", "2160p"); // sonarr cleanup
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}

View File

@@ -1,292 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using AngleSharp.Html.Parser;
using Newtonsoft.Json;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
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";
public override string[] IndexerUrls => new[] { "https://piratethenet.org/" };
public override string[] LegacyUrls => new[] { "http://piratethenet.org/" };
public override string Description => "PirateTheNet (PTN) is a ratioless movie tracker.";
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
private string LoginUrl => Settings.BaseUrl + "takelogin.php";
private string CaptchaUrl => Settings.BaseUrl + "simpleCaptcha.php?numImages=1";
public PirateTheNet(IIndexerHttpClient httpClient,
IEventAggregator eventAggregator,
IIndexerStatusService indexerStatusService,
IConfigService configService,
Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new PirateTheNetRequestGenerator(Settings, Capabilities);
}
public override IParseIndexerResponse GetParser()
{
return new PirateTheNetParser(Settings, Capabilities.Categories);
}
protected override async Task DoLogin()
{
var captchaPage = await ExecuteAuth(new HttpRequest(CaptchaUrl));
var captchaResponse = JsonConvert.DeserializeAnonymousType(captchaPage.Content, new
{
images = new[] { new { hash = string.Empty } }
});
var requestBuilder = new HttpRequestBuilder(LoginUrl)
{
LogResponseContent = true,
AllowAutoRedirect = true,
Method = HttpMethod.Post
};
var authLoginRequest = requestBuilder
.SetCookies(captchaPage.GetCookies())
.AddFormParameter("username", Settings.Username)
.AddFormParameter("password", Settings.Password)
.AddFormParameter("captchaSelection", captchaResponse.images[0].hash)
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
.SetHeader("Referer", LoginUrl)
.Build();
var response = await ExecuteAuth(authLoginRequest);
if (CheckIfLoginNeeded(response))
{
throw new IndexerAuthException("Login Failed.");
}
var cookies = response.GetCookies();
UpdateCookies(cookies, DateTime.Now.AddDays(30));
_logger.Debug("Authentication succeeded.");
}
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{
return !httpResponse.Content.Contains("logout.php");
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
}
};
caps.Categories.AddCategoryMapping("1080P", NewznabStandardCategory.MoviesHD, "1080P");
caps.Categories.AddCategoryMapping("2160P", NewznabStandardCategory.MoviesHD, "2160P");
caps.Categories.AddCategoryMapping("720P", NewznabStandardCategory.MoviesHD, "720P");
caps.Categories.AddCategoryMapping("BDRip", NewznabStandardCategory.MoviesSD, "BDRip");
caps.Categories.AddCategoryMapping("BluRay", NewznabStandardCategory.MoviesBluRay, "BluRay");
caps.Categories.AddCategoryMapping("BRRip", NewznabStandardCategory.MoviesSD, "BRRip");
caps.Categories.AddCategoryMapping("DVDR", NewznabStandardCategory.MoviesDVD, "DVDR");
caps.Categories.AddCategoryMapping("DVDRip", NewznabStandardCategory.MoviesSD, "DVDRip");
caps.Categories.AddCategoryMapping("FLAC", NewznabStandardCategory.AudioLossless, "FLAC OST");
caps.Categories.AddCategoryMapping("MP3", NewznabStandardCategory.AudioMP3, "MP3 OST");
caps.Categories.AddCategoryMapping("MP4", NewznabStandardCategory.MoviesOther, "MP4");
caps.Categories.AddCategoryMapping("Packs", NewznabStandardCategory.MoviesOther, "Packs");
caps.Categories.AddCategoryMapping("R5", NewznabStandardCategory.MoviesDVD, "R5 / SCR");
caps.Categories.AddCategoryMapping("Remux", NewznabStandardCategory.MoviesOther, "Remux");
caps.Categories.AddCategoryMapping("TVRip", NewznabStandardCategory.MoviesOther, "TVRip");
caps.Categories.AddCategoryMapping("WebRip", NewznabStandardCategory.MoviesWEBDL, "WebRip");
return caps;
}
}
public class PirateTheNetRequestGenerator : IIndexerRequestGenerator
{
private readonly UserPassTorrentBaseSettings _settings;
private readonly IndexerCapabilities _capabilities;
public PirateTheNetRequestGenerator(UserPassTorrentBaseSettings settings, IndexerCapabilities capabilities)
{
_settings = settings;
_capabilities = capabilities;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories, searchCriteria.FullImdbId));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests($"{searchCriteria.SanitizedSearchTerm}", searchCriteria.Categories));
return pageableRequests;
}
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
{
var parameters = new NameValueCollection
{
{ "action", "torrentstable" },
{ "viewtype", "0" },
{ "visiblecategories", "Action,Adventure,Animation,Biography,Comedy,Crime,Documentary,Drama,Family,Fantasy,History,Horror,Kids,Music,Mystery,Packs,Romance,Sci-Fi,Short,Sports,Thriller,War,Western" },
{ "page", "1" },
{ "visibility", "showall" },
{ "compression", "showall" },
{ "sort", "added" },
{ "order", "DESC" },
{ "titleonly", "true" },
{ "packs", "showall" },
{ "bookmarks", "showall" },
{ "subscriptions", "showall" },
{ "skw", "showall" }
};
if (imdbId.IsNotNullOrWhiteSpace())
{
parameters.Set("advancedsearchparameters", $"[imdb={imdbId}]");
}
else if (term.IsNotNullOrWhiteSpace())
{
parameters.Set("searchstring", term);
}
var queryCats = _capabilities.Categories.MapTorznabCapsToTrackers(categories);
if (queryCats.Any())
{
parameters.Set("hiddenqualities", string.Join(",", queryCats));
}
var searchUrl = _settings.BaseUrl + "torrentsutils.php";
if (parameters.Count > 0)
{
searchUrl += $"?{parameters.GetQueryString()}";
}
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
yield return request;
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class PirateTheNetParser : IParseIndexerResponse
{
private readonly UserPassTorrentBaseSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public PirateTheNetParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var releaseInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
using var dom = parser.ParseDocument(indexerResponse.Content);
var rows = dom.QuerySelectorAll("table.main > tbody > tr");
foreach (var row in rows.Skip(1))
{
var qDetails = row.QuerySelector("td:nth-of-type(2) > a:nth-of-type(1)");
var title = qDetails?.GetAttribute("alt")?.Trim();
var infoUrl = _settings.BaseUrl + qDetails?.GetAttribute("href")?.TrimStart('/');
var downloadUrl = _settings.BaseUrl + row.QuerySelector("td > a:has(img[alt=\"Download Torrent\"])")?.GetAttribute("href")?.TrimStart('/');
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(9)")?.TextContent);
var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-of-type(10)")?.TextContent);
var cat = row.QuerySelector("td:nth-of-type(1) > a > img")?.GetAttribute("src")?.Split('/').Last().Split('.').First() ?? "packs";
var release = new TorrentInfo
{
Guid = infoUrl,
InfoUrl = infoUrl,
DownloadUrl = downloadUrl,
Title = title,
Categories = _categories.MapTrackerCatToNewznab(cat),
Seeders = seeders,
Peers = seeders + leechers,
Size = ParseUtil.GetBytes(row.QuerySelector("td:nth-of-type(7)")?.TextContent.Trim()),
Files = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(4)")?.TextContent),
Grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(8)")?.TextContent),
DownloadVolumeFactor = 0, // ratioless
UploadVolumeFactor = 1,
MinimumRatio = 1,
MinimumSeedTime = 259200, // 72 hours
};
var added = row.QuerySelector("td:nth-of-type(6) > nobr")?.TextContent.Trim();
if (added.StartsWith("Today "))
{
release.PublishDate = DateTime.Now.Date + DateTime.ParseExact(added.Split(" ", 2).Last(), "hh:mm tt", CultureInfo.InvariantCulture).TimeOfDay;
}
else if (added.StartsWith("Yesterday "))
{
release.PublishDate = DateTime.Now.AddDays(-1).Date + DateTime.ParseExact(added.Split(" ", 2).Last(), "hh:mm tt", CultureInfo.InvariantCulture).TimeOfDay;
}
else
{
release.PublishDate = DateTime.ParseExact(added, "MMM d yyyy hh:mm tt", CultureInfo.InvariantCulture);
}
releaseInfos.Add(release);
}
return releaseInfos.ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}

View File

@@ -1467,27 +1467,27 @@ namespace NzbDrone.Core.Indexers.Definitions
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
return GetPageableRequests(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
return GetPageableRequests(searchCriteria.SearchTerm, searchCriteria.Categories);
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return GetPageableRequests(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
return GetPageableRequests(searchCriteria.SearchTerm, searchCriteria.Categories);
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
return GetPageableRequests(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories, searchCriteria.Season ?? 0);
return GetPageableRequests(searchCriteria.SearchTerm, searchCriteria.Categories, searchCriteria.Season ?? 0);
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
return GetPageableRequests(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
return GetPageableRequests(searchCriteria.SearchTerm, searchCriteria.Categories);
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
return GetPageableRequests(searchCriteria.SanitizedSearchTerm, searchCriteria.Categories);
return GetPageableRequests(searchCriteria.SearchTerm, searchCriteria.Categories);
}
private IndexerPageableRequestChain GetPageableRequests(string searchTerm, int[] categories, int season = 0)
@@ -1525,8 +1525,10 @@ namespace NzbDrone.Core.Indexers.Definitions
}
else
{
// use the normal search
// replace any space, special char, etc. with % (wildcard)
searchString = new Regex("[^a-zA-Zа-яА-ЯёЁ0-9]+").Replace(searchString, "%");
searchString = searchString.Replace("-", " ");
if (season != 0)
{
searchString += " Сезон: " + season;

View File

@@ -19,6 +19,7 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Definitions;
@@ -50,7 +51,7 @@ public class Shazbat : TorrentIndexerBase<ShazbatSettings>
public override IParseIndexerResponse GetParser()
{
return new ShazbatParser(Settings, RateLimit, _httpClient, _logger);
return new ShazbatParser(Definition, Settings, RateLimit, _httpClient, _logger);
}
protected override async Task DoLogin()
@@ -101,7 +102,7 @@ public class Shazbat : TorrentIndexerBase<ShazbatSettings>
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
Flags = new List<IndexerFlag>
{
@@ -202,6 +203,7 @@ public class ShazbatRequestGenerator : IIndexerRequestGenerator
public class ShazbatParser : IParseIndexerResponse
{
private readonly ProviderDefinition _definition;
private readonly ShazbatSettings _settings;
private readonly TimeSpan _rateLimit;
private readonly IIndexerHttpClient _httpClient;
@@ -210,8 +212,9 @@ public class ShazbatParser : IParseIndexerResponse
private readonly Regex _torrentInfoRegex = new (@"\((?<size>\d+)\):(?<seeders>\d+) \/ :(?<leechers>\d+)$", RegexOptions.Compiled);
private readonly HashSet<string> _hdResolutions = new () { "1080p", "1080i", "720p" };
public ShazbatParser(ShazbatSettings settings, TimeSpan rateLimit, IIndexerHttpClient httpClient, Logger logger)
public ShazbatParser(ProviderDefinition definition, ShazbatSettings settings, TimeSpan rateLimit, IIndexerHttpClient httpClient, Logger logger)
{
_definition = definition;
_settings = settings;
_rateLimit = rateLimit;
_httpClient = httpClient;
@@ -272,7 +275,7 @@ public class ShazbatParser : IParseIndexerResponse
_logger.Debug("Downloading Feed " + showRequest.ToString());
var releaseRequest = new IndexerRequest(showRequest);
var releaseResponse = new IndexerResponse(releaseRequest, _httpClient.Execute(releaseRequest.HttpRequest));
var releaseResponse = new IndexerResponse(releaseRequest, _httpClient.ExecuteProxied(releaseRequest.HttpRequest, _definition));
if (releaseResponse.HttpResponse.Content.ContainsIgnoreCase("sign in now"))
{

View File

@@ -1,287 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using AngleSharp.Html.Parser;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.Settings;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers.Definitions
{
[Obsolete("Remove per Site Request Prowlarr Issue 573")]
public class TVVault : TorrentIndexerBase<UserPassTorrentBaseSettings>
{
public override string Name => "TVVault";
public override string[] IndexerUrls => new[] { "https://tv-vault.me/" };
private string LoginUrl => Settings.BaseUrl + "login.php";
public override string Description => "TV-Vault is a very unique tracker dedicated for old TV shows, TV movies and documentaries.";
public override string Language => "en-US";
public override Encoding Encoding => Encoding.UTF8;
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override IndexerCapabilities Capabilities => SetCapabilities();
public override TimeSpan RateLimit => TimeSpan.FromSeconds(5);
public TVVault(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
: base(httpClient, eventAggregator, indexerStatusService, configService, logger)
{
}
public override IIndexerRequestGenerator GetRequestGenerator()
{
return new TVVaultRequestGenerator { Settings = Settings, Capabilities = Capabilities };
}
public override IParseIndexerResponse GetParser()
{
return new TVVaultParser(Settings, Capabilities.Categories);
}
protected override async Task DoLogin()
{
var requestBuilder = new HttpRequestBuilder(LoginUrl)
{
LogResponseContent = true,
AllowAutoRedirect = true,
Method = HttpMethod.Post
};
var cookies = Cookies;
Cookies = null;
var authLoginRequest = requestBuilder
.AddFormParameter("username", Settings.Username)
.AddFormParameter("password", Settings.Password)
.AddFormParameter("keeplogged", "1")
.AddFormParameter("login", "Log+In!")
.SetHeader("Content-Type", "application/x-www-form-urlencoded")
.SetHeader("Referer", LoginUrl)
.Build();
var response = await ExecuteAuth(authLoginRequest);
if (CheckIfLoginNeeded(response))
{
var parser = new HtmlParser();
using var dom = parser.ParseDocument(response.Content);
var errorMessage = dom.QuerySelector("form#loginform")?.TextContent.Trim();
throw new IndexerAuthException(errorMessage ?? "Unknown error message, please report.");
}
cookies = response.GetCookies();
UpdateCookies(cookies, DateTime.Now.AddDays(30));
_logger.Debug("TVVault authentication succeeded.");
}
protected override bool CheckIfLoginNeeded(HttpResponse httpResponse)
{
return !httpResponse.Content.Contains("logout.php");
}
private IndexerCapabilities SetCapabilities()
{
var caps = new IndexerCapabilities
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
},
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
},
Flags = new List<IndexerFlag>
{
IndexerFlag.FreeLeech
}
};
caps.Categories.AddCategoryMapping(1, NewznabStandardCategory.TV);
caps.Categories.AddCategoryMapping(2, NewznabStandardCategory.Movies);
caps.Categories.AddCategoryMapping(3, NewznabStandardCategory.TVHD);
caps.Categories.AddCategoryMapping(4, NewznabStandardCategory.TVSD);
return caps;
}
}
public class TVVaultRequestGenerator : IIndexerRequestGenerator
{
public UserPassTorrentBaseSettings Settings { get; set; }
public IndexerCapabilities Capabilities { get; set; }
private IEnumerable<IndexerRequest> GetPagedRequests(string term, int[] categories, string imdbId = null)
{
var searchUrl = string.Format("{0}/torrents.php", Settings.BaseUrl.TrimEnd('/'));
var qc = new NameValueCollection
{
{ "order_by", "s3" },
{ "order_way", "DESC" },
{ "disablegrouping", "1" }
};
if (imdbId.IsNotNullOrWhiteSpace())
{
qc.Add("action", "advanced");
qc.Add("imdbid", imdbId);
}
else if (!string.IsNullOrWhiteSpace(term))
{
qc.Add("searchstr", StripSearchString(term));
}
var catList = Capabilities.Categories.MapTorznabCapsToTrackers(categories);
foreach (var cat in catList)
{
qc.Add($"filter_cat[{cat}]", "1");
}
searchUrl = searchUrl + "?" + qc.GetQueryString();
var request = new IndexerRequest(searchUrl, HttpAccept.Html);
yield return request;
}
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories, searchCriteria.FullImdbId));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(MusicSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(TvSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedTvSearchString), searchCriteria.Categories, searchCriteria.FullImdbId));
return pageableRequests;
}
public IndexerPageableRequestChain GetSearchRequests(BookSearchCriteria searchCriteria)
{
return new IndexerPageableRequestChain();
}
public IndexerPageableRequestChain GetSearchRequests(BasicSearchCriteria searchCriteria)
{
var pageableRequests = new IndexerPageableRequestChain();
pageableRequests.Add(GetPagedRequests(string.Format("{0}", searchCriteria.SanitizedSearchTerm), searchCriteria.Categories));
return pageableRequests;
}
private string StripSearchString(string term)
{
// Search does not support searching with episode numbers so strip it if we have one
// AND filter the result later to achieve the proper result
term = Regex.Replace(term, @"[S|E]\d\d", string.Empty);
return term.Trim();
}
public Func<IDictionary<string, string>> GetCookies { get; set; }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
public class TVVaultParser : IParseIndexerResponse
{
private readonly UserPassTorrentBaseSettings _settings;
private readonly IndexerCapabilitiesCategories _categories;
public TVVaultParser(UserPassTorrentBaseSettings settings, IndexerCapabilitiesCategories categories)
{
_settings = settings;
_categories = categories;
}
public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
{
var torrentInfos = new List<ReleaseInfo>();
var parser = new HtmlParser();
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=\"]")
.GetAttribute("href"));
var rssFeedQuery = HttpUtility.ParseQueryString(rssFeedUri.Query);
var downloadLinkExtraParams = "&authkey=" + rssFeedQuery["authkey"] + "&torrent_pass=" + rssFeedQuery["passkey"];
var rows = doc.QuerySelectorAll("table.torrent_table > tbody > tr.torrent");
foreach (var row in rows)
{
var qDetailsLink = row.QuerySelector("a[href^=\"torrents.php?id=\"]");
var title = qDetailsLink.TextContent;
var description = qDetailsLink.NextSibling.TextContent.Trim();
title += " " + description;
var details = _settings.BaseUrl + qDetailsLink.GetAttribute("href");
var torrentId = qDetailsLink.GetAttribute("href").Split('=').Last();
var link = _settings.BaseUrl + "torrents.php?action=download&id=" + torrentId + downloadLinkExtraParams;
var files = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(3)").TextContent);
var publishDate = DateTimeUtil.FromTimeAgo(row.QuerySelector("td:nth-child(4)").TextContent);
var size = ParseUtil.GetBytes(row.QuerySelector("td:nth-child(5)").FirstChild.TextContent);
var grabs = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(6)").TextContent);
var seeders = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(7)").TextContent);
var leechers = ParseUtil.CoerceInt(row.QuerySelector("td:nth-child(8)").TextContent);
var dlVolumeFactor = row.QuerySelector("strong.freeleech_normal") != null ? 0 : 1;
var category = new List<IndexerCategory> { TvCategoryFromQualityParser.ParseTvShowQuality(description) };
var release = new TorrentInfo
{
MinimumRatio = 1,
MinimumSeedTime = 0,
Description = description,
Title = title,
PublishDate = publishDate,
Categories = category,
DownloadUrl = link,
InfoUrl = details,
Guid = link,
Seeders = seeders,
Peers = leechers + seeders,
Size = size,
Grabs = grabs,
Files = files,
DownloadVolumeFactor = dlVolumeFactor,
UploadVolumeFactor = 1
};
torrentInfos.Add(release);
}
return torrentInfos.ToArray();
}
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
}
}

View File

@@ -32,7 +32,7 @@ namespace NzbDrone.Core.Indexers.Definitions.TorrentRss
public override IParseIndexerResponse GetParser()
{
return _torrentRssParserFactory.GetParser(Settings);
return _torrentRssParserFactory.GetParser(Settings, Definition);
}
public override IEnumerable<ProviderDefinition> DefaultDefinitions

View File

@@ -3,12 +3,13 @@ using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers.Definitions.TorrentRss
{
public interface ITorrentRssParserFactory
{
TorrentRssParser GetParser(TorrentRssIndexerSettings settings);
TorrentRssParser GetParser(TorrentRssIndexerSettings settings, ProviderDefinition definition);
}
public class TorrentRssParserFactory : ITorrentRssParserFactory
@@ -26,10 +27,10 @@ namespace NzbDrone.Core.Indexers.Definitions.TorrentRss
_logger = logger;
}
public TorrentRssParser GetParser(TorrentRssIndexerSettings indexerSettings)
public TorrentRssParser GetParser(TorrentRssIndexerSettings indexerSettings, ProviderDefinition definition)
{
var key = indexerSettings.ToJson();
var parserSettings = _settingsCache.Get(key, () => DetectParserSettings(indexerSettings), TimeSpan.FromDays(7));
var parserSettings = _settingsCache.Get(key, () => DetectParserSettings(indexerSettings, definition), TimeSpan.FromDays(7));
if (parserSettings.UseEZTVFormat)
{
@@ -51,9 +52,9 @@ namespace NzbDrone.Core.Indexers.Definitions.TorrentRss
};
}
private TorrentRssIndexerParserSettings DetectParserSettings(TorrentRssIndexerSettings indexerSettings)
private TorrentRssIndexerParserSettings DetectParserSettings(TorrentRssIndexerSettings indexerSettings, ProviderDefinition definition)
{
var settings = _torrentRssSettingsDetector.Detect(indexerSettings);
var settings = _torrentRssSettingsDetector.Detect(indexerSettings, definition);
if (settings == null)
{

View File

@@ -9,28 +9,29 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers.Definitions.TorrentRss
{
public interface ITorrentRssSettingsDetector
{
TorrentRssIndexerParserSettings Detect(TorrentRssIndexerSettings settings);
TorrentRssIndexerParserSettings Detect(TorrentRssIndexerSettings settings, ProviderDefinition definition);
}
public class TorrentRssSettingsDetector : ITorrentRssSettingsDetector
{
private const long ValidSizeThreshold = 2 * 1024 * 1024;
private readonly IHttpClient _httpClient;
private readonly IIndexerHttpClient _httpClient;
private readonly Logger _logger;
public TorrentRssSettingsDetector(IHttpClient httpClient, Logger logger)
public TorrentRssSettingsDetector(IIndexerHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
}
public TorrentRssIndexerParserSettings Detect(TorrentRssIndexerSettings settings)
public TorrentRssIndexerParserSettings Detect(TorrentRssIndexerSettings settings, ProviderDefinition definition)
{
_logger.Debug("Evaluating TorrentRss feed '{0}'", settings.BaseUrl);
@@ -43,7 +44,7 @@ namespace NzbDrone.Core.Indexers.Definitions.TorrentRss
try
{
httpResponse = _httpClient.Execute(request.HttpRequest);
httpResponse = _httpClient.ExecuteProxied(request.HttpRequest, definition);
}
catch (Exception ex)
{

View File

@@ -29,8 +29,6 @@ namespace NzbDrone.Core.Indexers
public int AppProfileId { get; set; }
public LazyLoaded<AppSyncProfile> AppProfile { get; set; }
public IndexerStatus Status { get; set; }
public List<SettingsField> ExtraFields { get; set; } = new List<SettingsField>();
public List<SettingsField> ExtraFields { get; set; } = new ();
}
}

View File

@@ -55,10 +55,10 @@ namespace NzbDrone.Core.Indexers
{
MapCardigannDefinition(definition);
}
catch
catch (Exception ex)
{
// Skip indexer if we fail in Cardigann mapping
_logger.Debug("Indexer '{0}' has no definition", definition.Name);
_logger.Debug(ex, "Indexer '{0}' has no definition", definition.Name);
}
}

View File

@@ -63,5 +63,8 @@ namespace NzbDrone.Core.Indexers
[FieldDefinition(4, Type = FieldType.Number, Label = "IndexerSettingsPackSeedTime", HelpText = "IndexerSettingsPackSeedTimeIndexerHelpText", Unit = "minutes", Advanced = true)]
public int? PackSeedTime { get; set; }
[FieldDefinition(5, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
}
}

View File

@@ -78,9 +78,9 @@
"UILanguage": "UI-sprog",
"Custom": "Brugerdefinerede",
"TagsSettingsSummary": "Se alle tags og hvordan de bruges. Ubrugte tags kan fjernes",
"Test": "Prøve",
"TestAll": "Test alle",
"TestAllClients": "Test alle klienter",
"Test": "Afprøv",
"TestAll": "Afprøv alle",
"TestAllClients": "Afprøv alle klienter",
"Type": "Type",
"UnableToAddANewIndexerProxyPleaseTryAgain": "Kunne ikke tilføje en ny indekser. Prøv igen.",
"UnableToAddANewNotificationPleaseTryAgain": "Kan ikke tilføje en ny underretning, prøv igen.",
@@ -92,7 +92,7 @@
"SetTags": "Indstil tags",
"YesCancel": "Ja, Annuller",
"AcceptConfirmationModal": "Accepter bekræftelsesmodal",
"AddIndexer": "Tilføj indeksør",
"AddIndexer": "Tilføj indekser",
"AnalyticsEnabledHelpText": "Send anonym brugs- og fejlinformation til {appName}s servere. Dette inkluderer information om din browser, hvilke af {appName}s WebUI-sider du bruger, fejlrapportering, samt OS- og runtime-version. Vi bruger disse oplysninger til at prioritere funktioner og fejlrettelser.",
"Backups": "Sikkerhedskopier",
"BypassProxyForLocalAddresses": "Bypass-proxy til lokale adresser",
@@ -315,7 +315,7 @@
"HistoryCleanupDaysHelpTextWarning": "Filer i papirkurven, der er ældre end det valgte antal dage, renses automatisk",
"OnGrab": "ved hentning",
"OnHealthIssue": "Om sundhedsspørgsmål",
"TestAllIndexers": "Test alle indeksører",
"TestAllIndexers": "Afprøv alle indeks",
"GrabReleases": "Hent udgivelse",
"Link": "Links",
"MappedDrivesRunningAsService": "Tilsluttede netværksdrev er ikke tilgængelige, når programmet kører som en Windows-tjeneste. Se FAQ'en for mere information",
@@ -375,5 +375,7 @@
"AddConnection": "Tilføj forbindelse",
"EditConnectionImplementation": "Tilføj forbindelse - {implementationName}",
"AddApplicationImplementation": "Tilføj forbindelse - {implementationName}",
"AddIndexerImplementation": "Tilføj betingelse - {implementationName}"
"AddIndexerImplementation": "Tilføj betingelse - {implementationName}",
"ApplyChanges": "Anvend ændringer",
"AddDownloadClientImplementation": "Tilføj downloadklient - {implementationName}"
}

View File

@@ -38,7 +38,7 @@
"AudioSearch": "Audio Suche",
"Auth": "Authentifizierung",
"Authentication": "Authentifizierung",
"AuthenticationMethodHelpText": "Für den Zugriff auf {appName} sind Benutzername und Passwort erforderlich.",
"AuthenticationMethodHelpText": "Für den Zugriff auf {appName} sind Benutzername und Passwort erforderlich",
"Automatic": "Automatisch",
"AutomaticSearch": "Automatische Suche",
"Backup": "Sicherung",
@@ -53,7 +53,7 @@
"BookSearch": "Buch Suche",
"BookSearchTypes": "Buch-Suchtypen",
"Branch": "Branch",
"BranchUpdate": "Verwendeter Branch zur Aktualisierung von {appName}",
"BranchUpdate": "Branch, der verwendet werden soll, um {appName} zu updaten",
"BranchUpdateMechanism": "Git-Branch für den externen Updateablauf",
"BypassProxyForLocalAddresses": "Proxy für lokale Adressen umgehen",
"Cancel": "Abbrechen",
@@ -533,7 +533,7 @@
"AuthenticationMethodHelpTextWarning": "Bitte wähle eine gültige Authentifizierungsmethode aus",
"AuthenticationRequiredPasswordHelpTextWarning": "Gib ein neues Passwort ein",
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Neues Passwort bestätigen",
"DefaultNameCopiedProfile": "{Name} Kopieren",
"DefaultNameCopiedProfile": "{name} Kopieren",
"AuthenticationMethod": "Authentifizierungsmethode",
"Clone": "Klonen",
"CountDownloadClientsSelected": "{count} Download-Client(s) ausgewählt",
@@ -545,5 +545,6 @@
"EditIndexerProxyImplementation": "Indexer Proxy hinzufügen - {implementationName}",
"CountApplicationsSelected": "{count} Ausgewählte Sammlung(en)",
"DownloadClientAriaSettingsDirectoryHelpText": "Optionaler Speicherort für Downloads. Lassen Sie das Feld leer, um den standardmäßigen rTorrent-Speicherort zu verwenden",
"ManageClients": "Verwalte Clienten"
"ManageClients": "Verwalte Clienten",
"BlackholeFolderHelpText": "Ordner, in dem {appName} die Datei {extension} speichert"
}

View File

@@ -515,7 +515,7 @@
"EditConnectionImplementation": "Προσθήκη",
"EditApplicationImplementation": "Προσθήκη",
"AddApplicationImplementation": "Προσθήκη",
"AddConnectionImplementation": "Προσθήκη",
"AddConnectionImplementation": "Προσθήκη - {implementationName}",
"AddIndexerImplementation": "Προσθήκη",
"EditIndexerProxyImplementation": "Προσθήκη",
"CountApplicationsSelected": "Επιλέχθηκαν {0} συλλογές"

View File

@@ -134,6 +134,7 @@
"CountIndexersAvailable": "{count} indexer(s) available",
"CountIndexersSelected": "{count} indexer(s) selected",
"Custom": "Custom",
"CustomFilter": "Custom Filter",
"CustomFilters": "Custom Filters",
"Database": "Database",
"DatabaseMigration": "Database Migration",
@@ -174,6 +175,7 @@
"DisabledUntil": "Disabled Until",
"Discord": "Discord",
"Docker": "Docker",
"Donate": "Donate",
"Donations": "Donations",
"DownloadClient": "Download Client",
"DownloadClientAriaSettingsDirectoryHelpText": "Optional location to put downloads in, leave blank to use the default Aria2 location",
@@ -394,6 +396,8 @@
"IndexerSettingsPasskey": "Pass Key",
"IndexerSettingsQueryLimit": "Query Limit",
"IndexerSettingsQueryLimitHelpText": "The number of max queries as specified by the respective unit that {appName} will allow to the site",
"IndexerSettingsRejectBlocklistedTorrentHashes": "Reject Blocklisted Torrent Hashes While Grabbing",
"IndexerSettingsRejectBlocklistedTorrentHashesHelpText": "If a torrent is blocked by hash it may not properly be rejected during RSS/Search for some indexers, enabling this will allow it to be rejected after the torrent is grabbed, but before it is sent to the client.",
"IndexerSettingsRssKey": "RSS Key",
"IndexerSettingsSeedRatio": "Seed Ratio",
"IndexerSettingsSeedRatioHelpText": "The ratio a torrent should reach before stopping, empty uses the download client's default. Ratio should be at least 1.0 and follow the indexers rules",
@@ -420,6 +424,7 @@
"InvalidUILanguage": "Your UI is set to an invalid language, correct it and save your settings",
"KeyboardShortcuts": "Keyboard Shortcuts",
"Label": "Label",
"LabelIsRequired": "Label is required",
"Language": "Language",
"LastDuration": "Last Duration",
"LastExecution": "Last Execution",
@@ -443,9 +448,11 @@
"MappedDrivesRunningAsService": "Mapped network drives are not available when running as a Windows Service. Please see the FAQ for more information",
"MassEditor": "Mass Editor",
"Mechanism": "Mechanism",
"Menu": "Menu",
"Message": "Message",
"MinimumSeeders": "Minimum Seeders",
"MinimumSeedersHelpText": "Minimum seeders required by the Application for the indexer to grab",
"Mixed": "Mixed",
"Mode": "Mode",
"More": "More",
"MoreInfo": "More Info",
@@ -517,6 +524,8 @@
"Privacy": "Privacy",
"Private": "Private",
"Protocol": "Protocol",
"ProwlarrDownloadClientsAlert": "If you intend to do searches directly within {appName}, you need to add Download Clients. Otherwise, you do not need to add them here. For searches from your Apps, the download clients configured there are used instead.",
"ProwlarrDownloadClientsInAppOnlyAlert": "Download clients are for {appName} in-app searches only and do not sync to apps. There are no plans to add any such functionality.",
"ProwlarrSupportsAnyDownloadClient": "{appName} supports any of the download clients listed below.",
"ProwlarrSupportsAnyIndexer": "{appName} supports many indexers in addition to any indexer that uses the Newznab/Torznab standard using 'Generic Newznab' (for usenet) or 'Generic Torznab' (for torrents). Search & Select your indexer from below.",
"Proxies": "Proxies",

View File

@@ -22,7 +22,7 @@
"Clear": "Borrar",
"BackupNow": "Hacer copia de seguridad ahora",
"Backup": "Copia de seguridad",
"AppDataLocationHealthCheckMessage": "No será posible actualizar para prevenir la eliminación de AppData al Actualizar",
"AppDataLocationHealthCheckMessage": "No será posible actualizar para evitar la eliminación de AppData al actualizar",
"Analytics": "Analíticas",
"All": "Todo",
"About": "Acerca de",
@@ -31,7 +31,7 @@
"UpdateCheckUINotWritableMessage": "No se puede instalar la actualización porque la carpeta UI '{0}' no tiene permisos de escritura para el usuario '{1}'.",
"UpdateCheckStartupTranslocationMessage": "No se puede instalar la actualización porque la carpeta de arranque '{0}' está en una carpeta de \"App Translocation\".",
"UpdateCheckStartupNotWritableMessage": "No se puede instalar la actualización porque la carpeta de arranque '{0}' no tiene permisos de escritura para el usuario '{1}'.",
"UnselectAll": "Deseleccionar Todo",
"UnselectAll": "Desmarcar todo",
"UI": "UI",
"Tasks": "Tareas",
"Tags": "Etiquetas",
@@ -39,15 +39,15 @@
"Style": "Estilo",
"Status": "Estado",
"Sort": "Ordenar",
"ShowAdvanced": "Mostrar Avanzado",
"ShowAdvanced": "Mostrar avanzado",
"Settings": "Ajustes",
"SetTags": "Poner Etiquetas",
"SelectAll": "Seleccionar Todas",
"SetTags": "Establecer etiquetas",
"SelectAll": "Seleccionar todo",
"Security": "Seguridad",
"Search": "Buscar",
"Scheduled": "Programado",
"SaveChanges": "Guardar Cambios",
"RestoreBackup": "Recuperar Backup",
"SaveChanges": "Guardar cambios",
"RestoreBackup": "Restaurar copia de seguridad",
"ReleaseBranchCheckOfficialBranchMessage": "Las versión {0} no es una versión válida de {appName}, no recibirás actualizaciones",
"Refresh": "Actualizar",
"Queue": "Cola",
@@ -56,9 +56,9 @@
"ProxyCheckBadRequestMessage": "Fallo al comprobar el proxy. Status code: {0}",
"Proxy": "Proxy",
"Options": "Opciones",
"NoChange": "Sin Cambio",
"NoChanges": "Sin Cambios",
"MoreInfo": "Más Información",
"NoChange": "Sin cambio",
"NoChanges": "Sin cambios",
"MoreInfo": "Más información",
"Logging": "Registro de eventos",
"LogFiles": "Archivos de Registro",
"Language": "Idioma",
@@ -66,7 +66,7 @@
"Added": "Añadido",
"Actions": "Acciones",
"UISettingsSummary": "Fecha, idioma, y opciones de color deteriorado",
"TagsSettingsSummary": "Ver todas las etiquetas y cómo se usan. Las etiquetas no utilizadas se pueden eliminar",
"TagsSettingsSummary": "Vea todas las etiquetas y cómo se usan. Las etiquetas sin usar pueden ser eliminadas",
"Size": "Tamaño",
"ReleaseStatus": "Estado del Estreno",
"Protocol": "Protocolo",
@@ -86,10 +86,10 @@
"Type": "Tipo",
"Title": "Título",
"Time": "Tiempo",
"TestAll": "Testear Todo",
"Test": "Test",
"TestAll": "Probar todo",
"Test": "Prueba",
"TableOptionsColumnsMessage": "Escoger qué columnas son visibles y en que orden aparecerán",
"TableOptions": "Opciones de Tabla",
"TableOptions": "Opciones de tabla",
"Source": "Fuente",
"Shutdown": "Apagar",
"Seeders": "Semillas",
@@ -115,16 +115,16 @@
"Apply": "Aplicar",
"Age": "Antigüedad",
"SystemTimeCheckMessage": "El reloj del sistema está retrasado más de un día. Las tareas de mantenimiento no se ejecutarán correctamente hasta que se haya corregido",
"UnsavedChanges": "Cambios no guardados",
"ShowSearchHelpText": "Mostrar botón de búsqueda al pasar el cursor por encima",
"ShowSearch": "Mostrar Búsqueda",
"UnsavedChanges": "Cambios sin guardar",
"ShowSearchHelpText": "Muestra el botón de búsqueda al pasar por encima",
"ShowSearch": "Mostrar búsqueda",
"SettingsTimeFormat": "Formato de Hora",
"SettingsShowRelativeDatesHelpText": "Mostrar fechas relativas (Hoy/Ayer/etc) o absolutas",
"SettingsShowRelativeDates": "Mostrar Fechas Relativas",
"SettingsShortDateFormat": "Formato Corto de Fecha",
"SettingsLongDateFormat": "Formato Largo de Fecha",
"PendingChangesStayReview": "Permanecer y revisar cambios",
"PendingChangesMessage": "Hay cambios sin salvar, estás seguro de que quieres salir de esta página?",
"PendingChangesStayReview": "Quedarse y revisar cambios",
"PendingChangesMessage": "Tienes cambios sin guardar. ¿Estás seguro que quieres salir de esta página?",
"PendingChangesDiscardChanges": "Descartar cambios y salir",
"SettingsEnableColorImpairedModeHelpText": "Estilo modificado para permitir que usuarios con problemas de color distingan mejor la información codificada por colores",
"SettingsEnableColorImpairedMode": "Activar Modo De Color Degradado",
@@ -160,51 +160,51 @@
"AnalyticsEnabledHelpText": "Envíe información anónima de uso y error a los servidores de {appName}. Esto incluye información sobre su navegador, qué páginas de {appName} WebUI utiliza, informes de errores, así como el sistema operativo y la versión en tiempo de ejecución. Usaremos esta información para priorizar funciones y correcciones de errores.",
"YesCancel": "Sí, Cancelar",
"Version": "Versión",
"Username": "Nombre de usuario",
"UseProxy": "Usar el Proxy",
"Username": "Usuario",
"UseProxy": "Usar proxy",
"Usenet": "Usenet",
"UrlBaseHelpText": "Para soporte de reverse proxy, vacio por defecto",
"UrlBaseHelpText": "Para soporte de proxy inverso, por defecto está vacío",
"URLBase": "URL Base",
"Uptime": "Tiempo de actividad",
"UpdateScriptPathHelpText": "Ruta del script propio que toma el paquete de actualización y se encarga del proceso de actualización restante",
"UpdateScriptPathHelpText": "Ruta a un script personalizado que toma un paquete de actualización extraído y gestiona el resto del proceso de actualización",
"UpdateMechanismHelpText": "Usar el actualizador incorporado de {appName} o un script",
"UpdateAutomaticallyHelpText": "Descargar e instalar actualizaciones automáticamente. Se podrán instalar desde Sistema: Actualizaciones también",
"UpdateAutomaticallyHelpText": "Descargar e instalar actualizaciones automáticamente. Todavía puedes instalar desde Sistema: Actualizaciones",
"UnableToLoadTags": "No se pueden cargar las Etiquetas",
"UnableToLoadNotifications": "No se pueden cargar las Notificaciones",
"DownloadClientsLoadError": "No se puden cargar los gestores de descargas",
"UISettings": "Ajustes del UI",
"Torrents": "Torrents",
"TestAllClients": "Comprobar Todos los Gestores",
"TestAllClients": "Probar todos los clientes",
"TagsHelpText": "Se aplica a películas con al menos una etiqueta coincidente",
"SuggestTranslationChange": "Sugerir un cambio en la traducción",
"StartupDirectory": "Directorio de Arranque",
"SSLPort": "Puerto SSL",
"SSLCertPath": "Ruta del Certificado SSL",
"SSLCertPassword": "Contraseña del Certificado SSL",
"SendAnonymousUsageData": "Enviar Datos de Uso Anónimamente",
"ScriptPath": "Ruta del Script",
"SendAnonymousUsageData": "Enviar datos de uso anónimos",
"ScriptPath": "Ruta del script",
"Retention": "Retención",
"Result": "Resultado",
"RestartRequiredHelpTextWarning": "Requiere reiniciar para que surta efecto",
"RestartRequiredHelpTextWarning": "Requiere reiniciar para que tenga efecto",
"RestartProwlarr": "Reiniciar {appName}",
"RestartNow": "Reiniciar Ahora",
"ResetAPIKey": "Reajustar API",
"RestartNow": "Reiniciar ahora",
"ResetAPIKey": "Restablecer clave API",
"Reset": "Reiniciar",
"RemoveFilter": "Eliminar filtro",
"RemovedFromTaskQueue": "Eliminar de la cola de tareas",
"RefreshMovie": "Actualizar película",
"ReadTheWikiForMoreInformation": "Lee la Wiki para más información",
"ProxyUsernameHelpText": "Tienes que introducir tu nombre de usuario y contraseña sólo si son requeridos. Si no, déjalos vacios.",
"ProxyType": "Tipo de Proxy",
"ProxyPasswordHelpText": "Tienes que introducir tu nombre de usuario y contraseña sólo si son requeridos. Si no, déjalos vacios.",
"ProxyBypassFilterHelpText": "Usa ',' como separador, y '*.' como wildcard para subdominios",
"PortNumber": "Número de Puerto",
"ProxyUsernameHelpText": "Solo necesitas introducir un usuario y contraseña si se requiere alguno. De otra forma déjalos en blanco.",
"ProxyType": "Tipo de proxy",
"ProxyPasswordHelpText": "Solo necesitas introducir un usuario y contraseña si se requiere alguno. De otra forma déjalos en blanco.",
"ProxyBypassFilterHelpText": "Usa ',' como separador, y '*.' como comodín para subdominios",
"PortNumber": "Número de puerto",
"Port": "Puerto",
"Password": "Contraseña",
"PageSizeHelpText": "Número de elementos por página",
"PackageVersion": "Versión del paquete",
"NotificationTriggers": "Desencadenantes de Notificaciones",
"NoLeaveIt": "No, Déjalo",
"NotificationTriggers": "Disparadores de notificación",
"NoLeaveIt": "No, déjalo",
"New": "Nuevo",
"NetCore": ".NET Core",
"Mode": "Modo",
@@ -214,7 +214,7 @@
"LogLevel": "Nivel de Registro",
"LaunchBrowserHelpText": " Abrir un navegador web e ir a la página de inicio de {appName} al arrancar la app.",
"Interval": "Intervalo",
"IndexerFlags": "Marcas de Indexer",
"IndexerFlags": "Banderas del indexador",
"IncludeHealthWarningsHelpText": "Incluir Alertas de Salud",
"IllRestartLater": "Lo reiniciaré más tarde",
"IgnoredAddresses": "Ignorar direcciones",
@@ -224,12 +224,12 @@
"EnableAutomaticSearch": "Habilitar Búsqueda Automática",
"ConnectSettings": "Conectar Ajustes",
"BindAddress": "Dirección de Ligado",
"OpenBrowserOnStart": "Abrir navegador al arrancar",
"OpenBrowserOnStart": "Abrir navegador al inicio",
"OnHealthIssueHelpText": "En Problema de Salud",
"TagCannotBeDeletedWhileInUse": "No se puede eliminar estando en uso",
"SSLCertPathHelpText": "Ruta al archivo pfx",
"SSLCertPasswordHelpText": "Contraseña para el archivo pfx",
"RssIsNotSupportedWithThisIndexer": "RSS no son soportadas por este indexer",
"RssIsNotSupportedWithThisIndexer": "RSS no está soportado con este indexador",
"RemovingTag": "Eliminando etiqueta",
"Manual": "Manual",
"LogLevelTraceHelpTextWarning": "El registro de seguimiento sólo debe activarse temporalmente",
@@ -248,7 +248,7 @@
"UnableToLoadUISettings": "No se han podido cargar los ajustes de UI",
"UnableToLoadHistory": "No se ha podido cargar la historia",
"UnableToLoadGeneralSettings": "No se han podido cargar los ajustes Generales",
"UnableToLoadBackups": "No se han podido cargar las copias de seguridad",
"UnableToLoadBackups": "No se pudo cargar las copias de seguridad",
"UnableToAddANewNotificationPleaseTryAgain": "No se ha podido añadir una nueva notificación, prueba otra vez.",
"UnableToAddANewIndexerPleaseTryAgain": "No se ha podido añadir un nuevo indexer, prueba otra vez.",
"UnableToAddANewDownloadClientPleaseTryAgain": "No se ha podido añadir un nuevo gestor de descargas, prueba otra vez.",
@@ -258,12 +258,12 @@
"ProwlarrSupportsAnyIndexer": "{appName} soporta cualquier indexer que utilice el estandar Newznab, como también cualquiera de los indexers listados debajo.",
"ProwlarrSupportsAnyDownloadClient": "{appName} soporta cualquier gestor de descargas indicado debajo.",
"NoUpdatesAreAvailable": "No hay actualizaciones disponibles",
"NoTagsHaveBeenAddedYet": "No se han añadido etiquetas todavía",
"NoLogFiles": "Sin archivos de registro",
"NoTagsHaveBeenAddedYet": "Ninguna etiqueta ha sido añadida aún",
"NoLogFiles": "No hay archivos de registro",
"NoBackupsAreAvailable": "No hay copias de seguridad disponibles",
"MaintenanceRelease": "Lanzamiento de mantenimiento: Corrección de errores y otras mejoras. Ver historial de commits de Github para mas detalle",
"ForMoreInformationOnTheIndividualDownloadClients": "Para más información individual de los gestores de descarga, haz clic en lls botones de información.",
"FilterPlaceHolder": "Buscar películas",
"FilterPlaceHolder": "Buscar Indexadores",
"Exception": "Excepción",
"ErrorLoadingContents": "Error cargando contenidos",
"UILanguageHelpTextWarning": "Recargar el Navegador",
@@ -293,13 +293,13 @@
"Add": "Añadir",
"Custom": "Personalizado",
"Donations": "Donaciones",
"SearchIndexers": "Buscar películas",
"SearchIndexers": "Buscar Indexadores",
"Enabled": "Habilitado",
"Grabs": "Capturar",
"Presets": "Preajustes",
"Rss": "RSS",
"Today": "Hoy",
"Tomorrow": "mañana",
"Tomorrow": "Mañana",
"Torrent": "Torrents",
"UnableToAddANewIndexerProxyPleaseTryAgain": "No se ha podido añadir un nuevo indexer, prueba otra vez.",
"Wiki": "Wiki",
@@ -321,10 +321,10 @@
"HistoryCleanupDaysHelpText": "Ajustar a 0 para desactivar la limpieza automática",
"HistoryCleanupDaysHelpTextWarning": "Los archivos en la papelera de reciclaje más antiguos que el número de días seleccionado serán limpiados automáticamente",
"OnGrab": "Al Capturar lanzamiento",
"OnHealthIssue": "En Problema de Salud",
"TestAllIndexers": "Comprobar Todos los Indexers",
"NotificationTriggersHelpText": "Seleccione qué eventos deben activar esta notificación",
"OnApplicationUpdate": "Al Actualizar La Aplicación",
"OnHealthIssue": "Al haber un problema de salud",
"TestAllIndexers": "Probar todos los indexadores",
"NotificationTriggersHelpText": "Selecciona qué eventos deberían disparar esta notificación",
"OnApplicationUpdate": "Al actualizar la aplicación",
"OnApplicationUpdateHelpText": "Al Actualizar La Aplicación",
"AddRemoveOnly": "Sólo añadir y eliminar",
"AddedToDownloadClient": "Descarga añadida al cliente",
@@ -378,7 +378,7 @@
"DeleteSelectedIndexersMessageText": "¿Está seguro de querer eliminar {count} indexador(es) seleccionado(s)?",
"DeleteSelectedDownloadClientsMessageText": "¿Está seguro de querer eliminar {count} cliente(s) de descarga seleccionado(s)?",
"ApplyTagsHelpTextHowToApplyApplications": "Cómo aplicar etiquetas a las aplicaciones seleccionadas",
"SelectIndexers": "Buscar películas",
"SelectIndexers": "Seleccionar Indexadores",
"ApplyTagsHelpTextHowToApplyIndexers": "Cómo aplicar etiquetas a los indexadores seleccionados",
"ApplyTagsHelpTextRemove": "Eliminar: Elimina las etiquetas introducidas",
"ApplyTagsHelpTextReplace": "Reemplazar: Sustituye las etiquetas por las introducidas (introduce \"no tags\" para borrar todas las etiquetas)",
@@ -411,7 +411,7 @@
"Artist": "Artista",
"DeleteAppProfileMessageText": "¿Estás seguro de que quieres eliminar el perfil de la aplicación '{name}'?",
"AddConnection": "Añadir Conexión",
"NotificationStatusAllClientHealthCheckMessage": "Las listas no están disponibles debido a errores",
"NotificationStatusAllClientHealthCheckMessage": "Las notificaciones no están disponibles debido a fallos",
"NotificationStatusSingleClientHealthCheckMessage": "Listas no disponibles debido a errores: {0}",
"EditIndexerImplementation": "Editar Indexador - {implementationName}",
"AuthBasic": "Básico (ventana emergente del navegador)",
@@ -421,8 +421,8 @@
"Clone": "Clonar",
"DisabledForLocalAddresses": "Deshabilitado para Direcciones Locales",
"External": "Externo",
"None": "Ninguna",
"ResetAPIKeyMessageText": "¿Está seguro de que desea restablecer su clave API?",
"None": "Ninguno",
"ResetAPIKeyMessageText": "¿Estás seguro que quieres restablecer tu clave API?",
"EditIndexerProxyImplementation": "Editar Proxy de Indexador - { implementationName}",
"AppUpdated": "{appName} Actualizado",
"AppUpdatedVersion": "{appName} ha sido actualizado a la versión `{version}`, para obtener los cambios más recientes, necesitará recargar {appName}",
@@ -549,7 +549,7 @@
"QueryOptions": "Opciones de Consulta",
"NewznabUrl": "Url Newznab",
"QueryType": "Tipo de Consulta",
"SearchCountIndexers": "Buscar {count} indexador(es)",
"SearchCountIndexers": "Buscar en {count} indexador(es)",
"SelectedCountOfCountReleases": "Seleccionados {selectedCount} de {itemCount} lanzamientos",
"SyncLevel": "Nivel de Sincronización",
"StopSelecting": "Detener la Selección",
@@ -612,5 +612,14 @@
"NotificationsEmailSettingsUseEncryptionHelpText": "Si prefiere utilizar el cifrado si está configurado en el servidor, utilizar siempre el cifrado mediante SSL (sólo puerto 465) o StartTLS (cualquier otro puerto) o no utilizar nunca el cifrado",
"IndexerHDBitsSettingsPasskeyHelpText": "Clave de acceso desde los Detalles de Usuario",
"IndexerSettingsPasskey": "Clave de Acceso",
"BlackholeFolderHelpText": "La carpeta en donde {appName} se almacenaran los {extension} file"
"BlackholeFolderHelpText": "La carpeta en donde {appName} se almacenaran los {extension} file",
"CustomFilter": "Filtros personalizados",
"LabelIsRequired": "Se requiere etiqueta",
"TorrentBlackholeSaveMagnetFiles": "Guardar archivos magnet",
"TorrentBlackholeTorrentFolder": "Carpeta de torrent",
"UseSsl": "Usar SSL",
"UsenetBlackholeNzbFolder": "Carpeta Nzb",
"TorrentBlackholeSaveMagnetFilesHelpText": "Guarda el enlace magnet si no hay ningún archivo .torrent disponible (útil solo si el cliente de descarga soporta magnets guardados en un archivo)",
"SecretToken": "Token secreto",
"TorrentBlackholeSaveMagnetFilesExtension": "Guardar extensión de archivos magnet"
}

View File

@@ -45,7 +45,7 @@
"Refresh": "Päivitä",
"RefreshMovie": "Päivitä elokuva",
"ReleaseBranchCheckOfficialBranchMessage": "\"{0}\" ei ole kelvollinen {appName}-julkaisuhaara ja tämän vuoksi et saa päivityksiä.",
"RestartRequiredHelpTextWarning": "Käyttöönotto vaatii {appName}in uudelleenkäynnistyksen.",
"RestartRequiredHelpTextWarning": "Käyttöönotto vaatii in uudelleenkäynnistyksen.",
"Result": "Tulos",
"Settings": "Asetukset",
"SettingsLongDateFormat": "Pitkän päiväyksen esitys",
@@ -56,7 +56,6 @@
"UpdateMechanismHelpText": "Käytä {appName}in sisäänrakennettua päivitystoimintoa tai komentosarjaa.",
"Enable": "Käytä",
"UI": "Käyttöliittymä",
"UrlBaseHelpText": "Lisää {appName}in URL-osoitteeseen jälkiliitteen, esim. \"http://[osoite]:[portti]/[URL-perusta]\". Oletusarvo on tyhjä.",
"Usenet": "Usenet",
"BackupNow": "Varmuuskopioi nyt",
"NoBackupsAreAvailable": "Varmuuskopioita ei ole käytettävissä",
@@ -71,7 +70,7 @@
"NoTagsHaveBeenAddedYet": "Tunnisteita ei ole vielä lisätty.",
"ApplyTags": "Tunnistetoimenpide",
"Authentication": "Tunnistautuminen",
"AuthenticationMethodHelpText": "Vaadi {appName}in käyttöön käyttäjätunnus ja salasana.",
"AuthenticationMethodHelpText": "Vaadi {appName}in käyttöön käyttäjätunnus ja salasana",
"BindAddressHelpText": "Toimiva IP-osoite, localhost tai * (tähti) kaikille verkkoliitännöille.",
"Close": "Sulje",
"DeleteNotification": "Poista ilmoitus",
@@ -168,7 +167,7 @@
"Size": "Koko",
"Sort": "Järjestys",
"UnableToAddANewDownloadClientPleaseTryAgain": "Uuden lataustyökalun lisäys epäonnistui. Yitä uudelleen.",
"AppDataLocationHealthCheckMessage": "Päivityksiä ei sallita, jotta AppData-kansion poistaminen päivityksen yhteydessä voidaan estää.",
"AppDataLocationHealthCheckMessage": "Päivityksiä ei sallita, jotta AppData-kansion poistaminen päivityksen yhteydessä voidaan estää",
"UnableToLoadHistory": "Historian lataus epäonnistui.",
"UnableToLoadNotifications": "Virhe ladattaessa kytköksiä",
"UnableToLoadTags": "Tunnisteiden lataus ei onnistu",
@@ -227,7 +226,7 @@
"BeforeUpdate": "Ennen päivitystä",
"BindAddress": "Sidososoite",
"Branch": "Haara",
"BranchUpdate": "{appName}in versiopäivityksiin käytettävä kehityshaara.",
"BranchUpdate": "{appName}in versiopäivityksiin käytettävä kehityshaara",
"BranchUpdateMechanism": "Ulkoisen päivitysratkaisun käyttämä kehityshaara.",
"BypassProxyForLocalAddresses": "Ohjaa paikalliset osoitteet välityspalvelimen ohi",
"Cancel": "Peruuta",

View File

@@ -25,7 +25,7 @@
"AppDataLocationHealthCheckMessage": "La mise à jour ne sera pas possible afin empêcher la suppression de AppData lors de la mise à jour",
"Analytics": "Statistiques",
"All": "Tout",
"About": "À propos",
"About": "Tagalog",
"IndexerStatusCheckSingleClientMessage": "Indexeurs indisponibles en raison d'échecs : {0}",
"DownloadClientStatusCheckSingleClientMessage": "Clients de Téléchargement indisponibles en raison d'échecs: {0}",
"SetTags": "Définir des étiquettes",
@@ -146,7 +146,7 @@
"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 {appName}. Cela inclut des informations sur votre navigateur, quelle page {appName} 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.",
"AnalyticsEnabledHelpText": "Envoyer des informations anonymes sur l'utilisation et les erreurs aux serveurs de {appName}. Cela inclut des informations sur votre navigateur, les pages de l'interface Web de {appName} que vous utilisez, les rapports d'erreurs ainsi que le système d'exploitation et la version d'exécution. Nous utiliserons ces informations pour prioriser les fonctionnalités et les corrections de bugs.",
"IgnoredAddresses": "Adresses ignorées",
"Hostname": "Nom d'hôte",
"GeneralSettings": "Réglages généraux",
@@ -162,13 +162,13 @@
"DeleteNotification": "Supprimer la notification",
"DeleteDownloadClient": "Supprimer le client de téléchargement",
"DeleteBackup": "Supprimer la sauvegarde",
"DatabaseMigration": "Migration de la base de données",
"DatabaseMigration": "Migration des bases de données",
"ConnectSettings": "Paramètres de connexion",
"BackupFolderHelpText": "Les chemins correspondants seront sous le répertoire AppData de {appName}",
"BackupFolderHelpText": "Les chemins d'accès relatifs se trouvent dans le répertoire AppData de {appName}",
"IllRestartLater": "Je redémarrerai plus tard",
"CancelPendingTask": "Êtes-vous sur de vouloir annuler cette tâche en attente ?",
"BranchUpdateMechanism": "Branche utilisée par le mécanisme de mise à jour extérieur",
"BranchUpdate": "Branche à utiliser pour mettre {appName} à jour",
"BranchUpdate": "Branche à utiliser pour mettre à jour {appName}",
"BeforeUpdate": "Avant la mise à jour",
"DeleteDownloadClientMessageText": "Voulez-vous supprimer le client de téléchargement « {name} » ?",
"DeleteBackupMessageText": "Voulez-vous supprimer la sauvegarde « {name} » ?",
@@ -222,7 +222,7 @@
"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",
"IndexerFlags": "Drapeaux de l'indexeur",
"DeleteTagMessageText": "Voulez-vous vraiment supprimer l'étiquette '{label}' ?",
"UISettings": "Paramètres UI",
"UILanguageHelpTextWarning": "Rechargement du navigateur requis",
@@ -302,7 +302,7 @@
"SettingsFilterSentryEvents": "Filtrer les événements d'analyse",
"SettingsConsoleLogLevel": "Niveau de journalisation de la console",
"SearchIndexers": "Recherche indexeurs",
"IndexerRss": "Indexeur Rss",
"IndexerRss": "Indexeur RSS",
"IndexerQuery": "Requête indexeur",
"IndexerObsoleteCheckMessage": "Les indexeurs sont obsolètes ou ont été mis à jour : {0}. Veuillez supprimer et (ou) rajouter à {appName}",
"IndexerHealthCheckNoIndexers": "Aucun indexeur activé, {appName} ne renverra pas de résultats de recherche",
@@ -345,7 +345,7 @@
"Donations": "Dons",
"Enabled": "Activé",
"Grabs": "Complétés",
"Id": "Id",
"Id": "ID",
"Presets": "Préconfigurations",
"Privacy": "Visibilité",
"Query": "Requête",
@@ -482,7 +482,7 @@
"Author": "Auteur",
"AverageResponseTimesMs": "Temps de réponse moyen des indexeurs (ms)",
"IndexerFailureRate": "Taux d'échec de l'indexeur",
"Label": "Libellé",
"Label": "Étiquette",
"More": "Plus",
"Publisher": "Éditeur",
"Season": "Saison",
@@ -593,9 +593,9 @@
"ActiveIndexers": "Indexeurs actifs",
"ActiveApps": "Applications actives",
"AuthenticationRequiredUsernameHelpTextWarning": "Saisir un nouveau nom d'utilisateur",
"Clone": "Cloner",
"Clone": "Dupliquer",
"PackSeedTime": "Temps de Seed",
"ApplicationTagsHelpText": "Synchroniser les indexeurs avec cette application qui n'ont aucune étiquette ou qui ont une ou plusieurs étiquettes correspondantes",
"ApplicationTagsHelpText": "Synchroniser les indexeurs avec cette application qui n'ont aucune étiquette ou qui ont une ou plusieurs étiquettes correspondantes. Si aucune étiquette n'est listée ici, aucun indexeur ne sera empêché de se synchroniser en raison de ses étiquettes.",
"OnHealthRestored": "Sur la santé restaurée",
"OnHealthRestoredHelpText": "Sur la santé restaurée",
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Confirmer le nouveau mot de passe",
@@ -605,5 +605,139 @@
"DownloadClientQbittorrentSettingsContentLayout": "Disposition du contenu",
"DownloadClientQbittorrentSettingsContentLayoutHelpText": "Utiliser la disposition du contenu configurée par qBittorrent, la disposition originale du torrent ou toujours créer un sous-dossier (qBittorrent 4.3.2+)",
"DownloadClientAriaSettingsDirectoryHelpText": "Emplacement facultatif pour les téléchargements, laisser vide pour utiliser l'emplacement par défaut Aria2",
"ManageClients": "Gérer les clients"
"ManageClients": "Gérer les clients",
"DownloadClientDelugeSettingsUrlBaseHelpText": "Ajoute un préfixe à l'URL json du déluge, voir {url}",
"Destination": "Cible",
"Directory": "Dossier",
"DownloadClientDownloadStationSettingsDirectoryHelpText": "Dossier partagé dans lequel placer les téléchargements (facultatif), laissez vide pour utiliser l'emplacement par défaut de Download Station",
"CustomFilter": "Filtre personnalisé",
"NotificationsEmailSettingsUseEncryption": "Utiliser le cryptage",
"NotificationsEmailSettingsUseEncryptionHelpText": "Préférer utiliser le cryptage s'il est configuré sur le serveur, toujours utiliser le cryptage via SSL (port 465 uniquement) ou StartTLS (tout autre port) ou ne jamais utiliser le cryptage",
"IndexerHDBitsSettingsPasskeyHelpText": "Clé d'accès à partir des détails de l'utilisateur",
"NoApplicationsFound": "Aucune application trouvée",
"IndexerAlphaRatioSettingsExcludeSceneHelpText": "Exclure les communiqués de SCENE des résultats",
"IndexerAlphaRatioSettingsExcludeScene": "Exclure SCENE",
"IndexerBeyondHDSettingsLimitedOnly": "Limité seulement",
"IndexerBeyondHDSettingsRssKeyHelpText": "Clé RSS du site (trouvée dans My Security => Clé RSS)",
"IndexerFileListSettingsUsernameHelpText": "Nom d'utilisateur du site",
"IndexerFileListSettingsPasskeyHelpText": "Site Passkey (Il s'agit de la chaîne alphanumérique dans l'url du tracker affichée dans votre client de téléchargement)",
"IndexerGazelleGamesSettingsApiKeyHelpTextWarning": "Doit avoir les permissions Utilisateur et Torrents",
"IndexerGazelleGamesSettingsSearchGroupNamesHelpText": "Rechercher des publications par nom de groupe",
"IndexerHDBitsSettingsMediums": "Supports",
"IndexerHDBitsSettingsMediumsHelpText": "Si elle n'est pas spécifiée, toutes les options sont utilisées.",
"IndexerHDBitsSettingsUsernameHelpText": "Nom d'utilisateur du site",
"IndexerIPTorrentsSettingsCookieUserAgent": "Cookie User-Agent",
"IndexerIPTorrentsSettingsCookieUserAgentHelpText": "User-Agent associé au cookie utilisé par le navigateur",
"IndexerNewznabSettingsAdditionalParametersHelpText": "Paramètres supplémentaires de Newznab",
"IndexerNebulanceSettingsApiKeyHelpText": "Clé API à partir de Paramètres de l'utilisateur > Clés API. La clé doit avoir les permissions Liste et Téléchargement",
"IndexerNewznabSettingsApiKeyHelpText": "Clé API du site",
"IndexerNewznabSettingsVipExpirationHelpText": "Entrez la date (yyyy-mm-dd) pour l'expiration du VIP ou vide, {appName} notifiera 1 semaine après l'expiration du VIP",
"IndexerRedactedSettingsApiKeyHelpText": "Clé API du site (dans Paramètres => Paramètres d'accès)",
"IndexerPassThePopcornSettingsFreeleechOnlyHelpText": "Rechercher les publications freeleech uniquement",
"IndexerSettingsAdditionalParameters": "Paramètres supplémentaires",
"IndexerSettingsApiPathHelpText": "Chemin d'accès à l'api, généralement {url}",
"IndexerSettingsApiUser": "Utilisateur de l'API",
"IndexerSettingsAppsMinimumSeeders": "Apps avec le nombre minimum de seeders disponibles",
"IndexerSettingsFreeleechOnly": "Freeleech seulement",
"IndexerSettingsGrabLimit": "Limite de saisie",
"IndexerSettingsQueryLimit": "Limite de requête",
"IndexerSettingsQueryLimitHelpText": "Le nombre de requêtes maximales tel que spécifié par l'unité respective que {appName} autorisera au site",
"IndexerSettingsRssKey": "Clé RSS",
"IndexerSettingsSeedRatioHelpText": "Le ratio qu'un torrent doit atteindre avant de s'arrêter, vide utilise la valeur par défaut du client de téléchargement. Le ratio doit être d'au moins 1.0 et suivre les règles des indexeurs",
"IndexerSettingsSeedTime": "Temps d'envoie",
"IndexerTorrentSyndikatSettingsApiKeyHelpText": "Clé API du site",
"BlackholeFolderHelpText": "Dossier dans lequel {appName} stockera le fichier {extension}",
"DefaultCategory": "Catégorie par défaut",
"DownloadClientFloodSettingsAdditionalTags": "Étiquette supplémentaire",
"DownloadClientFloodSettingsAdditionalTagsHelpText": "Ajoute les propriétés des médias sous forme d'étiquette. Les conseils sont des exemples.",
"DownloadClientFreeboxSettingsApiUrl": "URL DE L'API",
"DownloadClientFreeboxSettingsApiUrlHelpText": "Définir l'URL de base de l'API Freebox avec la version de l'API, par exemple '{url}', par défaut '{defaultApiUrl}'",
"DownloadClientFloodSettingsTagsHelpText": "Étiquettes initiales d'un téléchargement. Pour être reconnu, un téléchargement doit avoir toutes les étiquettes initiales. Cela permet d'éviter les conflits avec des téléchargements non apparentés.",
"DownloadClientFloodSettingsUrlBaseHelpText": "Ajoute d'un préfixe à l'API Flood, tel que {url}",
"DownloadClientFreeboxSettingsPortHelpText": "Port utilisé pour accéder à l'interface de la Freebox, la valeur par défaut est '{port}'",
"DownloadClientNzbgetSettingsAddPausedHelpText": "Cette option nécessite au moins la version 16.0 de NzbGet",
"DownloadClientPneumaticSettingsNzbFolder": "Dossier Nzb",
"DownloadClientFreeboxSettingsAppTokenHelpText": "Le jeton de l'application récupéré lors de la création de l'accès à l'API Freebox (c'est-à-dire 'app_token')",
"DownloadClientFreeboxSettingsHostHelpText": "Nom d'hôte ou adresse IP de la Freebox, par défaut '{url}' (ne fonctionnera que si elle est sur le même réseau)",
"DownloadClientSettingsInitialState": "État initial",
"DownloadClientSettingsPriorityItemHelpText": "Priorité à utiliser lors de la saisie des articles",
"DownloadClientSettingsUrlBaseHelpText": "Ajoute un préfixe à l'url {clientName}, tel que {url}",
"DownloadClientSettingsUseSslHelpText": "Utiliser une connexion sécurisée lors de la connexion à {clientName}",
"DownloadClientSettingsInitialStateHelpText": "État initial pour les torrents ajoutés à {clientName}",
"DownloadClientTransmissionSettingsDirectoryHelpText": "Emplacement facultatif pour les téléchargements, laisser vide pour utiliser l'emplacement de transmission par défaut",
"DownloadClientTransmissionSettingsUrlBaseHelpText": "Ajoute un préfixe à l'url rpc de {clientName}, par exemple {url}, la valeur par défaut étant '{defaultUrl}'",
"IndexerAlphaRatioSettingsFreeleechOnlyHelpText": "Rechercher les publications freeleech uniquement",
"IndexerBeyondHDSettingsApiKeyHelpText": "Clé API du site (dans My Security => Clé API)",
"IndexerBeyondHDSettingsRefundOnly": "Remboursement uniquement",
"IndexerBeyondHDSettingsRefundOnlyHelpText": "Recherche de remboursement seulement",
"IndexerBeyondHDSettingsRewindOnly": "Rembobiner seulement",
"IndexerBeyondHDSettingsRewindOnlyHelpText": "Recherche en arrière uniquement",
"IndexerBeyondHDSettingsFreeleechOnlyHelpText": "Rechercher les publications freeleech uniquement",
"IndexerBeyondHDSettingsLimitedOnlyHelpText": "Recherche de freeleech uniquement (UL limitée)",
"IndexerBeyondHDSettingsSearchTypesHelpText": "Sélectionnez les types de rejets qui vous intéressent. Si aucune option n'est sélectionnée, toutes les options sont utilisées.",
"IndexerFileListSettingsFreeleechOnlyHelpText": "Rechercher les publications freeleech uniquement",
"IndexerGazelleGamesSettingsApiKeyHelpText": "Clé API du site (dans Paramètres => Paramètres d'accès)",
"IndexerBeyondHDSettingsSearchTypes": "Types de recherche",
"IndexerHDBitsSettingsFreeleechOnlyHelpText": "Afficher uniquement les versions freeleech",
"IndexerHDBitsSettingsOrigins": "Origines",
"IndexerHDBitsSettingsCodecs": "Codecs",
"IndexerHDBitsSettingsCodecsHelpText": "Si elle n'est pas spécifiée, toutes les options sont utilisées.",
"IndexerHDBitsSettingsUseFilenamesHelpText": "Cochez cette option si vous souhaitez utiliser les noms de fichiers des torrents comme titres de publication",
"IndexerNzbIndexSettingsApiKeyHelpText": "Clé API du site",
"IndexerOrpheusSettingsApiKeyHelpText": "Clé API du site (dans Paramètres => Paramètres d'accès)",
"IndexerPassThePopcornSettingsApiKeyHelpText": "Clé API du site",
"IndexerPassThePopcornSettingsApiUserHelpText": "Ces paramètres se trouvent dans les paramètres de sécurité de PassThePopcorn (Modifier le profil > Sécurité).",
"IndexerSettingsGrabLimitHelpText": "Le nombre de prises maximales tel que spécifié par l'unité respective que {appName} autorisera sur le site",
"IndexerSettingsLimitsUnit": "Limites d'unité",
"IndexerSettingsCookie": "Cookie",
"IndexerSettingsCookieHelpText": "Cookie du site",
"IndexerSettingsSeedTimeHelpText": "Durée pendant laquelle un torrent doit être envoyé avant de s'arrêter, vide utilise la valeur par défaut du client de téléchargement",
"IndexerSettingsSeedRatio": "Ratio d'envoie",
"IndexerSettingsVipExpiration": "Expiration de la carte VIP",
"SecretToken": "Jeton secret",
"TorrentBlackholeSaveMagnetFiles": "Enregistrer les fichiers magnétiques",
"TorrentBlackholeSaveMagnetFilesExtension": "Sauvegarde des fichiers magnétiques Extension",
"UsenetBlackholeNzbFolder": "Dossier Nzb",
"XmlRpcPath": "Chemin d'accès XML RPC",
"IndexerSettingsPackSeedTimeIndexerHelpText": "Durée pendant laquelle un torrent de pack (saison ou discographie) doit être diffusé avant de s'arrêter, vide est la valeur par défaut de l'application",
"TorrentBlackholeSaveMagnetFilesHelpText": "Enregistrer le lien magnétique si aucun fichier .torrent n'est disponible (utile uniquement si le client de téléchargement prend en charge les liens magnétiques enregistrés dans un fichier)",
"IndexerId": "ID de l'indexeur",
"DownloadClientFreeboxSettingsAppId": "ID de l'application",
"DownloadClientFreeboxSettingsAppToken": "Jeton d'application",
"DownloadClientPneumaticSettingsNzbFolderHelpText": "Ce dossier devra être accessible depuis XBMC",
"DownloadClientPneumaticSettingsStrmFolder": "Dossier Strm",
"DownloadClientPneumaticSettingsStrmFolderHelpText": "Les fichiers .strm contenus dans ce dossier seront importés par drone",
"DownloadClientQbittorrentSettingsFirstAndLastFirst": "Premier et dernier premiers",
"DownloadClientQbittorrentSettingsInitialStateHelpText": "État initial des torrents ajoutés à qBittorrent. Notez que les torrents forcés ne respectent pas les restrictions relatives aux seeds",
"DownloadClientQbittorrentSettingsSequentialOrder": "Ordre séquentiel",
"DownloadClientQbittorrentSettingsSequentialOrderHelpText": "Téléchargement dans l'ordre séquentiel (qBittorrent 4.1.0+)",
"DownloadClientRTorrentSettingsAddStoppedHelpText": "L'activation ajoutera des torrents et des magnets à rTorrent dans un état d'arrêt. Cela peut endommager les fichiers magnétiques.",
"DownloadClientRTorrentSettingsUrlPath": "Chemin d'url",
"DownloadClientRTorrentSettingsUrlPathHelpText": "Chemin d'accès au point de terminaison XMLRPC, voir {url}. Il s'agit généralement de RPC2 ou de [chemin vers ruTorrent]{url2} lors de l'utilisation de ruTorrent.",
"DownloadClientRTorrentSettingsAddStopped": "Ajout arrêté",
"DownloadClientSettingsAddPaused": "Ajout en pause",
"DownloadClientSettingsDefaultCategoryHelpText": "Catégorie de secours par défaut si aucune catégorie mappée n'existe pour une version. L'ajout d'une catégorie spécifique à {appName} permet d'éviter les conflits avec des téléchargements sans rapport avec {appName}. L'utilisation d'une catégorie est facultative, mais fortement recommandée.",
"DownloadClientSettingsDestinationHelpText": "Spécifie manuellement la destination du téléchargement, laisser vide pour utiliser la destination par défaut",
"IndexerGazelleGamesSettingsSearchGroupNames": "Recherche de noms de groupes",
"IndexerHDBitsSettingsOriginsHelpText": "Si elle n'est pas spécifiée, toutes les options sont utilisées.",
"IndexerHDBitsSettingsUseFilenames": "Utiliser les noms de fichiers",
"IndexerIPTorrentsSettingsFreeleechOnlyHelpText": "Rechercher les publications freeleech uniquement",
"IndexerSettingsApiPath": "Chemin d'accès à l'API",
"IndexerSettingsAppsMinimumSeedersHelpText": "Nombre minimum de seeders requis par les applications pour que l'indexeur s'en saisisse, vide est la valeur par défaut du profil Sync",
"IndexerSettingsBaseUrl": "Url de base",
"IndexerSettingsBaseUrlHelpText": "Sélectionnez l'url de base que {appName} utilisera pour les requêtes vers le site",
"IndexerSettingsLimitsUnitHelpText": "L'unité de temps pour le comptage des limites par indexeur",
"IndexerSettingsPackSeedTime": "Temps de seed du pack",
"IndexerSettingsPasskey": "Clé de passage",
"LabelIsRequired": "L'étiquette est requise",
"DownloadClientFreeboxSettingsAppIdHelpText": "L'ID de l'application donné lors de la création de l'accès à l'API Freebox (c'est-à-dire 'app_id')",
"DownloadClientQbittorrentSettingsFirstAndLastFirstHelpText": "Télécharger d'abord le premier et le dernier morceau (qBittorrent 4.1.0+)",
"DownloadClientQbittorrentSettingsUseSslHelpText": "Utilisez une connexion sécurisée. Voir Options -> UI Web -> 'Utiliser HTTPS au lieu de HTTP' dans qBittorrent.",
"IndexerSettingsRejectBlocklistedTorrentHashesHelpText": "Si un torrent est bloqué par le hachage, il peut ne pas être correctement rejeté pendant le RSS/recherche pour certains indexeurs. L'activation de cette fonction permet de le rejeter après que le torrent a été saisi, mais avant qu'il ne soit envoyé au client.",
"TorrentBlackholeSaveMagnetFilesExtensionHelpText": "Extension à utiliser pour les liens magnétiques, la valeur par défaut est '.magnet'",
"TorrentBlackholeTorrentFolder": "Dossier Torrent",
"UseSsl": "Utiliser SSL",
"IndexerSettingsRejectBlocklistedTorrentHashes": "Rejeter les hachages de torrents bloqués lors de la saisie",
"DownloadClientRTorrentSettingsDirectoryHelpText": "Emplacement facultatif dans lequel placer les téléchargements. Laisser vide pour utiliser l'emplacement par défaut de rTorrent",
"DownloadClientSettingsDefaultCategorySubFolderHelpText": "Catégorie de secours par défaut si aucune catégorie mappée n'existe pour une version. L'ajout d'une catégorie spécifique à {appName} permet d'éviter les conflits avec des téléchargements sans rapport avec {appName}. L'utilisation d'une catégorie est facultative, mais fortement recommandée. Crée un sous-répertoire [catégorie] dans le répertoire de sortie."
}

View File

@@ -134,20 +134,20 @@
"SaveSettings": "Beállítások mentése",
"SaveChanges": "Változtatások mentése",
"Save": "Mentés",
"RssIsNotSupportedWithThisIndexer": "Az RSS nem támogatott ezzel az indexerrel",
"RssIsNotSupportedWithThisIndexer": "Ez az indexelő nem támogatja az RSS-t",
"Retention": "Visszatartás",
"Result": "Eredmény",
"RestoreBackup": "Biztonsági mentés visszaállítása",
"Restore": "Visszaállít",
"RestartRequiredHelpTextWarning": "Újraindítás szükséges a hatálybalépéshez",
"RestartRequiredHelpTextWarning": "Újraindítás szükséges az életbe lépéshez",
"RestartProwlarr": "{appName} Újraindítása",
"RestartNow": "Újraindítás Most",
"RestartNow": "Újraindítás most",
"Restart": "Újrakezd",
"ResetAPIKey": "API Kulcs visszaállítása",
"ResetAPIKey": "API Kulcs Visszaállítása",
"Reset": "Visszaállítás",
"RemovingTag": "Címke eltávolítása",
"RemoveFilter": "Szűrő törlése",
"RemovedFromTaskQueue": "Eltávolítva a feladatsorról",
"RemoveFilter": "Szűrő Eltávolítás",
"RemovedFromTaskQueue": "Eltávolítva a feladatsorból",
"Reload": "Újratölt",
"ReleaseStatus": "Kiadás státusza",
"ReleaseBranchCheckOfficialBranchMessage": "A(z) {0} nem érvényes {appName} frissítési ágazat, ezért nem kap frissítéseket",
@@ -157,21 +157,21 @@
"ProwlarrSupportsAnyIndexer": "A {appName} számos indexert támogat, minden olyan indexelő mellett, amely a Newznab / Torznab szabványt használja, valamint a 'Generic Newznab' (usenethez) vagy a 'Generic Torznab' (torrentekhez) használatával. Keresés és az alább felsorolt indexelők kiválasztása.",
"ProwlarrSupportsAnyDownloadClient": "A {appName} minden olyan letöltési klienst támogat, amely a Newznab szabványt használja, valamint az alább felsorolt letöltési klienseket.",
"Queue": "Várakozási sor",
"ProxyUsernameHelpText": "Csak akkor kell megadnod felhasználónevet és jelszót, ha szükséges. Egyébként hagyd üresen.",
"ProxyType": "Proxy Típusa",
"ProxyPasswordHelpText": "Csak akkor kell megadnod felhasználónevet és jelszót, ha szükséges. Egyébként hagyd üresen.",
"ProxyUsernameHelpText": "Csak akkor kell megadnia egy felhasználónevet és jelszót, ha szükséges. Ellenkező esetben hagyja üresen.",
"ProxyType": "Proxy típus",
"ProxyPasswordHelpText": "Csak akkor kell megadnia egy felhasználónevet és jelszót, ha szükséges. Ellenkező esetben hagyja üresen.",
"ProxyCheckResolveIpMessage": "Nem sikerült megoldani a konfigurált proxykiszolgáló IP-címét {0}",
"ProxyCheckFailedToTestMessage": "Proxy tesztelése sikertelen: {0}",
"ProxyCheckBadRequestMessage": "Proxy tesztelése sikertelen. Állapotkód: {0}",
"ProxyBypassFilterHelpText": "Használja elválasztóként a ',' és a '*' karaktereket, az aldomainek helyettesítőjeként",
"ProxyBypassFilterHelpText": "Használja a ',' jelet elválasztóként és a '*' jelet. helyettesítő karakterként az aldomainekhez",
"Proxy": "Proxy",
"Protocol": "Protokoll",
"Priority": "Prioritás",
"PortNumber": "Port száma",
"Port": "Port",
"PendingChangesStayReview": "Maradj, és tekintsd át a változásokat",
"PendingChangesMessage": "Nem mentett módosításaid vannak, biztosan el akarod hagyni ezt az oldalt?",
"PendingChangesDiscardChanges": "Változtatások törlése és kilépés",
"PendingChangesStayReview": "Maradjon és tekintse át a változtatásokat",
"PendingChangesMessage": "Vannak nem mentett módosításai. Biztosan elhagyja ezt az oldalt?",
"PendingChangesDiscardChanges": "Vesse el a változtatásokat, és lépjen ki",
"Peers": "Peerek",
"Password": "Jelszó",
"PageSizeHelpText": "Az egyes oldalakon megjelenítendő elemek száma",
@@ -501,7 +501,7 @@
"AuthForm": "Űrlapok (bejelentkezési oldal)",
"DisabledForLocalAddresses": "Helyi címeknél letiltva",
"None": "Egyik sem",
"ResetAPIKeyMessageText": "Biztos hogy vissza szeretnéd állítani az API-Kulcsod?",
"ResetAPIKeyMessageText": "Biztosan visszaállítja API-kulcsát?",
"AuthenticationRequiredPasswordHelpTextWarning": "Adjon meg új jelszót",
"AuthenticationRequiredUsernameHelpTextWarning": "Adjon meg új felhasználónevet",
"AppUpdated": "{appName} frissítve",
@@ -546,5 +546,7 @@
"DeleteSelectedApplicationsMessageText": "Biztosan törölni szeretne {count} kiválasztott importlistát?",
"CountApplicationsSelected": "{count} Gyűjtemény(ek) kiválasztva",
"ManageClients": "Ügyfelek kezelése",
"IndexerDownloadClientHealthCheckMessage": "Indexelők érvénytelen letöltési kliensekkel: {0}."
"IndexerDownloadClientHealthCheckMessage": "Indexelők érvénytelen letöltési kliensekkel: {0}.",
"PasswordConfirmation": "Jelszó megerősítése",
"SecretToken": "Titkos token"
}

View File

@@ -297,7 +297,6 @@
"Category": "Categoria",
"ClearHistory": "Cancella cronologia",
"ClearHistoryMessageText": "Sei sicuro di voler cancellare tutta la cronologia di {appName}?",
"Discord": "Discord",
"Donations": "Donazioni",
"EnableRssHelpText": "Abilita feed RSS per l'Indicizzatore",
"HomePage": "Pagina Iniziale",
@@ -524,5 +523,11 @@
"EditConnectionImplementation": "Aggiungi Connessione - {implementationName}",
"EditDownloadClientImplementation": "Aggiungi un Client di Download - {implementationName}",
"EditIndexerImplementation": "Aggiungi indicizzatore - {implementationName}",
"EditIndexerProxyImplementation": "Aggiungi indicizzatore - {implementationName}"
"EditIndexerProxyImplementation": "Aggiungi indicizzatore - {implementationName}",
"AdvancedSettingsShownClickToHide": "Impostazioni avanzate mostrate, clicca per nasconderle",
"AdvancedSettingsHiddenClickToShow": "Impostazioni avanzate nascoste, clicca per mostrarle",
"AddApplication": "Aggiungi Applicazione",
"AddCategory": "Aggiungi Categoria",
"ActiveApps": "App Attive",
"ActiveIndexers": "Indicizzatori Attivi"
}

View File

@@ -118,7 +118,7 @@
"Torrents": "급류",
"Type": "유형",
"DeleteApplicationMessageText": "알림 '{0}'을(를) 삭제하시겠습니까?",
"AuthenticationMethodHelpText": "{appName}에 액세스하려면 사용자 이름 암호 필요",
"AuthenticationMethodHelpText": "{appName}에 접근하려면 사용자 이름 암호 필요합니다",
"BackupFolderHelpText": "상대 경로는 {appName}의 AppData 디렉토리에 있습니다.",
"Branch": "분기",
"BranchUpdate": "{appName} 업데이트에 사용할 분기",

View File

@@ -24,7 +24,7 @@
"AnalyticsEnabledHelpText": "Stuur anonieme gebruiks- en foutinformatie naar de servers van {appName}. Dit omvat informatie over uw browser, welke {appName} WebUI pagina's u gebruikt, foutrapportage en OS en runtime versie. We zullen deze informatie gebruiken om prioriteiten te stellen voor functies en het verhelpen van fouten.",
"ApiKey": "API-sleutel",
"ApiKeyValidationHealthCheckMessage": "Maak je API sleutel alsjeblieft minimaal {0} karakters lang. Dit kan gedaan worden via de instellingen of het configuratiebestand",
"AppDataDirectory": "AppData folder",
"AppDataDirectory": "AppData map",
"AppDataLocationHealthCheckMessage": "Updaten zal niet mogelijk zijn om het verwijderen van AppData te voorkomen",
"AppProfileInUse": "App-profiel in gebruik",
"AppProfileSelectHelpText": "App-profielen worden gebruikt om de instellingen voor RSS, Automatisch zoeken en Interactief zoeken bij applicatiesynchronisatie te beheren",
@@ -47,7 +47,7 @@
"Backup": "Veiligheidskopie",
"BackupFolderHelpText": "Relatieve paden zullen t.o.v. de {appName} AppData map bekeken worden",
"BackupIntervalHelpText": "Tussentijd voor automatische back-up",
"BackupNow": "Nu backup nemen",
"BackupNow": "Back-up nu maken",
"BackupRetentionHelpText": "Automatische veiligheidskopieën ouder dan de retentie periode zullen worden opgeruimd",
"Backups": "Veiligheidskopieën",
"BeforeUpdate": "Voor Update",
@@ -96,9 +96,9 @@
"DeleteIndexerProxy": "Indexeerproxy verwijderen",
"DeleteIndexerProxyMessageText": "Weet u zeker dat u de proxy '{0}' wilt verwijderen?",
"DeleteNotification": "Verwijder Notificatie",
"DeleteNotificationMessageText": "Bent u zeker dat u de notificatie '{0}' wilt verwijderen?",
"DeleteNotificationMessageText": "Weet je zeker dat je de notificatie {name} wil verwijderen?",
"DeleteTag": "Verwijder Tag",
"DeleteTagMessageText": "Bent u zeker dat u de tag '{0}' wilt verwijderen?",
"DeleteTagMessageText": "Weet je zeker dat je de tag '{label}' wil verwijderen?",
"Description": "Beschrijving",
"Details": "Details",
"DevelopmentSettings": "Ontwikkelingsinstellingen",
@@ -116,11 +116,11 @@
"Edit": "Bewerk",
"EditIndexer": "Bewerk Indexeerder",
"EditSyncProfile": "Synchronisatieprofiel toevoegen",
"Enable": "Activeer",
"EnableAutomaticSearch": "Activeer Automatisch Zoeken",
"Enable": "Inschakelen",
"EnableAutomaticSearch": "Schakel automatisch zoeken in",
"EnableAutomaticSearchHelpText": "Zal worden gebruikt wanneer automatische zoekopdrachten worden uitgevoerd via de gebruikersinterface of door {appName}",
"EnableIndexer": "Indexer inschakelen",
"EnableInteractiveSearch": "Activeer Interactief Zoeken",
"EnableInteractiveSearch": "Schakel interactief zoeken in",
"EnableInteractiveSearchHelpText": "Zal worden gebruikt wanneer interactief zoeken wordt gebruikt",
"EnableRss": "RSS inschakelen",
"EnableRssHelpText": "Rss-feed voor Indexer inschakelen",
@@ -402,7 +402,7 @@
"UpdateCheckStartupNotWritableMessage": "Kan de update niet installeren omdat de map '{0}' niet schrijfbaar is voor de gebruiker '{1}'.",
"UpdateCheckStartupTranslocationMessage": "Kan de update niet installeren omdat de map '{0}' zich in een 'App Translocation' map bevindt.",
"UpdateCheckUINotWritableMessage": "Kan de update niet installeren omdat de UI map '{0}' niet schrijfbaar is voor de gebruiker '{1}'.",
"UpdateMechanismHelpText": "Gebruik het ingebouwde updatemechanisme of een extern script",
"UpdateMechanismHelpText": "Gebruik de ingebouwde updater van {appName} of een script",
"UpdateScriptPathHelpText": "Pad naar een aangepast script dat een uitgepakt updatepakket accepteert en de rest van het updateproces afhandelt",
"Updates": "Updates",
"Uptime": "Bedrijfstijd",
@@ -483,5 +483,6 @@
"AuthenticationRequiredUsernameHelpTextWarning": "Voeg een nieuwe gebruikersnaam in",
"AuthenticationRequiredWarning": "Om toegang zonder authenticatie te voorkomen vereist {appName} nu verificatie. Je kan dit optioneel uitschakelen voor lokale adressen.",
"Episode": "aflevering",
"CountApplicationsSelected": "{count} Collectie(s) geselecteerd"
"CountApplicationsSelected": "{count} Collectie(s) geselecteerd",
"BlackholeFolderHelpText": "De map waarin {appName} het {extension} bestand opslaat"
}

View File

@@ -420,7 +420,7 @@
"ApplyTagsHelpTextRemove": "Remover: eliminar as etiquetas adicionadas",
"DeleteSelectedDownloadClients": "Eliminar cliente de transferências",
"DeleteSelectedApplicationsMessageText": "Tem a certeza que quer eliminar as {count} aplicações seleccionadas?",
"DeleteSelectedDownloadClientsMessageText": "Tem a certeza que quer eliminar os {count} clientes de transferência seleccionados?",
"DeleteSelectedDownloadClientsMessageText": "Tem a certeza de que pretende eliminar o(s) cliente(s) de {count} transferência selecionado(s)?",
"DeleteSelectedIndexersMessageText": "Tem a certeza que quer eliminar os {count} indexadores seleccionados?",
"DownloadClientPriorityHelpText": "Priorizar múltiplos clientes de transferências. Utilizaremos round robin para clientes com a mesma prioridade.",
"More": "Mais",
@@ -433,8 +433,8 @@
"Track": "Rastreio",
"UpdateAvailable": "Nova atualização disponível",
"Label": "Rótulo",
"ConnectionLostReconnect": "O Radarr tentará ligar-se automaticamente, ou você pode clicar em Recarregar abaixo.",
"ConnectionLostToBackend": "O Radarr perdeu a ligação com o back-end e precisará ser recarregado para restaurar a funcionalidade.",
"ConnectionLostReconnect": "O {appName} tentará ligar-se automaticamente, ou você pode clicar em Recarregar abaixo.",
"ConnectionLostToBackend": "O {appName} perdeu a ligação com o back-end e precisará ser recarregado para restaurar a funcionalidade.",
"WhatsNew": "O que há de novo?",
"RecentChanges": "Mudanças recentes",
"minutes": "Minutos",

View File

@@ -195,7 +195,7 @@
"IndexerDetails": "Detalhes do Indexador",
"IndexerDisabled": "Indexador Desabilitado",
"IndexerFailureRate": "Taxa de falha do indexador",
"IndexerFlags": "Sinalizadores do indexador",
"IndexerFlags": "Sinalizadores do Indexador",
"IndexerHealthCheckNoIndexers": "Não há indexadores habilitados, o {appName} não retornará resultados para a pesquisa",
"IndexerInfo": "Info do Indexador",
"IndexerLongTermStatusCheckAllClientMessage": "Todos os indexadores estão indisponíveis devido a falhas por mais de 6 horas",
@@ -735,5 +735,14 @@
"TorrentBlackholeSaveMagnetFiles": "Salvar Arquivos Magnets",
"TorrentBlackholeSaveMagnetFilesExtensionHelpText": "Extensão a ser usada para links magnet, o padrão é '.magnet'",
"TorrentBlackholeSaveMagnetFilesHelpText": "Salve o link magnet se nenhum arquivo .torrent estiver disponível (útil apenas se o cliente de download suportar magnets salvos em um arquivo)",
"UseSsl": "Usar SSL"
"UseSsl": "Usar SSL",
"IndexerSettingsRejectBlocklistedTorrentHashes": "Rejeitar Hashes de Torrent Bloqueados Durante a Captura",
"IndexerSettingsRejectBlocklistedTorrentHashesHelpText": "Se um torrent for bloqueado por hash, ele pode não ser rejeitado corretamente durante o RSS/Pesquisa de alguns indexadores. Ativar isso permitirá que ele seja rejeitado após o torrent ser capturado, mas antes de ser enviado ao cliente.",
"CustomFilter": "Filtro Personalizado",
"LabelIsRequired": "Rótulo é requerido",
"ProwlarrDownloadClientsInAppOnlyAlert": "Os clientes de download destinam-se apenas a pesquisas no aplicativo {appName} e não sincronizam com aplicativos. Não há planos para adicionar tal funcionalidade.",
"ProwlarrDownloadClientsAlert": "Se você pretende fazer pesquisas diretamente em {appName}, você precisa adicionar Clientes de Download. Caso contrário, você não precisa adicioná-los aqui. Para pesquisas em seus aplicativos, os clientes de download configurados são usados.",
"Menu": "Menu",
"Mixed": "Misturado",
"Donate": "Doar"
}

View File

@@ -462,5 +462,7 @@
"EditIndexerImplementation": "Adăugați Indexator - {implementationName}",
"EditIndexerProxyImplementation": "Adăugați proxy indexator - {implementationName}",
"EditApplicationImplementation": "Adăugați aplicație - {implementareName}",
"RestartProwlarr": "Reporniți {appName}"
"RestartProwlarr": "Reporniți {appName}",
"AddDownloadClientToProwlarr": "Adăugarea unui client de descărcare permite {appName} să trimită versiuni direct din interfața utilizatorului în timp ce se efectuează o căutare manuală",
"ActiveApps": "Aplicații active"
}

View File

@@ -104,7 +104,7 @@
"ApplicationStatusCheckAllClientMessage": "Hatalar nedeniyle tüm listeler kullanılamıyor",
"CancelPendingTask": "Bu bekleyen görevi iptal etmek istediğinizden emin misiniz?",
"DeleteTag": "Etiketi Sil",
"BindAddressHelpText": "Tüm arayüzler için geçerli IP4 adresi veya '*'",
"BindAddressHelpText": "Tüm arayüzler için geçerli IP adresi, localhost veya '*'",
"ConnectSettings": "Bağlantı Ayarları",
"DatabaseMigration": "DB Geçişi",
"DeleteApplicationMessageText": "'{0}' bildirimini silmek istediğinizden emin misiniz?",
@@ -136,7 +136,7 @@
"AllIndexersHiddenDueToFilter": "Uygulanan filtre nedeniyle tüm filmler gizlendi.",
"AnalyticsEnabledHelpText": "Anonim kullanım ve hata bilgilerini {appName} sunucularına gönderin. Bu, tarayıcınızla ilgili bilgileri, kullandığınız {appName} WebUI sayfalarını, hata raporlamasının yanı sıra işletim sistemi ve çalışma zamanı sürümünü içerir. Bu bilgileri, özellikleri ve hata düzeltmelerini önceliklendirmek için kullanacağız.",
"ApiKey": "API Anahtarı",
"AppDataDirectory": "AppData dizini",
"AppDataDirectory": "Uygulama Veri Dizini",
"NoUpdatesAreAvailable": "Güncelleme yok",
"OAuthPopupMessage": "Pop-up'lar tarayıcınız tarafından engelleniyor",
"Ok": "Tamam",
@@ -306,7 +306,7 @@
"Automatic": "Otomatik",
"AutomaticSearch": "Otomatik Arama",
"Backup": "Destek olmak",
"Cancel": "İptal etmek",
"Cancel": "Vazgeç",
"Level": "Seviye",
"Time": "Zaman",
"MaintenanceRelease": "Bakım Sürümü: hata düzeltmeleri ve diğer iyileştirmeler. Daha fazla ayrıntı için Github İşlem Geçmişine bakın",
@@ -336,8 +336,8 @@
"ApplyTagsHelpTextAdd": "Ekle: Etiketleri mevcut etiket listesine ekleyin",
"ApplyTagsHelpTextHowToApplyApplications": "Seçilen filmlere etiketler nasıl uygulanır",
"ApplyTagsHelpTextRemove": "Kaldır: Girilen etiketleri kaldırın",
"ApplyTagsHelpTextHowToApplyIndexers": "Seçilen filmlere etiketler nasıl uygulanır",
"ApplyTagsHelpTextReplace": "Değiştir: Etiketleri girilen etiketlerle değiştirin (tüm etiketleri temizlemek için hiçbir etiket girmeyin)",
"ApplyTagsHelpTextHowToApplyIndexers": "Seçilen indeksleyicilere etiketler nasıl uygulanır?",
"ApplyTagsHelpTextReplace": "Değiştir: Etiketleri girilen etiketlerle değiştirin (tüm etiketleri kaldırmak için etiket girmeyin)",
"DeleteSelectedDownloadClients": "İndirme İstemcisini Sil",
"DownloadClientPriorityHelpText": "Birden çok İndirme İstemcisine öncelik verin. Round-Robin, aynı önceliğe sahip müşteriler için kullanılır.",
"Genre": "Türler",
@@ -348,7 +348,7 @@
"RecentChanges": "Son değişiklikler",
"minutes": "Dakika",
"WhatsNew": "Ne var ne yok?",
"ConnectionLostReconnect": "Radarr otomatik olarak bağlanmayı deneyecek veya aşağıdan yeniden yükle'yi tıklayabilirsiniz.",
"ConnectionLostReconnect": "{appName} otomatik bağlanmayı deneyecek veya aşağıda yeniden yükle seçeneğini işaretleyebilirsiniz.",
"NotificationStatusAllClientHealthCheckMessage": "Hatalar nedeniyle tüm listeler kullanılamıyor",
"NotificationStatusSingleClientHealthCheckMessage": "Hatalar nedeniyle kullanılamayan listeler: {0}",
"Applications": "Uygulamalar",
@@ -372,5 +372,25 @@
"EditIndexerProxyImplementation": "Koşul Ekle - {implementationName}",
"AddCustomFilter": "Özel Filtre Ekleyin",
"AddDownloadClientImplementation": "İndirme İstemcisi Ekle - {implementationName}",
"EditDownloadClientImplementation": "İndirme İstemcisi Ekle - {implementationName}"
"EditDownloadClientImplementation": "İndirme İstemcisi Ekle - {implementationName}",
"ApplicationURL": "Uygulama URL'si",
"AuthenticationRequired": "Kimlik Doğrulama Gerekli",
"ApplyChanges": "Değişiklikleri Uygula",
"CountDownloadClientsSelected": "{count} indirme istemcisi seçildi",
"CountIndexersSelected": "{count} dizin oluşturucu seçildi",
"AuthenticationRequiredWarning": "Kimlik doğrulaması olmadan uzaktan erişimi engellemek için, {appName}'da artık kimlik doğrulamanın etkinleştirilmesini gerektiriyor. İsteğe bağlı olarak yerel adresler için kimlik doğrulamayı devre dışı bırakabilirsiniz.",
"Clone": "Klon",
"Category": "Kategori",
"AppUpdated": "{appName} Güncellendi",
"AppUpdatedVersion": "{appName}, `{version}` sürümüne güncellendi; en son değişikliklerin etkin olabilmesi için {appName} uygulamasını yeniden başlatmanız gerekli",
"ApplicationUrlHelpText": "Bu uygulamanın http(s)://, bağlantı noktası ve URL tabanını içeren harici URL'si",
"AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Yeni şifreyi onayla",
"AuthenticationRequiredPasswordHelpTextWarning": "Yeni şifre girin",
"AuthenticationRequiredUsernameHelpTextWarning": "Yeni kullanıcı adınızı girin",
"ConnectionLostToBackend": "{appName}'ın arka uçla bağlantısı kesildi ve işlevselliğin geri kazanılması için yeniden yüklenmesi gerekecek.",
"BlackholeFolderHelpText": "{appName} uygulamasının {extension} dosyasını depolayacağı klasör",
"AuthenticationMethod": "Kimlik Doğrulama Yöntemi",
"AuthenticationMethodHelpTextWarning": "Lütfen geçerli bir kimlik doğrulama yöntemi seçin",
"AuthenticationRequiredHelpText": "İstekler için Kimlik doğrulamanın gereklilik ayarını değiştirin. Riskleri anlamadığınız sürece değiştirmeyin.",
"CustomFilter": "Özel Filtre"
}

View File

@@ -34,7 +34,7 @@
"DeleteBackupMessageText": "Ви впевнені, що хочете видалити резервну копію '{0}'?",
"DeleteDownloadClient": "Видалити клієнт завантаження",
"DeleteDownloadClientMessageText": "Ви впевнені, що хочете видалити клієнт завантаження '{0}'?",
"Authentication": "Аутентифікація",
"Authentication": "Автентифікація",
"Automatic": "Автоматичний",
"AutomaticSearch": "Автоматичний пошук",
"Backup": "Резервне копіювання",
@@ -344,7 +344,7 @@
"DeleteSelectedApplicationsMessageText": "Ви впевнені, що хочете видалити тег {0} ?",
"DeleteSelectedDownloadClients": "Видалити клієнт завантаження",
"DeleteSelectedDownloadClientsMessageText": "Ви впевнені, що хочете видалити тег {0} ?",
"ApplyTagsHelpTextHowToApplyIndexers": "Як застосувати теги до вибраних фільмів",
"ApplyTagsHelpTextHowToApplyIndexers": "Як застосувати теги до вибраних індексаторів",
"ApplyTagsHelpTextRemove": "Видалити: видалити введені теги",
"DeleteSelectedIndexersMessageText": "Ви впевнені, що хочете видалити тег {0} ?",
"DownloadClientPriorityHelpText": "Надайте пріоритет кільком клієнтам завантаження. Круговий алгоритм використовується для клієнтів з таким же пріоритетом.",
@@ -384,5 +384,9 @@
"CountApplicationsSelected": "Вибрано колекцій: {0}",
"Applications": "Додатки",
"Categories": "Категорії",
"EditDownloadClientImplementation": "Додати клієнт завантаження - {implementationName}"
"EditDownloadClientImplementation": "Додати клієнт завантаження - {implementationName}",
"ApplyChanges": "Застосувати зміни",
"AuthenticationMethod": "Метод автентифікації",
"AuthenticationMethodHelpTextWarning": "Виберіть дійсний метод автентифікації",
"AppUpdated": "{appName} Оновлено"
}

View File

@@ -22,7 +22,7 @@
<PackageReference Include="System.Text.Json" Version="6.0.9" />
<PackageReference Include="MonoTorrent" Version="2.0.7" />
<PackageReference Include="YamlDotNet" Version="13.1.1" />
<PackageReference Include="AngleSharp" Version="1.0.6" />
<PackageReference Include="AngleSharp" Version="1.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Common\Prowlarr.Common.csproj" />

View File

@@ -12,6 +12,7 @@ namespace Prowlarr.Api.V1.Indexers
public double? SeedRatio { get; set; }
public int? SeedTime { get; set; }
public int? PackSeedTime { get; set; }
public bool? RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
}
public class IndexerBulkResourceMapper : ProviderBulkResourceMapper<IndexerBulkResource, IndexerDefinition>
@@ -35,6 +36,7 @@ namespace Prowlarr.Api.V1.Indexers
((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.SeedRatio = resource.SeedRatio ?? ((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.SeedRatio;
((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.SeedTime = resource.SeedTime ?? ((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.SeedTime;
((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.PackSeedTime = resource.PackSeedTime ?? ((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.PackSeedTime;
((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.RejectBlocklistedTorrentHashesWhileGrabbing = resource.RejectBlocklistedTorrentHashesWhileGrabbing ?? ((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.RejectBlocklistedTorrentHashesWhileGrabbing;
}
});

View File

@@ -5016,6 +5016,10 @@
"type": "integer",
"format": "int32",
"nullable": true
},
"rejectBlocklistedTorrentHashesWhileGrabbing": {
"type": "boolean",
"nullable": true
}
},
"additionalProperties": false

View File

@@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
</ItemGroup>
<ItemGroup>

View File

@@ -2328,9 +2328,9 @@ camelcase@^5.3.1:
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001517:
version "1.0.30001525"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001525.tgz#d2e8fdec6116ffa36284ca2c33ef6d53612fe1c8"
integrity sha512-/3z+wB4icFt3r0USMwxujAqRvaD/B7rvGTsKhbhSQErVrJvkZCLhgNLJxU8MevahQVH6hCU9FsHdNUFbiwmE7Q==
version "1.0.30001591"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz"
integrity sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==
chalk@^1.1.3:
version "1.1.3"